用`O_APPEND`标志打开的文件`write(2)`的原子性

时间:2018-06-08 01:52:05

标签: c macos unix filesystems system-calls

我正在阅读这本书the linux programming interface by Michael Kerrisk

提供以下源代码作为练习5-3的解决方案。评论描述了行使的目的和程序及其用法。

/*************************************************************************\
*                  Copyright (C) Michael Kerrisk, 2017.                   *
*                                                                         *
* This program is free software. You may use, modify, and redistribute it *
* under the terms of the GNU General Public License as published by the   *
* Free Software Foundation, either version 3 or (at your option) any      *
* later version. This program is distributed without any warranty.  See   *
* the file COPYING.gpl-v3 for details.                                    *

/* Solution for Exercise 5-3 */

/* atomic_append.c

   Demonstrate the difference between using nonatomic lseek()+write()
   and O_APPEND when writing to a file.

   Usage: file num-bytes [x]

   The program write 'num-bytes' bytes to 'file' a byte at a time. If
   no additional command-line argument is supplied, the program opens the
   file with the O_APPEND flag. If a command-line argument is supplied, the
   O_APPEND is omitted when calling open(), and the program calls lseek()
   to seek to the end of the file before calling write(). This latter
   technique is vulnerable to a race condition, where data is lost because
   the lseek() + write() steps are not atomic. This can be demonstrated
   by looking at the size of the files produced by these two commands:

        atomic_append f1 1000000 & atomic_append f1 1000000

        atomic_append f2 1000000 x & atomic_append f2 1000000 x
*/

#include <sys/stat.h>
#include <fcntl.h>
#include "lib/tlpi.h"

int
main(int argc, char *argv[])
{
    int numBytes, j, flags, fd;
    Boolean useLseek;

    if (argc < 3 || strcmp(argv[1], "--help") == 0)
        usageErr("%s file num-bytes [x]\n"
                 "        'x' means use lseek() instead of O_APPEND\n",
                 argv[0]);

    useLseek = argc > 3;
    flags = useLseek ? 0 : O_APPEND;
    numBytes = getInt(argv[2], 0, "num-bytes");

    fd = open(argv[1], O_RDWR | O_CREAT | flags, S_IRUSR | S_IWUSR);
    if (fd == -1)
        errExit("open");

    for (j = 0; j < numBytes; j++) {
        if (useLseek)
            if (lseek(fd, 0, SEEK_END) == -1)
                errExit("lseek");
        if (write(fd, "x", 1) != 1)
            fatal("write() failed");
    }

    printf("%ld done\n", (long) getpid());
    exit(EXIT_SUCCESS);
}

在运行命令atomic_append f1 1000000 & atomic_append f1 1000000atomic_append f2 1000000 x & atomic_append f2 1000000 x之后,作者写道ls -l f1 f2应该类似于

-rw------- 1 mtk users 2000000 Jan 9 11:14 f1
-rw------- 1 mtk users 1999962 Jan 9 11:14 f2 

也就是说,在不使用lseek(2)的情况下,所有字节都被写入而没有任何数据丢失。没有数据丢失,因为write(2)是原子的,并且由于在O_APPEND文件时使用open(2)标志,因此两个进程都不会覆盖彼此的输出。

然而,在运行macOS High Sierra 10.13.1的机器上运行这个确切的源代码后,两个命令都会导致某些字节被覆盖,这表明write(2)不是原子的。在我的机器上,ls -l f1 f2输出

-rw-------  1 user  staff  1983995 Jun  7 21:38 f1
-rw-------  1 user  staff  1964984 Jun  7 21:37 f2
这让我非常困惑。为什么文件write(2)上的open(2)加上O_APPEND标志看似不是原子的?

我已经使用上面的程序在干净的文件上仔细执行了上述命令。结果总是重现。

如果需要,我可以提供更多关于练习或我的设置的背景信息。

这被标记为this question的副本。那里的讨论提供了一些见解,即:

  • 某些文件系统(在我的情况下是OSX的文件系统)不保证原子性。这是最合理的答案。但是,OSX在上述程序中是如何失败的呢?

0 个答案:

没有答案