Python中的原子性“ln -sf”(符号链接覆盖现有文件)

3

我想要创建一个符号链接,如果需要的话,覆盖现有的文件或符号链接。

我发现os.path.exists只对非断开的符号链接返回True,因此我猜测任何测试都必须包括os.path.lexists

在Python中实现ln -sf最原子的方法是什么?(即,在删除和符号链接创建之间防止另一个进程创建文件)


区别:这个问题没有指定原子要求


如果您准备ln -s file tmplink,那么mv tmplink link是原子操作。 - Amadan
@Amadan 感谢您的建议。我仍然看到一个安全漏洞,但我希望在我的回答中尽可能地解决了它。 - Tom Hale
就此而言,“ln -sf”本身实际上并不是原子的。GNU Coreutils 内部实现了@Amadan提出的解决方案(当前已接受的答案也是如此);FreeBSD和Busybox只是在链接之前删除目标文件。因此,对于这个问题,“par”实际上很容易被清除。 - JamesTheAwesomeDude
1个回答

1
这段代码旨在最小化竞态条件的可能性:
import os
import tempfile

def symlink_force(target, link_name):
    '''
    Create a symbolic link link_name pointing to target.
    Overwrites link_name if it exists.
    '''

    # os.replace() may fail if files are on different filesystems
    link_dir = os.path.dirname(link_name)

    while True:
        temp_link_name = tempfile.mktemp(dir=link_dir)
        try:
            os.symlink(target, temp_link_name)
            break
        except FileExistsError:
            pass
    try:
        os.replace(temp_link_name, link_name)
    except OSError:  # e.g. permission denied
        os.remove(temp_link_name)
        raise

注意:
  1. 如果函数中断(例如计算机崩溃),目标可能存在一个额外的随机链接。

  2. 仍然存在一个不太可能的竞争条件:在随机命名的 temp_link_name 处创建的符号链接可能在替换 link_name 之前被另一个进程修改。

我提出了一个Python问题,以突出 os.symlink() 需要目标不存在的问题。

感谢Robert Seimer 的贡献


我针对上述安全漏洞提出了一个 问题 - Tom Hale
你可以通过将 mktempsymlink 调用放在一个循环内,重试直到赢得比赛来处理(不太可能的)竞争情况。显然,您需要检查 symlink 失败是因为 EEXIST 而不是其他永远无法成功的原因。为了提高效率,您可能希望将嵌入的 dirname 调用提升到循环之上。 - ottomeister
@ottomeister 谢谢,你的建议很有帮助。但是如何防止在创建符号链接之前和之后更改 mktemp 命名的文件呢? - Tom Hale
你无法防止这种情况发生。任何有写入权限的不良进程都可以破坏整个安排。在重命名之后,有可能会有东西替换或删除新符号链接,就像有可能在重命名之前替换或删除它一样。但是,如果你担心并发的 mktemp 意外地在同一个名称上发生冲突,那么你可以 mkdtemp 一个目录,在该目录中创建新的符号链接,然后将符号链接重命名为原始符号链接(然后 rmdir 临时目录)。文件系统内的 rename 是原子操作。 - ottomeister
我有点惊讶,这个功能没有开箱即用。 - JamesTheAwesomeDude

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