【问题标题】:Write file with specific permissions in Python在 Python 中写入具有特定权限的文件
【发布时间】:2011-08-03 05:17:19
【问题描述】:

我正在尝试创建一个只有用户可读和可写的文件 (0600)。

使用os.open() 的唯一方法如下?

import os
fd = os.open('/path/to/file', os.O_WRONLY, 0o600)
myFileObject = os.fdopen(fd)
myFileObject.write(...)
myFileObject.close()

理想情况下,我希望能够使用with 关键字,以便自动关闭对象。有没有更好的方法来做我上面所做的事情?

【问题讨论】:

  • 如果我们已经使用 pandas 读取了 csv 怎么办......然后在写入之后如何设置权限。

标签: python file-io permissions


【解决方案1】:

更新 伙计们,虽然我在这里感谢你们的支持,但我本人不得不在下面反对我最初提出的解决方案。原因是以这种方式做事,会有一段时间,无论多么小,文件确实存在,并且没有适当的权限 - 这留下了广泛的攻击方式,甚至是错误的行为。
当然,首先创建具有正确权限的文件是可行的方法 - 与正确性相反,使用 Python 的 with 只是一些糖果。

因此,请将此答案作为“不该做什么”的示例;

原帖

您可以改用os.chmod

>>> import os
>>> name = "eek.txt"
>>> with open(name, "wt") as myfile:
...   os.chmod(name, 0o600)
...   myfile.write("eeek")
...
>>> os.system("ls -lh " + name)
-rw------- 1 gwidion gwidion 4 2011-04-11 13:47 eek.txt
0
>>>

(请注意,在 Python 中使用八进制的方法是显式的 - 像在“0o600”中一样在它前面加上“0o”。在 Python 2.x 中,它可以只写 0600 - 但是这既具有误导性又被弃用。)

但是,如果您的安全性至关重要,您可能应该像您一样使用os.open 创建它,并使用os.fdopenos.open 返回的文件描述符中检索 Python File 对象。

【讨论】:

  • 我知道这不是问题,但相同的代码对于动态设置文件所有者和组很有用,只需更改 os.chown(name, uid, gid) 的 chmod 行。
【解决方案2】:

有什么问题? file.close() 将关闭文件,即使它是用os.open() 打开的。

with os.fdopen(os.open('/path/to/file', os.O_WRONLY | os.O_CREAT, 0o600), 'w') as handle:
  handle.write(...)

【讨论】:

  • 我认为这个答案比我的要好,但这不是“问题是什么”:您提出了一个 OP 不知道的新因素——即文件处理程序转换为 Python 文件对象
  • @jsbueno:我刚刚将前两行组合在一起并使用了with。在问题的示例中,文件无论如何都通过myFileObject.close() 关闭。
  • 这对我不起作用。带有这些标志的 os.open 期望文件已经存在。>>> f = os.open('test.txt', os.O_WRONLY, 0600) Traceback (last recent call last): File "", line 1、在 OSError: [Errno 2] No such file or directory: 'test.txt'
  • @stair314,这可能是因为您没有指定O_CREAT。查看当前版本的答案。
  • @vartec,这个答案有一个umask 特定的问题。我已经发布了解决这个问题的答案。
【解决方案3】:

此答案解决了answer by vartec 的多个问题,尤其是umask 问题。

import os
import stat

# Define file params
fname = '/tmp/myfile'
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL  # Refer to "man 2 open".
mode = stat.S_IRUSR | stat.S_IWUSR  # This is 0o600.
umask = 0o777 ^ mode  # Prevents always downgrading umask to 0.

# For security, remove file with potentially elevated mode
try:
    os.remove(fname)
except OSError:
    pass

# Open file descriptor
umask_original = os.umask(umask)
try:
    fdesc = os.open(fname, flags, mode)
finally:
    os.umask(umask_original)

# Open file handle and write to file
with os.fdopen(fdesc, 'w') as fout:
    fout.write('something\n')

如果所需的模式是0600,可以更清楚地指定为八进制数0o600。更好的是,只需使用 stat 模块。

即使首先删除旧文件,仍可能出现竞争条件。在标志中包含os.O_EXCLos.O_CREAT 将阻止文件在由于竞争条件而存在时被创建。这是必要的辅助安全措施,以防止打开可能已经存在且具有潜在提升的mode 的文件。在 Python 3 中,如果文件存在,则会引发带有 [Errno 17] 的 FileExistsError

未能首先将umask 设置为00o777 ^ mode 可能会导致os.open 设置不正确的mode(权限)。这是因为默认的umask 通常不是0,它会应用到指定的mode。例如,如果我原来的umask20o002,而我指定的模式是0o222,如果我没有先设置umask,则生成的文件可以改为有@ 的mode 987654346@,这不是我想要的。根据man 2 open,创建文件的模式为mode & ~umask

umask 会尽快恢复到原来的值。这种获取和设置不是线程安全的,在多线程应用程序中必须使用threading.Lock

有关 umask 的更多信息,请参阅this thread

【讨论】:

  • 非常感谢您解决umask 问题,解决了我的问题。
  • 感谢您的回答!我无法弄清楚为什么我从 python 保存的每个文件都获得 0600 文件权限,即使使用os.open(mode=0666) 也是如此。这解决了问题。
  • 这个答案让我对引用 XOR 感到有点困惑。 umask 不会异或。但是,如果您想确保文件的权限与指定的完全一致,而不是因为用户的 umask 而设置的位较少,则将 umask 设置为 0 是正确的。如果您的目标只是确保文件不是世界可读/可写的,则无需设置 umask。
  • @Nelson 已修复。移除异或。目标是通用的,即拥有一个可以写入具有任意指定参数化权限的文件的函数。
【解决方案4】:

我想建议对 A-B-B 的出色答案进行修改,以便更清楚地区分关注点。主要优点是您可以将打开文件描述符期间发生的异常与实际写入文件期间的其他问题分开处理。

外部try ... finally 块负责在打开文件描述符时处理权限和umask 问题。内部 with 块处理在使用 Python 文件对象时可能出现的异常(因为这是 OP 的愿望):

try:
    oldumask = os.umask(0)
    fdesc = os.open(outfname, os.O_WRONLY | os.O_CREAT, 0o600)
    with os.fdopen(fdesc, "w") as outf:
        # ...write to outf, closes on success or on exceptions automatically...
except IOError, ... :
    # ...handle possible os.open() errors here...
finally:
    os.umask(oldumask)

如果你想追加到文件而不是写入,那么文件描述符应该像这样打开:

fdesc = os.open(outfname, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o600)

和这样的文件对象:

with os.fdopen(fdesc, "a") as outf:

当然所有其他常见的组合都是可能的。

【讨论】:

  • 在这种情况下附加现有文件没有意义,因为指定给os.openmode 将永远不会被应用。以前存在的模式将保留。
【解决方案5】:

问题是关于设置权限以确保文件不会是世界可读的(仅对当前用户读/写)。

不幸的是,代码本身:

fd = os.open('/path/to/file', os.O_WRONLY, 0o600)

不保证权限将被拒绝给全世界。它确实尝试为当前用户设置 r/w (前提是 umask 允许),就是这样!

在两个非常不同的测试系统上,此代码使用我的默认 umask 和 -rw-rw-rw- 创建一个文件,其中包含 -rw-r--r-- strong> 使用 umask(0) 这绝对不是我们想要的(并且会带来严重的安全风险)。

如果你想确保文件没有为组和世界设置位,你必须首先对这些位进行 umask(记住 - umask 是拒绝权限):

os.umask(0o177)

此外,要 100% 确保文件不存在具有不同权限的文件,您必须先 chmod/删除它(删除更安全,因为您可能在目标目录中没有写入权限 - 如果您有安全问题,你不想在不允许的地方写一些文件!),否则如果黑客在你之前创建了具有全球读写权限的文件,你可能会遇到安全问题。移动。在这种情况下,os.open 将在不设置权限的情况下打开文件,而您将得到一个世界性的 r/w 秘密文件...

所以你需要:

import os
if os.path.isfile(file):
    os.remove(file)
original_umask = os.umask(0o177)  # 0o777 ^ 0o600
try:
    handle = os.fdopen(os.open(file, os.O_WRONLY | os.O_CREAT, 0o600), 'w')
finally:
    os.umask(original_umask)

这是确保创建 -rw-------- 文件的安全方法,无论您的环境和配置如何。当然,您可以根据需要捕获和处理 IOErrors。如果您在目标目录中没有写入权限,则您应该无法创建该文件,如果该文件已存在,则删除将失败。

【讨论】:

  • 按照您的建议删除文件后,flags 的理想值可能是os.O_WRONLY | os.O_CREAT | os.O_EXCL。请注意,os.O_EXCL 会阻止打开已存在的文件。
  • "代码:...不保证权限将被拒绝给世界。它保证文件将对当前用户具有读写权限,就是这样!" - 这是错误的。提供给os.open() 的权限将按照创建文件时提供的方式设置(在使用 umask 屏蔽之后)。 0o600 确实意味着该文件将是 go-rwx。它确实 not 意味着该文件是 u+rw(因为 umask)。有关详细信息,请参阅open(2) man page。可能让您感到困惑的是模式 only 可用于文件创建;如果文件已经存在,则忽略它。
  • @marcelm 是的,你是对的(在我事先设置 umask 的上下文中我太过分了),我修好了,谢谢!
【解决方案6】:

我会做不同的。

from contextlib import contextmanager

@contextmanager
def umask_helper(desired_umask):
    """ A little helper to safely set and restore umask(2). """
    try:
        prev_umask = os.umask(desired_umask)
        yield
    finally:
        os.umask(prev_umask)

# ---------------------------------- […] ---------------------------------- #

        […]

        with umask_helper(0o077):
            os.mkdir(os.path.dirname(MY_FILE))
            with open(MY_FILE, 'wt') as f:
                […]

文件操作代码往往已经是try-except-heavy;使用 os.umask 的 finally 让情况变得更糟不会让你的眼睛更快乐。同时,滚动您自己的上下文管理器就这么简单,而且缩进嵌套更加整洁。

【讨论】:

    猜你喜欢
    • 2012-02-04
    • 2016-02-16
    • 2012-09-17
    • 2019-09-03
    • 2016-10-25
    • 1970-01-01
    • 2013-07-11
    • 2014-06-20
    相关资源
    最近更新 更多