mirror of
https://github.com/checkpoint-restore/criu.git
synced 2026-07-17 16:47:50 +00:00
docs: update Re-opening Files documentation
- Explain non-blocking techniques for FIFOs - Detail link-remap and ghost file strategies for unlinked files - Document mount namespace (mnt_id) and open_ns_root usage - Explain fown restoration (F_SETOWN_EX, UID switching, F_SETSIG) - Clarify flag sanitization and O_PATH handling Signed-off-by: Andrei Vagin <avagin@google.com>
This commit is contained in:
parent
3919b1bdb3
commit
9e3cde7b9e
1 changed files with 30 additions and 224 deletions
|
|
@ -1,237 +1,43 @@
|
|||
# How Hard is it to Open a File?
|
||||
# The Complexity of Re-opening Files during Restore
|
||||
|
||||
This article outlines what CRIU must handle when recreating an open file descriptor during restoration.
|
||||
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.
|
||||
|
||||
Suppose we have information about a file we want to open, such as its access mode and path:
|
||||
## 1. Basic Opening
|
||||
|
||||
At its simplest, a file is defined by its path and access mode:
|
||||
```c
|
||||
struct file {
|
||||
char *path;
|
||||
unsigned mode;
|
||||
} *f;
|
||||
int fd = open(f->path, f->mode);
|
||||
```
|
||||
However, this is only the beginning of the process.
|
||||
|
||||
To have this path opened by a process, we might try:
|
||||
## 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.
|
||||
|
||||
```c
|
||||
int fd;
|
||||
fd = open(f->path, f->mode);
|
||||
```
|
||||
## 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.
|
||||
* **link-remap**: If the file still has other hard links elsewhere, CRIU may create a temporary link to it to allow it to be re-opened via a path.
|
||||
* **Ghost Files**: If the link count is zero, CRIU captures the entire content of the file during the dump. During restore, it recreates this file in a temporary location, opens it, and then unlinks it to match the original state.
|
||||
|
||||
However, this is insufficient. Regular files are not the only things opened via paths; FIFOs are another example. A standard `open()` on a FIFO with certain flags might hang indefinitely. To prevent this, the code must be modified:
|
||||
## 4. Directories and Hard Links
|
||||
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.
|
||||
|
||||
```c
|
||||
int fd, tfd = -1;
|
||||
## 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.
|
||||
* **mnt_id**: CRIU records the mount ID for every file during the dump.
|
||||
* **open_ns_root**: During restoration, CRIU uses file descriptors referring to the root of the specific mount namespace to ensure that `openat()` targets the correct physical file, regardless of the restorer's current root.
|
||||
|
||||
if (S_ISFIFO(f->mode))
|
||||
tfd = open(f->path, O_RDWR);
|
||||
## 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.
|
||||
* **F_SETOWN_EX**: CRIU restores this ownership using the extended owner structure.
|
||||
* **UID Switching**: Setting the owner of a file may require specific privileges. CRIU may temporarily switch its effective UIDs during the `fcntl` call to satisfy kernel permission checks if the file owner differs from the restorer.
|
||||
* **F_SETSIG**: The specific signal number to be delivered is also faithfully restored.
|
||||
|
||||
fd = open(f->path, f->mode);
|
||||
## 7. Position and Flags
|
||||
* **Lseek**: The current byte offset (`pos`) is restored using `lseek`.
|
||||
* **Flag Sanitization**: Certain flags (like `O_CREAT`, `O_EXCL`, `O_TRUNC`) only make sense during the initial creation of a file. CRIU strips these before the restore-time `open()` to avoid accidentally creating or truncating existing files.
|
||||
* **O_PATH**: Files opened with `O_PATH` are handled as pure path references; they do not have positions, ownership, or data access.
|
||||
|
||||
if (tfd >= 0)
|
||||
close(tfd);
|
||||
```
|
||||
## 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.
|
||||
|
||||
Using `tfd` to keep the FIFO open for reading and writing ensures the subsequent `open()` succeeds regardless of the flags.
|
||||
|
||||
Even this revised approach has issues. In Linux, a file can be unlinked while it is still open (these [invisible files](invisible-files.md) require careful handling during a dump). If a file is unlinked, the path it once occupied may no longer exist or may point to something else. We must create a temporary name for it and unlink it after opening. Thus, we extend our file information and the opening logic:
|
||||
|
||||
```c
|
||||
struct file {
|
||||
char *path;
|
||||
unsigned mode;
|
||||
char *temp_path;
|
||||
} *f;
|
||||
```
|
||||
|
||||
```c
|
||||
int fd, tfd = -1;
|
||||
|
||||
if (f->temp_path)
|
||||
link(f->temp_path, f->path);
|
||||
|
||||
if (S_ISFIFO(f->mode))
|
||||
tfd = open(f->path, O_RDWR);
|
||||
|
||||
fd = open(f->path, f->mode);
|
||||
|
||||
if (tfd >= 0)
|
||||
close(tfd);
|
||||
|
||||
if (f->temp_path)
|
||||
unlink(f->path);
|
||||
```
|
||||
|
||||
Directories can also be open while removed. Since `link()` and `unlink()` do not work for directories, we must adjust the logic:
|
||||
|
||||
```c
|
||||
int fd, tfd = -1;
|
||||
|
||||
if (f->temp_path) {
|
||||
if (S_ISDIR(f->mode))
|
||||
mkdir(f->path);
|
||||
else
|
||||
link(f->temp_path, f->path);
|
||||
}
|
||||
|
||||
if (S_ISFIFO(f->mode))
|
||||
tfd = open(f->path, O_RDWR);
|
||||
|
||||
fd = open(f->path, f->mode);
|
||||
|
||||
if (tfd >= 0)
|
||||
close(tfd);
|
||||
|
||||
if (f->temp_path) {
|
||||
if (S_ISDIR(f->mode))
|
||||
rmdir(f->path);
|
||||
else
|
||||
unlink(f->path);
|
||||
}
|
||||
```
|
||||
|
||||
Hard links introduce further complexity. If a file has multiple hard links that were all opened and then removed, we cannot simply delete the `temp_path` after opening it, as other `struct file` instances might still need it.
|
||||
|
||||
```c
|
||||
struct temp_file {
|
||||
char *path;
|
||||
unsigned users;
|
||||
};
|
||||
|
||||
struct file {
|
||||
char *path;
|
||||
unsigned mode;
|
||||
struct temp_file *temp;
|
||||
} *f;
|
||||
```
|
||||
|
||||
The opening logic then becomes:
|
||||
|
||||
```c
|
||||
int fd, tfd = -1;
|
||||
|
||||
if (f->temp) {
|
||||
if (S_ISDIR(f->mode))
|
||||
mkdir(f->path);
|
||||
else
|
||||
link(f->temp->path, f->path);
|
||||
}
|
||||
|
||||
if (S_ISFIFO(f->mode))
|
||||
tfd = open(f->path, O_RDWR);
|
||||
|
||||
fd = open(f->path, f->mode);
|
||||
|
||||
if (tfd >= 0)
|
||||
close(tfd);
|
||||
|
||||
if (f->temp) {
|
||||
if (--f->temp->users == 0) {
|
||||
if (S_ISDIR(f->mode))
|
||||
rmdir(f->path);
|
||||
else
|
||||
unlink(f->temp->path);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that making `temp_file` data sharable across processes and ensuring `f->temp->users` is updated safely in an SMP environment requires additional implementation details. We also do not currently handle the rare case where a file/directory is removed and replaced by another object of the same name.
|
||||
|
||||
Mount namespaces add another layer of complexity. Two files with the same path may reside in different mount namespaces. We must track the mount point ID:
|
||||
|
||||
```c
|
||||
struct file {
|
||||
char *path;
|
||||
unsigned mode;
|
||||
struct temp_file *temp;
|
||||
unsigned mnt_id;
|
||||
} *f;
|
||||
```
|
||||
|
||||
The opening logic then uses `open_ns_root()` to access the correct mount namespace:
|
||||
|
||||
```c
|
||||
int fd, tfd = -1, ns_fd;
|
||||
char *rel_path = f->path + 1;
|
||||
|
||||
ns_fd = open_ns_root(f->mnt_id);
|
||||
|
||||
if (f->temp) {
|
||||
if (S_ISDIR(f->mode))
|
||||
mkdirat(ns_fd, rel_path);
|
||||
else
|
||||
linkat(ns_fd, f->temp->path, ns_fd, rel_path);
|
||||
}
|
||||
|
||||
if (S_ISFIFO(f->mode))
|
||||
tfd = openat(ns_fd, rel_path, O_RDWR);
|
||||
|
||||
fd = openat(ns_fd, rel_path, f->mode);
|
||||
|
||||
if (tfd >= 0)
|
||||
close(tfd);
|
||||
|
||||
if (f->temp) {
|
||||
if (--f->temp->users == 0) {
|
||||
if (S_ISDIR(f->mode))
|
||||
unlinkat(ns_fd, rel_path, AT_REMOVEDIR);
|
||||
else
|
||||
unlinkat(ns_fd, f->temp->path, 0);
|
||||
}
|
||||
}
|
||||
|
||||
close(ns_fd);
|
||||
```
|
||||
|
||||
Finally, open files have attributes like current position and ownership (`fown`) that must be restored. Flags like `O_TRUNC` and `O_CREAT` must also be sanitized.
|
||||
|
||||
```c
|
||||
struct file {
|
||||
char *path;
|
||||
unsigned mode;
|
||||
struct temp_file *temp;
|
||||
unsigned mnt_id;
|
||||
unsigned long pos;
|
||||
struct fown fown;
|
||||
} *f;
|
||||
```
|
||||
|
||||
```c
|
||||
int fd, tfd = -1, ns_fd, open_flags;
|
||||
char *rel_path = f->path + 1;
|
||||
|
||||
ns_fd = open_ns_root(f->mnt_id);
|
||||
|
||||
if (f->temp) {
|
||||
if (S_ISDIR(f->mode))
|
||||
mkdirat(ns_fd, rel_path);
|
||||
else
|
||||
linkat(ns_fd, f->temp->path, ns_fd, rel_path);
|
||||
}
|
||||
|
||||
if (S_ISFIFO(f->mode))
|
||||
tfd = openat(ns_fd, rel_path, O_RDWR);
|
||||
|
||||
open_flags = sanitize_open_mode(f->mode);
|
||||
fd = openat(ns_fd, rel_path, open_flags);
|
||||
|
||||
if (tfd >= 0)
|
||||
close(tfd);
|
||||
|
||||
if (f->temp) {
|
||||
if (--f->temp->users == 0) {
|
||||
if (S_ISDIR(f->mode))
|
||||
unlinkat(ns_fd, rel_path, AT_REMOVEDIR);
|
||||
else
|
||||
unlinkat(ns_fd, f->temp->path, 0);
|
||||
}
|
||||
}
|
||||
|
||||
close(ns_fd);
|
||||
|
||||
fcntl(fd, F_SETSIG, f->fown.sig);
|
||||
fcntl(fd, F_SETOWN, &f->fown.owner);
|
||||
lseek(fd, f->pos, SEEK_SET);
|
||||
```
|
||||
|
||||
This covers the basics of opening the file. Once opened, it must be assigned to the correct file descriptor number in the process's FDT. While `dup2(fd, desired_fd)` seems simple, the reality is more involved, as explained in [How to assign a needed file descriptor to a file](how-to-assign-needed-file-descriptor-to-a-file.md).
|
||||
*See also: [How to assign needed file descriptor to a file](how-to-assign-needed-file-descriptor-to-a-file.md)*
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue