criu

The Complexity of Re-opening Files during Restore

Re-creating an open file descriptor during restoration is far more complex than simply calling open(path, flags). This article explores the numerous edge cases CRIU must handle to faithfully reconstruct the file state.

1. Basic Opening

At its simplest, a file is defined by its path and access mode:

int fd = open(f->path, f->mode);

However, this is only the beginning of the process.

2. FIFOs and Blocking

A standard open() call on a FIFO (named pipe) can hang indefinitely if there is no corresponding reader or writer on the other end. CRIU avoids this by first opening the FIFO with O_RDWR (to ensure at least one of each is present) and then using dup2 to establish the final descriptor with the correct original flags.

3. Unlinked but Open Files (Ghost Files)

Linux allows files to be deleted while they are still open. These “invisible” files no longer have a path in the filesystem.

Directories cannot be hard-linked. If a directory was unlinked, CRIU must recreate it, open it, and then remove it. For files with multiple hard links that were all deleted, CRIU must ensure they all point back to the same physical inode upon restoration, requiring careful tracking of “temporary” paths and user-space reference counts.

5. Mount Namespaces and Chroot

The same path (e.g., /etc/passwd) might refer to entirely different files depending on the mount namespace or chroot environment of the process.

6. File Ownership and Signals (fown)

Files can have an associated “owner” (a PID or PGID) that receives signals (like SIGIO or SIGPOLL) when I/O events occur.

7. Position and Flags

8. The Final Step: Descriptor Planting

Once a file is successfully opened (at a temporary descriptor number assigned by the kernel), it must be moved to the exact numeric descriptor the application expects (e.g., FD 42). This is achieved via dup2(), but requires coordination when descriptors are shared across a process tree.

See also: How to assign needed file descriptor to a file