1

I'm working through the book the linux programming interface by Michael Kerrisk.

The following source code is provided as a solution to Exercise 5-3. The comments describe the purpose of the exercice and the program and its usage.

/*************************************************************************\
*                  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);
}

After running the commands atomic_append f1 1000000 & atomic_append f1 1000000 and atomic_append f2 1000000 x & atomic_append f2 1000000 x, the author writes that ls -l f1 f2 should be similar to

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

That is, without using lseek(2), all the bytes are written without any data loss. No data is lost because write(2) is atomic, and since the O_APPEND flag is used when open(2)ing the files, both processes do not overwrite each other's output.

However, after running this exact source code on my machine running macOS High Sierra 10.13.1, both commands lead to some bytes being overwritten, suggesting that write(2) is not atomic. On my machine, ls -l f1 f2 outputs

-rw-------  1 user  staff  1983995 Jun  7 21:38 f1
-rw-------  1 user  staff  1964984 Jun  7 21:37 f2

which leaves me extremely confused. Why is a write(2) on a file open(2)ed with the O_APPEND flag seemingly not atomic?

I have carefully executed the above commands on clean files using the above program. The results were always reproduced.

I can provide more context concerning the exercise or my setup if needed.


This was marked as a duplicate of this question. The discussion there provides some insight, namely that:

  • Certain filesystems (in my case OSX's filesystem) do not guarantee atomicity. This is the most plausible answer. But, how exactly does OSX fail in the context of the above program ?
Theo
  • 1,190
  • 1
  • 11
  • 16
  • What filesystem are you using? – rici Jun 08 '18 at 02:18
  • @rici I use the standard OSX filesystem. – Theo Jun 08 '18 at 02:19
  • @theo: high sierra introduced a new filesystem (APFS) but according to a Macworld article, it only works with SSD; hard drives continue to use HFS+. I interpret that to mean that there are two standard filesystems and I don't know anything about your hardware. – rici Jun 08 '18 at 02:26
  • @paddy Reading that question was helpful, but I am not sure that it's a duplicate. I added a section explaining this. Overall, I was not able to extract a complete answer from that thread, though there are interesting leads. I'd like to add that I am a beginner with UNIX systems programming, so I may be mistaken in my interpretation of the answers. – Theo Jun 08 '18 at 02:39
  • 2
    The answer is almost certainly what you said, that APFS isn't posix-compliant in this regard. I'm trying to reproduce it here, but I can't compile your code without downloading the TLPI library. – Barmar Jun 08 '18 at 02:45
  • I have to admit that I'm surprised, though. It seems like this should be handled in a higher-level layer of the kernel code, independently of the filesystem driver. – Barmar Jun 08 '18 at 02:46
  • @Barmar Thanks for the input. The code (including the library) is available at http://man7.org/tlpi/code/. The program is found under `fileio/atomic_append.c`. – Theo Jun 08 '18 at 02:46
  • I know it's available, I just don't feel like doing that much work just to compile one test program. – Barmar Jun 08 '18 at 02:47
  • @bramar I agree that it's a duplicate. Thank for pointing this out. – Theo Jun 08 '18 at 02:58
  • 1
    Open Group says : *If the `O_APPEND` flag of the file status flags is set, the file offset shall be set to the end of the file prior to each write and no intervening file modification operation shall occur between changing the file offset and the write operation.* – Jean-Baptiste Yunès Jun 08 '18 at 08:36
  • 1
    That is not a duplicate (at least to the one suggested), here probem is atomicity of APPEND mode, not write perse. More, what OP observed is not reproductible for me. @Theo are you sure of your results? – Jean-Baptiste Yunès Jun 08 '18 at 08:44
  • @Jean-BaptisteYunès The main point I got from the other thread is that macOS isn't posix-compliant, and doesn't guarantee atomic writes. But this does not explain why the poster of that question solves their issue by adding the `O_APPEND` flag. – Theo Jun 08 '18 at 13:03
  • I recompiled and reproduced this at least 5 times on clean files. – Theo Jun 08 '18 at 13:04
  • 1
    Again, that question is not about atomic writes, it is about atomicity of seek/write by a call to write with append flag; and the manual is clear about it : *no intervening file modification operation shall occur*. If not append flag would be of no use. – Jean-Baptiste Yunès Jun 08 '18 at 13:18

0 Answers0