读取 umask(线程安全)

Reading umask (thread-safe)

我知道这种模式可以读取 Python 中的 umask:

current_umask = os.umask(0)  # line1
os.umask(current_umask)      # line2
return current_umask         # line3

但这不是线程安全的。

在第 1 行和第 2 行之间执行的线程将具有不同的 umask。

是否有线程安全的方法来读取 Python 中的 umask?

相关:https://bugs.python.org/issue35275

如果您的系统在 /proc/[pid]/status 中有 Umask 字段,您可以从中读取:

import os

def getumask():
    pid = os.getpid()
    with open(f'/proc/{pid}/status') as f:
        for l in f:
            if l.startswith('Umask'):
                return int(l.split()[1], base=8)
        return None

在 CentOS 7.5、Debian 9.6 下测试。

或者,您可以添加一个线程锁:)

umask 由子进程继承。您可以创建一个管道,派生一个子进程,在那里获取 umask 并将结果写入管道,以便父进程可以读取它。

相当昂贵,但没有任何特殊要求,如 /proc 虚拟文件系统。下面是一个只有低级 OS 调用(所有异步安全)且没有错误检查的示例:

import os
import struct

def get_umask():
    pipe = os.pipe()
    pid = os.fork()
    if pid == 0:
        os.close(pipe[0])
        umask = os.umask(0)
        os.write(pipe[1], struct.pack('H', umask))
        os.close(pipe[1])
        os._exit(0)
    else:
        os.close(pipe[1])
        value = os.read(pipe[0], 2)
        os.close(pipe[0])
        os.waitpid(pid, 0)
        return struct.unpack('H', value)[0]

print("umask {:03o}".format(get_umask()))

可以通过创建临时文件并检查其权限来确定umask。这应该适用于所有 *nix 系统:

def get_umask():
    import os, os.path, random, tempfile
    while True:
        # Generate a random name
        name = 'test'
        for _ in range(8):
            name += chr(random.randint(ord('a'), ord('z')))
        path = os.path.join(tempfile.gettempdir(), name)
        # Attempt to create a file with full permissions
        try:
            fd = os.open(path, os.O_RDONLY|os.O_CREAT|os.O_EXCL, 0o777)
        except FileExistsError:
            # File exists, try again
            continue
        try:
            # Deduce umask from the file's permission bits
            return 0o777 & ~os.stat(fd).st_mode
        finally:
            os.close(fd)
            os.unlink(path)

我知道的唯一真正、明确的线程安全方法是调用一个新进程。

import subprocess
umask_cmd = ('python', '-c', 'import os; print(os.umask(0777))')
umask = int(subprocess.check_output(umask_cmd))

请注意,如果您有 bash 或另一个 shell,您也可以调用它。由于它可能在一个奇怪的系统上,我选择在 umask_cmd 中使用 python 子进程,因为你必须有 python。如果你在一个非奇怪的 *nix 系统上,那么你可以使用 sh 或 bash 代替。