【问题标题】:Atomically change attributes of a file以原子方式更改文件的属性
【发布时间】:2020-06-10 11:21:24
【问题描述】:

我想以原子方式更改文件的某些属性。 (背景:这是针对用户空间 NFS 实现,其中 SETATTR 调用在文件上设置多个属性)。

我未能解决的问题是以原子方式进行更新。即,stat()s 或rename()s 文件的不同进程不应看到部分更新的属性。

不同的简化(即没有错误检查和符号链接处理)方法及其缺点是:

  • 使用传统函数:

    int setattr(char *path, attributes attrs)
    {
        lchown(path, attr.owner, attr.group);
        chmod(path, attr.mode)
        utime(path, ...)
        return 0
    }
    

    这不是原子的(并且无法在符号链接上正常工作)。

  • 使用fchown() 等人。

    int setattr(char *path, attributes attrs)
    {
         int fd = open(path, O_WRONLY);
         fchown(fd, attr.owner, attr.group);
         fchmod(fd, attr.mode);
         futimens(fd, ...);
         return 0;
    }
    

    这有点原子,但如果文件的模式不允许打开,或者文件是符号链接(在这种情况下应该修改符号链接的属性而不是目标的属性),则会失败。将O_PATH 添加到open() 调用在这里也无济于事,因为fchown()fchmod() 然后会失败。

  • 使用fchownat() 等人。

    int setattr(char *path, attributes attrs)
    {
         int fd = open(dirname(path), O_WRONLY | O_PATH);
         fchownat(fd, basename(path), attr.owner, attr.group, AT_SYMLINK_NOFOLLOW);
         fchmodat(fd, basename(path), attr.mode, 0);
         utimensat(fd, basename(path), ..., AT_SYMLINK_NOFOLLOW);
         return 0;
    }
    

    这看起来最有希望,但这又不是原子的。

我是否遗漏了什么或者没有一种方法可以满足我的需求?

【问题讨论】:

  • O_WRONLY 不会清除文件的内容吗?
  • @MarcoBonelli 它没有。这就是O_TRUNC 的语义。

标签: c unix system-calls


【解决方案1】:

也就是说,stat()s 或rename()s 文件的不同进程不应看到部分更新的属性。

如果文件已经存在,那么在修改它之前,您几乎无法阻止其他进程访问它。那艘船已经航行了。


rename 是原子的。

标准解决方案是:

  1. 在目标目录中创建一个具有唯一临时名称的文件。它必须在同一个目录中,以确保它在文件系统/相同的挂载点中,这样就不需要文件内容(inode)副本。
  2. 填充文件。如果使用缓冲 I/O(writepwritewritev 或其他系统调用之外的任何其他内容)写入文件,则需要刷新或关闭它(这会导致刷新,这最终执行上述系统调用之一)。
  3. 如有必要,请设置正确的权限和属性。创建文件时可以设置权限。
  4. rename 文件到它的最终文件名。 rename 基本上以原子方式为文件内容(inode)创建一个新文件名(硬链接)并删除旧文件名。

这样当文件存在时它是完整的并且具有正确的权限和属性。

【讨论】:

  • 如果有的话,这只会增加更多问题,因为程序可以在进程中间创建一个旧名称的文件。
  • @MarcoBonelli 没有什么可以阻止该程序在它已经退出时重新创建文件。 fopen(filename, "w") 你的文件不见了。
  • 并销毁文件内容?想象一下,如果命令行工具对您的一个程序执行此操作。正如我所说,这似乎增加了更多问题。
  • @MarcoBonelli 将解决方案改写为对您来说不那么模棱两可。
  • @MarcoBonelli 想象一下,如果命令行工具对您的某个程序执行此操作 - 当您指定相同的文件名时,所有这些都会执行。从shell重定向>file开始。
【解决方案2】:

不幸的是,如果不为它编写自己的自定义系统调用或内核模块,这是不可能的。

虽然 Linux 内核本身确实有能力实现这一点,但它并没有为它公开任何用户空间 API。该任务需要一个系统调用来一次设置所有属性,或者需要某种“锁定”机制来防止文件被其他进程访问(即open/stat),即使它们具有这样做的权利。由于 Linux 没有提供这样的系统调用,也没有提供这样的“锁定”机制,因此您想要实现的目标在用户空间是不可能的。

【讨论】:

  • 正如对@goswin-von-brederlow 的评论中所说,我认为这是真的。我现在采用fchownat() 方法,因为守护进程是单线程竞争,至少现在不会通过 NFS 发生,这是我在没有内核补丁的情况下可以达到的最好效果。谢谢。
【解决方案3】:

你不能,而且你展示的例子都不是原子的。在 chown 和 chmod 或 chmod 和 utimes 调用之间,其他线程总是有可能访问文件。

在 Linux 下,对于用户空间 NFS 服务器等情况,存在文件系统句柄(与文件描述符相对)的概念。目前我在谷歌中唯一能找到的是来自 xfsprogs 的联机帮助页:handle manpage。但我认为近年来这已在 Linux 中得到推广,以适用于更多文件系统。

【讨论】:

  • 我现在所做的是确保至少 nfs 客户端使用是序列化的,因此更新是原子的。对本地文件系统所做的更改仍然存在竞争,但我可以(并且必须)接受这一点。
  • 在这种情况下,无论您做什么,它都将始终有效。根本不可能有任何 stat() 或 rename() 继续进行,因为在当前操作完成之前序列化会阻止它们。所以这个问题变得毫无意义。
  • 这是一个总的想法,它总是有效的...... :-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-27
  • 2015-09-24
  • 2014-12-06
  • 1970-01-01
  • 1970-01-01
  • 2021-09-03
相关资源
最近更新 更多