在Python中创建一个临时FIFO(命名管道)?

33

你如何在Python中创建一个临时的FIFO(有名管道)? 这应该可以工作:

import tempfile

temp_file_name = mktemp()
os.mkfifo(temp_file_name)
open(temp_file_name, os.O_WRONLY)
# ... some process, somewhere, will read it ...

然而,我感到犹豫是因为在Python文档11.6中有一个很大的警告,它被弃用了并有可能被删除。

编辑:值得注意的是,我已经尝试过tempfile.NamedTemporaryFile(以及间接尝试tempfile.mkstemp),但是在运行os.mkfifo时会抛出:

OSError -17: 文件已存在

当你对mkstemp/NamedTemporaryFile创建的文件运行它时。


3
正如您所提到的,由于os.mkfifo在文件存在时会抛出错误,因此使用mkstemp甚至完全确定的文件名(除了一种不好的感觉和某些人天真地复制您的代码的风险)并不存在安全漏洞。 - user79758
1
问题:您打算如何将管道的名称传达给其他进程?这个通道如何保护? - Steven Huwig
@Joe:你说得对。我没有想到那个。@Steven Huwig:这两个进程都是由同一个父进程生成的,尽管我可能会在某个时候为此使用一个新的fifo。你为什么问呢? - Brian M. Hunt
6个回答

32

os.mkfifo()方法会在文件已经存在时抛出异常OSError: [Errno 17] File exists,因此在这里不存在安全问题。使用tempfile.mktemp()的安全问题在于竞争条件,攻击者有可能在你打开文件之前创建具有相同名称的文件,但由于os.mkfifo()方法如果文件已经存在则失败,所以这不是个问题。

然而,由于mktemp()方法已被弃用,因此不应该再使用它。你可以使用tempfile.mkdtemp()代替它:

import os, tempfile

tmpdir = tempfile.mkdtemp()
filename = os.path.join(tmpdir, 'myfifo')
print filename
try:
    os.mkfifo(filename)
except OSError, e:
    print "Failed to create FIFO: %s" % e
else:
    fifo = open(filename, 'w')
    # write stuff to fifo
    print >> fifo, "hello"
    fifo.close()
    os.remove(filename)
    os.rmdir(tmpdir)

编辑:我应该明确一点,仅仅因为使用mktemp()函数可以避免漏洞,并不意味着就没有其他需要考虑的安全问题;例如,攻击者可以在你的程序之前创建fifo(如果他们具有适当的权限),如果错误/异常没有被正确处理,这可能会导致你的程序崩溃。


6
os.rmdir(tmpdir) 应该不在 try-else 块内吗? - Artelius
2
你的意思是 except OSError as e - Tim Dierks
@TimDierks:新语法确实更易读,也是Python 3唯一支持的语法,但旧语法对所有Python 2.x版本都有效,而新语法至少需要Python 2.6。 - Tobias

8

您可能会发现使用以下上下文管理器很方便,它可以为您创建和删除临时文件:

import os
import tempfile
from contextlib import contextmanager


@contextmanager
def temp_fifo():
    """Context Manager for creating named pipes with temporary names."""
    tmpdir = tempfile.mkdtemp()
    filename = os.path.join(tmpdir, 'fifo')  # Temporary filename
    os.mkfifo(filename)  # Create FIFO
    try:
        yield filename
    finally:
        os.unlink(filename)  # Remove file
        os.rmdir(tmpdir)  # Remove directory

例如,您可以像这样使用它:

with temp_fifo() as fifo_file:
    # Pass the fifo_file filename e.g. to some other process to read from.
    # Write something to the pipe 
    with open(fifo_file, 'w') as f:
        f.write("Hello\n")

4

使用怎样的

d = mkdtemp()
t = os.path.join(d, 'fifo')

它遭受了与mktemp相同的安全问题。不过还是谢谢,Brendan。 - Brian M. Hunt
1
只有在用户已经破解了运行脚本的帐户(目录为0700)时,它才具有相同的安全问题。在这种情况下,他们可能会做更糟糕的事情。 - user79758
1
抱歉,我错了。这个与mktemp不同,因为如果文件存在,mkfifo()会拒绝(即没有机会进行MiM攻击)。这个方法可行。 - Brian M. Hunt

3
如果是在程序内部使用,而不涉及任何外部组件,请查看Queue模块。另外,Python队列具有线程安全的优点。

谢谢Nilamo。不幸的是这不是用于内部通信。我在其他地方使用了队列。感谢您的建议。 - Brian M. Hunt
1
没问题,只是提醒你要使用“最简单可行的方法”。如果你确实需要fifo,那么这个答案就没有帮助了。 - nilamo

1
有效地说,所有mkstemp做的就是在循环中运行mktemp并不断尝试独占创建,直到成功为止(请参见stdlib源代码here)。您可以使用os.mkfifo执行相同的操作:
import os, errno, tempfile

def mkftemp(*args, **kwargs):
    for attempt in xrange(1024):
        tpath = tempfile.mktemp(*args, **kwargs)

        try:
            os.mkfifo(tpath, 0600)
        except OSError as e:
            if e.errno == errno.EEXIST:
                # lets try again
                continue
            else:
                raise
        else:
           # NOTE: we only return the path because opening with
           # os.open here would block indefinitely since there 
           # isn't anyone on the other end of the fifo.
           return tpath
    else:
        raise IOError(errno.EEXIST, "No usable temporary file name found")

-1

为什么不直接使用mkstemp()呢?

例如:

import tempfile
import os

handle, filename = tempfile.mkstemp()
os.mkfifo(filename)
writer = open(filename, os.O_WRONLY)
reader = open(filename, os.O_RDONLY)
os.close(handle)

2
os.mkfifo抛出OSError 17:文件已存在。谢谢。 - Brian M. Hunt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接