2

A program Foo periodically updates a file and calls my C program Bar to process the file.

The issue is that the Foo might update the file, call Bar to process it, and while Bar reads the file, Foo might update the file again.

Is it possible for Bar to read the file in inconsistent state, e.g. read first half of the file as written by first Foo and the other half as written by the second Foo? If so, how would I prevent that, assuming I can modify only Bar's code?

BananaBlender
  • 23
  • 1
  • 4
  • Yes, you could get some mixed data from two versions of the file, though the chances of that happening depend on the size of the file and how Foo writes the file and how Bar reads the file. One possibility is to have Bar use `mmap()` with a private mapping to read the file; that should give you a consistent snapshot of the file (probably). You'd have to remap the memory on each cycle, though. – Jonathan Leffler Aug 10 '16 at 02:41

3 Answers3

3

Typically, Foo should not simply rewrite the contents of the file again and again, but create a new temporary file, and replace the old file with the temporary file when it is done (using link()). In this case, simply opening the file (at any point in time) will give the reader a consistent snapshot of the contents, because of how typical POSIX filesystems work. (After opening the file, the file descriptor will refer to the same inode/contents, even if the file gets deleted or replaced; the disk space will be released only after the last open file descriptor of a deleted/replaced file is closed.)

If Foo does rewrite the same file (without a temporary file) over and over, the recommended solution would be for both Foo and Bar to use fcntl()-based advisory locking. (However, using a temporary file and renaming/linking it over the actual file when complete, would be even better.)

(While flock()-based locking might seem easier, it is actually a bit of a guessing game whether it works on NFS mounts or not. fcntl() works, unless the NFS server is configured not to support locking. Which is a bit of an issue on some commercial web hosts, actually.)

If you cannot modify the behaviour of Foo, and it does not use advisory locking, there are still some options in Linux.

If Foo closes the file -- i.e., Bar is the only one to open the file --, then taking an exclusive file lease (using fcntl(descriptor, F_SETLEASE, F_WRLCK) is a workable solution. You can only get an exclusive file lease if descriptor is the only open descriptor on the file, and the owner user of the file is the same as the process UID (or the process has the CAP_LEASE capability). If any other process tries to open or truncate the file, the lease owner gets signaled (SIGIO by default), and has up to /proc/sys/fs/lease-break-time seconds to downgrade or release the lease. The opener is blocked for the duration, which allows Bar to either cancel the processing, or copy the file for later processing.

The other option for Bar is rather violent. It can monitor the file say once per second, and when the file is old enough -- say, a few seconds --, pause Foo by sending it a SIGSTOP signal, checking /proc/FOOPID/stat until it gets stopped, and rechecking the file statistics to verify it's still old, until making a temporary copy of it (either in memory, or on disk) for processing. After the file is read/copied, Bar can let Foo continue by sending it a SIGCONT signal.

Some filesystems may support file snapshots, but in my opinion, one of the above are much saner than relying on nonstandard filesystem support to function correctly. If Foo cannot be modified to co-operate, it is time to refactor it out of the picture. You do not want to be a hostage for a black box out of your control, so the sooner you replace it with something more user/administrator-friendly, the better you'll be in the long term.

Nominal Animal
  • 38,216
  • 5
  • 59
  • 86
0

This is difficult to do robustly without Foo's cooperation. Unixes have two main kinds of file locking:

Ideally, you use either of these in cooperative mode (advisory locking), where all participants attempt to acquire the lock and only one will get it at a time.

Without the other program's cooperation, your only recourse, as far as I know is mandatory locking, which you can have with fcntl if you allow it on the filesystem, but the manpage mentions that the Linux implementation is unreliable.

Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
  • Additionally, `fcntl()` locks work fine on NFS mounts (unless the server is configured to not support locking), whereas `flock()` locks might or might not work, depending on kernel (if Linux) and OS (like BSDs). On the other hand, in Linux, even scripts can use `flock` locks via the [`flock`](http://man7.org/linux/man-pages/man1/flock.1.html) utility (provided by util-linux, should be installed in all Linux distros); for example, if you want to flock a file while editing it in nano/vi/vim/emacs/gedit/other program, even if that program does not take an advisory lock. Which is why ... – Nominal Animal Aug 10 '16 at 17:14
  • ... it is imperative for the data producer (Foo) to either use a temporary file (in which case readers get a consistent snapshot even without advisory locking), or advisory locking. My own answer basically parallels yours, except for my unavoidable verbosity (which many do not like). :) – Nominal Animal Aug 10 '16 at 17:16
0

In all UN*X systems, what is warranted to happen atomically is the write(2) or read(2) system calls. The kernel even locks the file inode in memory, so while you are read(2)ing or write(2)ing it, it would not change.

For more spatial atomicity, you have to lock the whole file. You can use the file locking tools available to lock different regions of a file. Some are advisory (you can force an skip over them) and others are mandatory (you are blocked until the other side unblocks the file region)

See fcntl(2) and the options F_GETLK, F_SETLK and F_SETLKW to get lock info, set lock for reading or writing, respectively.

Luis Colorado
  • 10,974
  • 1
  • 16
  • 31