zdtm: add sk-unix-restore-fs-share test

Add a ZDTM test case where CRIU uses a helper process to restore
a non-empty process group with a terminated leader and a Unix
domain socket. This reproduces a corner case in which mount
namespace switching can fail during restore:

https://github.com/checkpoint-restore/criu/issues/2687

Signed-off-by: Qiao Ma <mqaio@linux.alibaba.com>
Signed-off-by: Radostin Stoyanov <rstoyanov@fedoraproject.org>
This commit is contained in:
Radostin Stoyanov 2025-10-03 17:02:25 +01:00 committed by Andrei Vagin
parent 790b3cf425
commit 520266d895
3 changed files with 198 additions and 0 deletions

View file

@ -382,6 +382,7 @@ TST_FILE = \
sk-unix-listen02 \
sk-unix-listen03 \
sk-unix-listen04 \
sk-unix-restore-fs-share \
mnt_ext_file_bind_auto \
TST_DIR = \

View file

@ -0,0 +1,196 @@
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "zdtmtst.h"
const char *test_doc = "Test non-empty process group with terminated parent and unix socket";
const char *test_author = "Qiao Ma <mqaio@linux.alibaba.com>";
char *filename;
TEST_OPTION(filename, string, "socket file name", 1);
static int create_and_connect(void)
{
struct sockaddr_un addr;
int client_fd;
client_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (client_fd == -1) {
pr_perror("socket");
return -1;
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
if (snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", filename) >= (int)sizeof(addr.sun_path)) {
pr_err("Socket path too long\n");
close(client_fd);
return -1;
}
if (connect(client_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
pr_perror("connect");
close(client_fd);
return -1;
}
return 0;
}
static int child(int ready_fd)
{
int listen_fd;
struct sockaddr_un addr;
int ret = EXIT_FAILURE;
listen_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (listen_fd == -1) {
pr_perror("socket");
return EXIT_FAILURE;
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
if (strlen(filename) >= sizeof(addr.sun_path)) {
pr_err("Socket path too long\n");
goto cleanup;
}
strncpy(addr.sun_path, filename, sizeof(addr.sun_path));
unlink(filename); /* Ignore error if file doesn't exist */
if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
pr_perror("bind");
goto cleanup;
}
if (listen(listen_fd, 5) == -1) {
pr_perror("listen");
goto cleanup;
}
if (create_and_connect() != 0) {
pr_err("Failed to create and connect\n");
goto cleanup;
}
/* Signal parent that socket is ready */
if (write(ready_fd, "1", 1) != 1) {
pr_perror("write ready_fd");
goto cleanup;
}
/* Wait indefinitely */
pause();
ret = EXIT_SUCCESS;
cleanup:
if (listen_fd != -1)
close(listen_fd);
unlink(filename);
return ret;
}
static int zombie_leader(int *cpid)
{
char buf;
pid_t pid;
int pipefd[2];
if (pipe(pipefd) == -1) {
pr_perror("pipe");
return EXIT_FAILURE;
}
if (setpgid(0, 0) == -1) {
pr_perror("setpgid");
return EXIT_FAILURE;
}
pid = fork();
if (pid < 0) {
pr_perror("Failed to fork child");
return EXIT_FAILURE;
}
if (pid == 0) {
/* Close read end */
close(pipefd[0]);
exit(child(pipefd[1]));
}
/* Close write end in parent */
close(pipefd[1]);
/* Wait for child to set up socket */
if (read(pipefd[0], &buf, 1) != 1) {
pr_err("Failed to receive readiness signal from child\n");
close(pipefd[0]);
return EXIT_FAILURE;
}
close(pipefd[0]);
*cpid = pid;
return EXIT_SUCCESS;
}
int main(int argc, char **argv)
{
int ret = EXIT_FAILURE, status;
pid_t pid;
int *cpid;
test_init(argc, argv);
cpid = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
if (cpid == MAP_FAILED) {
pr_perror("mmap");
return EXIT_FAILURE;
}
*cpid = 0;
pid = fork();
if (pid < 0) {
pr_perror("Failed to fork zombie");
goto out;
}
if (pid == 0)
exit(zombie_leader(cpid));
if (waitpid(pid, &status, 0) < 0) {
pr_perror("Failed to waitpid zombie");
goto out;
}
if (!WIFEXITED(status) || WEXITSTATUS(status) != EXIT_SUCCESS) {
pr_err("Unexpected exit code: %d\n", WEXITSTATUS(status));
goto out;
}
if (!*cpid) {
pr_err("Don't know grandchild's pid\n");
goto out;
}
test_daemon();
test_waitsig();
ret = EXIT_SUCCESS;
pass();
out:
/* Clean up */
if (*cpid)
kill(*cpid, SIGKILL);
munmap(cpid, sizeof(int));
return ret;
}

View file

@ -0,0 +1 @@
{'flavor': 'ns uns'}