Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions criu/files-reg.c
Original file line number Diff line number Diff line change
Expand Up @@ -1412,6 +1412,27 @@ static int check_path_remap(struct fd_link *link, const struct fd_parms *parms,

if (errno == ENOENT) {
link_strip_deleted(link);
/*
* On external tmpfs mounts (e.g. /dev/shm provided by
* the container runtime), link_remap.N is created on
* the source node's in-memory tmpfs but is not included
* in the checkpoint since CRIU does not dump external
* mount contents. The destination node gets a fresh
* empty tmpfs, so rfi_remap() fails with ENOENT on
* restore. Fall back to dump_ghost_remap() which
* embeds the file content in the image.
*
* For CRIU-managed tmpfs mounts (dumped via tar),
* link_remap.N is preserved in the archive, so the
* normal dump_linked_remap() path works correctly.
*/
if (parms->fs_type == TMPFS_MAGIC) {
struct mount_info *mi;

mi = lookup_mnt_id(parms->mnt_id);
if (mi && (mi->external || mnt_is_external_bind(mi)))
return dump_ghost_remap(rpath + 1, ost, lfd, id, nsid);
}
ret = dump_linked_remap(rpath + 1, plen - 1, parms, lfd, id, nsid, &fallback);
if (ret < 0 && fallback) {
/* fallback is true only if following conditions are true:
Expand Down
1 change: 1 addition & 0 deletions test/zdtm/static/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@ TST_DIR = \
mntns_remap \
mntns_open \
mntns_link_remap \
tmpfs_link_remap \
mntns_ghost \
mntns_ghost01 \
mntns_ro_root \
Expand Down
143 changes: 143 additions & 0 deletions test/zdtm/static/tmpfs_link_remap.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/limits.h>

#include "zdtmtst.h"

const char *test_doc = "Check that a tmpfs file with st_nlink>=1 but "

Check warning on line 13 in test/zdtm/static/tmpfs_link_remap.c

View workflow job for this annotation

GitHub Actions / build

"mapped-path ENOENT (POSIX semaphore pattern) "
"is correctly checkpointed and restored";
const char *test_author = "Filipe Augusto Lima de Souza <filipe@cast.ai>";

/*
* Reproduces the state created by Python multiprocessing on Linux (fork
* context): sem_open() creates /dev/shm/sem.NAME (st_nlink=1), mmaps it
* internally, then sem_unlink() removes the name while another hard link
* keeps st_nlink=1.
*
* Without the fix, CRIU dump takes dump_linked_remap() (because st_nlink>=1
* and fstatat ENOENT), creates link_remap.N on the source tmpfs, but never
* stores the content. On restore, link_remap.N does not exist on the
* destination tmpfs -> ENOENT -> "Can't open vma".
*
* With the fix, CRIU detects TMPFS_MAGIC and falls back to dump_ghost_remap()
* which embeds the content in the checkpoint image.
*/

#define FILE_SIZE 4096

Check warning on line 33 in test/zdtm/static/tmpfs_link_remap.c

View workflow job for this annotation

GitHub Actions / build


char *dirname;
TEST_OPTION(dirname, string, "directory name", 1);

int main(int argc, char **argv)
{
int fd;
void *map;
uint32_t crc_before, crc_after;
struct stat st;
char sempath[PATH_MAX];
char anchorpath[PATH_MAX];

test_init(argc, argv);

mkdir(dirname, 0700);
if (mount("none", dirname, "tmpfs", 0, "") < 0) {
pr_perror("mount tmpfs on %s", dirname);
return 1;
}

snprintf(sempath, sizeof(sempath), "%s/sem.zdtm-link-remap-test", dirname);
snprintf(anchorpath, sizeof(anchorpath), "%s/sem.zdtm-link-remap-anchor", dirname);

/* Step 1: create the file on tmpfs. */
fd = open(sempath, O_RDWR | O_CREAT | O_EXCL, 0600);
if (fd < 0) {
pr_perror("open %s", sempath);
goto umount;
}
if (ftruncate(fd, FILE_SIZE) < 0) {
pr_perror("ftruncate");
goto umount;
}

/* Step 2: mmap it MAP_SHARED -- creates the file-backed VMA. */
map = mmap(NULL, FILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map == MAP_FAILED) {
pr_perror("mmap");
goto umount;
}

/* Write known data and compute CRC before C/R. */
crc_before = ~0;
datagen(map, FILE_SIZE, &crc_before);

/*
* Step 3: create a hard link so st_nlink becomes 2.
* This keeps the inode alive after we remove the original name,
* ensuring CRIU sees st_nlink=1 (not 0) and takes dump_linked_remap
* instead of dump_ghost_remap -- which is the bug trigger.
*/
if (link(sempath, anchorpath) < 0) {
pr_perror("link");
goto umount;
}

/*
* Step 4: unlink the mapped name.
* st_nlink drops to 1 (anchor survives).
* /proc/PID/maps now shows the file as "(deleted)".
* fstatat on that path returns ENOENT.
* CRIU without fix: st_nlink=1 + ENOENT -> dump_linked_remap -> fails.
* CRIU with fix: TMPFS_MAGIC detected -> dump_ghost_remap -> works.
*/
if (unlink(sempath) < 0) {
pr_perror("unlink");
goto umount;
}

/* Verify the trigger state: st_nlink=1, mapped path gone. */
if (fstat(fd, &st) < 0) {
pr_perror("fstat");
goto umount;
}
if (st.st_nlink != 1) {
fail("Expected st_nlink=1, got %lu", (unsigned long)st.st_nlink);
goto umount;
}

test_daemon();
test_waitsig();

/* After C/R: verify the mmap content is intact. */
crc_after = ~0;
if (datachk(map, FILE_SIZE, &crc_after)) {
fail("mmap content CRC mismatch after restore");
goto cleanup;
}

/* Verify the fd is still usable. */
if (fstat(fd, &st) < 0) {
pr_perror("fstat after restore");
goto cleanup;
}
if (st.st_size != FILE_SIZE) {
fail("fd size changed: expected %d got %lld",
FILE_SIZE, (long long)st.st_size);
goto cleanup;
}

pass();
cleanup:
munmap(map, FILE_SIZE);
close(fd);
unlink(anchorpath);
umount:
umount2(dirname, MNT_DETACH);
return 0;
}
1 change: 1 addition & 0 deletions test/zdtm/static/tmpfs_link_remap.desc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{'flavor': 'ns uns', 'flags': 'suid', 'opts': '--link-remap'}
Loading