使用带有snprintf()的Write()以原子方式写入文件

时间:2016-11-27 22:46:13

标签: c io printf buffer

我希望能够原子地写入文件,我试图使用write()函数,因为它似乎在大多数linux / unix系统中授予原子写入。

由于我有可变的字符串长度和多个printf,我被告知使用snprintf()并将其作为参数传递给write函数,以便能够正确地执行此操作,这个函数的文档我做了一个测试实现如下:

int file = open("file.txt", O_CREAT | O_WRONLY);
if(file < 0)
    perror("Error:");
char buf[200] = "";
int numbytes = snprintf(buf, sizeof(buf), "Example string %s" stringvariable);
write(file, buf, numbytes);

从我的测试看起来它似乎有效,但我的问题是,如果这是最正确的实现方式,因为我正在创建一个相当大的缓冲区(我100%确定将适合我的所有printfs)之前存储它传递给写作。

1 个答案:

答案 0 :(得分:1)

  1. 不,write()不是原子的,即使在写入一次调用中提供的所有数据时也是如此。

  2. 在所有读者和作者中使用建议记录锁定(fcntl(fd, F_SETLKW, &lock))来实现原子文件更新。

    基于fcntl()的记录锁在Linux和BSD上通过NFS工作;基于flock()的文件锁可能不会,具体取决于系统和内核版本。 (如果在某些Web托管服务上禁用NFS锁定,则锁定将不可靠。)只需使用struct flock初始化.l_whence = SEEK_SET, .l_start = 0, .l_len = 0以引用整个文件。

  3. 使用asprintf()打印到动态分配的缓冲区:

     char *buffer = NULL;
     int   length;
    
     length = asprintf(&buffer, ...);
     if (length == -1) {
         /* Out of memory */
     }
    
     /* ... Have buffer and length ... */
    
     free(buffer);
    
  4. 添加锁定后,请将write()包裹在循环中:

     {
         const char       *p = (const char *)buffer;
         const char *const q = (const char *)buffer + length;
         ssize_t           n;
    
         while (p < q) {
    
             n = write(fd, p, (size_t)(q - p));
             if (n > 0)
                 p += n;
             else
             if (n != -1) {
                 /* Write error / kernel bug! */
             } else
             if (errno != EINTR) {
                 /* Error! Details in errno */
             }
         }
     }
    

    虽然有一些本地文件系统保证write()不会返回短计数,除非你的存储空间不足,不是所有的都做;尤其不是联网的。使用上面的循环可以使您的程序在这些文件系统上工作。在我看来,为可靠和稳健的操作添加的代码并不多。

  5. 在Linux中,您可以对文件执行a write lease以排除暂时打开该文件的任何其他进程。

    基本上,您无法阻止文件打开,但您可以将其延迟最多/proc/sys/fs/lease-break-time秒,通常为45秒。仅当没有其他进程打开文件时才授予租约,如果任何其他进程尝试打开文件,则租约所有者会收到信号。 (如果租赁所有者没有释放租约,例如关闭文件,内核将在租约中断时间结束后自动中断租约。)

    不幸的是,这些仅适用于Linux,仅适用于本地文件,因此用途有限。

  6. 如果读者不保持文件打开,但每次阅读时都打开,阅读和关闭文件,您可以编写完整的替换文件(必须在同一个文件系统上;我建议使用锁定 - 这个子目录),并将其硬链接到旧文件。

    所有读者都会看到旧文件或新文件,但保持文件打开的文件将永远不会看到任何更改。