criu/test/zdtm/lib/test.c
Haorong Lu bb29067de9 zdtm: add riscv64 support
Signed-off-by: Haorong Lu <ancientmodern4@gmail.com>
2025-03-21 12:40:31 -07:00

416 lines
7.8 KiB
C

#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sched.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/prctl.h>
#include <grp.h>
#include "zdtmtst.h"
#include "lock.h"
#include "ns.h"
futex_t sig_received;
/* clang-format off */
static struct {
futex_t stage;
} *test_shared_state;
/* clang-format on */
enum {
TEST_INIT_STAGE = 0,
TEST_RUNNING_STAGE,
TEST_FINI_STAGE,
TEST_FAIL_STAGE,
};
static int parent;
extern int criu_status_in, criu_status_in_peer, criu_status_out;
static void sig_hand(int signo)
{
if (parent)
futex_set_and_wake(&test_shared_state->stage, TEST_FAIL_STAGE);
futex_set_and_wake(&sig_received, signo);
if (criu_status_in >= 0)
close(criu_status_in);
}
static char *outfile;
TEST_OPTION(outfile, string, "output file", 1);
char *pidfile;
TEST_OPTION(pidfile, string, "file to store pid", 1);
static pid_t master_pid = 0;
int test_fork_id(int id)
{
return fork();
}
static int cwd = -1;
static void test_fini(void)
{
char path[PATH_MAX];
if (getpid() != master_pid)
return;
snprintf(path, sizeof(path), "%s%s", outfile, INPROGRESS);
renameat(cwd, path, cwd, outfile);
unlinkat(cwd, pidfile, 0);
}
static void setup_outfile(void)
{
if (!access(outfile, F_OK) || errno != ENOENT) {
fprintf(stderr, "Output file %s appears to exist, aborting\n", outfile);
exit(1);
}
cwd = open(".", O_RDONLY);
if (cwd < 0) {
fprintf(stderr, "Unable to open\n");
exit(1);
}
if (atexit(test_fini)) {
fprintf(stderr, "Can't register exit function\n");
exit(1);
}
if (test_log_init(outfile, INPROGRESS))
exit(1);
}
static void redir_stdfds(void)
{
int nullfd;
nullfd = open("/dev/null", O_RDWR);
if (nullfd < 0) {
pr_perror("Can't open /dev/null");
exit(1);
}
dup2(nullfd, STDIN_FILENO);
if (nullfd > 2)
close(nullfd);
}
void test_ext_init(int argc, char **argv)
{
parseargs(argc, argv);
if (test_log_init(outfile, ".external"))
exit(1);
}
#define PIPE_RD 0
#define PIPE_WR 1
int init_notify(void)
{
char *val;
int ret;
int p[2];
val = getenv("ZDTM_NOTIFY_FDIN");
if (!val)
return 0;
criu_status_in = atoi(val);
val = getenv("ZDTM_NOTIFY_FDOUT");
if (!val)
return -1;
criu_status_out = atoi(val);
if (pipe(p)) {
fprintf(stderr, "Unable to create a pipe: %m\n");
return -1;
}
criu_status_in_peer = p[PIPE_WR];
ret = dup2(p[PIPE_RD], criu_status_in);
if (ret < 0) {
fprintf(stderr, "dup2() failed: %m\n");
close(p[PIPE_RD]);
close(p[PIPE_WR]);
return -1;
}
close(p[PIPE_RD]);
if (pipe(p)) {
fprintf(stderr, "Unable to create a pipe: %m\n");
goto err_pipe_in;
}
close(p[PIPE_RD]);
ret = dup2(p[PIPE_WR], criu_status_out);
if (ret < 0) {
fprintf(stderr, "dup2() failed: %m\n");
goto err_pipe_out;
}
close(p[PIPE_WR]);
return 0;
err_pipe_out:
close(p[PIPE_RD]);
close(p[PIPE_WR]);
err_pipe_in:
close(criu_status_in);
close(criu_status_in_peer);
return -1;
}
int write_pidfile(int pid)
{
int fd = -1;
char tmp[] = ".zdtm.pidfile.XXXXXX";
fd = mkstemp(tmp);
if (fd == -1) {
fprintf(stderr, "Can't create the file %s: %m\n", tmp);
return -1;
}
if (fchmod(fd, 0666) < 0) {
fprintf(stderr, "Can't fchmod %s: %m\n", tmp);
goto err_c;
}
if (dprintf(fd, "%d", pid) == -1) {
fprintf(stderr, "Can't write in the file %s: %m\n", tmp);
goto err_c;
}
close(fd);
if (rename(tmp, pidfile) < 0) {
fprintf(stderr, "Can't rename %s to %s: %m\n", tmp, pidfile);
goto err_u;
}
return 0;
err_c:
close(fd);
err_u:
unlink(tmp);
return -1;
}
void test_init(int argc, char **argv)
{
pid_t pid;
char *val;
struct sigaction sa = {
.sa_handler = sig_hand,
.sa_flags = SA_RESTART,
};
sigemptyset(&sa.sa_mask);
parseargs(argc, argv);
val = getenv("ZDTM_NEWNS");
if (val) {
if (!strcmp(val, "1")) {
ns_create(argc, argv);
exit(1);
}
if (!strcmp(val, "2")) {
test_log_init(outfile, "ns");
redir_stdfds();
ns_init(argc, argv);
}
} else if (init_notify()) {
fprintf(stderr, "Can't init pre-dump notification: %m");
exit(1);
}
val = getenv("ZDTM_ROOTLESS");
if (!val) {
val = getenv("ZDTM_GROUPS");
if (val) {
char *tok = NULL;
unsigned int size = 0, groups[NGROUPS_MAX];
tok = strtok(val, " ");
while (tok) {
size++;
groups[size - 1] = atoi(tok);
tok = strtok(NULL, " ");
}
if (setgroups(size, groups)) {
fprintf(stderr, "Can't set groups: %m");
exit(1);
}
}
val = getenv("ZDTM_GID");
if (val && (setgid(atoi(val)) == -1)) {
fprintf(stderr, "Can't set gid: %m");
exit(1);
}
val = getenv("ZDTM_UID");
if (val && (setuid(atoi(val)) == -1)) {
fprintf(stderr, "Can't set gid: %m");
exit(1);
}
}
if (prctl(PR_SET_DUMPABLE, 1)) {
fprintf(stderr, "Can't set the dumpable flag");
exit(1);
}
if (sigaction(SIGTERM, &sa, NULL)) {
fprintf(stderr, "Can't set SIGTERM handler: %m\n");
exit(1);
}
if (sigaction(SIGCHLD, &sa, NULL)) {
fprintf(stderr, "Can't set SIGCHLD handler: %m\n");
exit(1);
}
setup_outfile();
redir_stdfds();
test_shared_state = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, 0, 0);
if (test_shared_state == MAP_FAILED) {
pr_perror("Unable to map a shared memory");
exit(1);
}
futex_init(&test_shared_state->stage);
futex_set(&test_shared_state->stage, TEST_INIT_STAGE);
pid = fork();
if (pid < 0) {
pr_perror("Daemonizing failed");
exit(1);
}
parent = 1;
if (pid) { /* parent will exit when the child is ready */
futex_wait_while(&test_shared_state->stage, TEST_INIT_STAGE);
if (futex_get(&test_shared_state->stage) == TEST_FAIL_STAGE) {
int ret;
if (waitpid(pid, &ret, 0) != pid) {
pr_perror("Unable to wait %d", pid);
exit(1);
}
if (WIFEXITED(ret)) {
pr_err("Test exited unexpectedly with code %d\n", WEXITSTATUS(ret));
exit(1);
}
if (WIFSIGNALED(ret)) {
pr_err("Test exited on unexpected signal %d\n", WTERMSIG(ret));
exit(1);
}
}
if (write_pidfile(pid))
exit(1);
_exit(0);
}
parent = 0;
if (setsid() < 0) {
pr_perror("Can't become session group leader");
exit(1);
}
/* record the test pid to remember the ownership of the pidfile */
master_pid = getpid();
sa.sa_handler = SIG_DFL;
if (sigaction(SIGCHLD, &sa, NULL)) {
pr_perror("Can't reset SIGCHLD handler");
exit(1);
}
srand48(time(NULL)); /* just in case we need it */
}
void test_daemon(void)
{
futex_set_and_wake(&test_shared_state->stage, TEST_RUNNING_STAGE);
}
int test_go(void)
{
return !futex_get(&sig_received);
}
void test_waitsig(void)
{
futex_wait_while(&sig_received, 0);
}
int test_wait_pre_dump(void)
{
int ret;
if (criu_status_in < 0) {
pr_err("Fd criu_status_in is not initialized\n");
return -1;
}
if (read(criu_status_in, &ret, sizeof(ret)) != sizeof(ret)) {
if (errno != EBADF || !futex_get(&sig_received))
pr_perror("Can't wait pre-dump");
return -1;
}
pr_err("pre-dump\n");
return 0;
}
int test_wait_pre_dump_ack(void)
{
int ret = 0;
if (criu_status_out < 0) {
pr_err("Fd criu_status_out is not initialized\n");
return -1;
}
pr_err("pre-dump-ack\n");
if (write(criu_status_out, &ret, sizeof(ret)) != sizeof(ret)) {
pr_perror("Can't reply to pre-dump notify");
return -1;
}
return 0;
}
pid_t sys_clone_unified(unsigned long flags, void *child_stack, void *parent_tid, void *child_tid, unsigned long newtls)
{
#ifdef __x86_64__
return (pid_t)syscall(__NR_clone, flags, child_stack, parent_tid, child_tid, newtls);
#elif (__i386__ || __arm__ || __aarch64__ || __powerpc64__ || __mips__ || __loongarch64 || __riscv)
return (pid_t)syscall(__NR_clone, flags, child_stack, parent_tid, newtls, child_tid);
#elif __s390x__
return (pid_t)syscall(__NR_clone, child_stack, flags, parent_tid, child_tid, newtls);
#else
#error "Unsupported architecture"
#endif
}