mirror of
https://github.com/checkpoint-restore/criu.git
synced 2026-07-18 00:58:31 +00:00
contrib: add reference client for criu-service
Add a reference C client that uses libcriu to checkpoint and restore processes. Set LOCAL=0 to build against the system-installed libcriu instead of the source tree. Status messages use fprintf(stderr) so they are not delayed by stdout buffering when running without a terminal (e.g. in CI). Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Radostin Stoyanov <rstoyanov@fedoraproject.org>
This commit is contained in:
parent
14cb3c2016
commit
57a92dbb32
4 changed files with 572 additions and 0 deletions
6
contrib/criu-service-client/.gitignore
vendored
Normal file
6
contrib/criu-service-client/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
criu-service-client
|
||||
*.o
|
||||
.install/
|
||||
criu-service.socket
|
||||
criu-service.log
|
||||
criu-service.pid
|
||||
78
contrib/criu-service-client/Makefile
Normal file
78
contrib/criu-service-client/Makefile
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
CC ?= gcc
|
||||
|
||||
CFLAGS ?= -Wall -Wextra -O2
|
||||
LDFLAGS ?=
|
||||
LDLIBS := -lcriu
|
||||
|
||||
TARGET := criu-service-client
|
||||
SRC := criu-service-client.c
|
||||
OBJ := $(SRC:.c=.o)
|
||||
|
||||
# By default, build libcriu from source into a local staging directory.
|
||||
# Set LOCAL=0 to use the system-installed libcriu and criu instead:
|
||||
# make LOCAL=0
|
||||
LOCAL ?= 1
|
||||
|
||||
INSTALL_DIR := $(CURDIR)/.install
|
||||
ifeq ($(LOCAL),1)
|
||||
CRIU_INCLUDE := $(INSTALL_DIR)/include
|
||||
CRIU_LIB := $(INSTALL_DIR)/lib
|
||||
CFLAGS += -I$(CRIU_INCLUDE)
|
||||
LDFLAGS += -L$(CRIU_LIB)
|
||||
endif
|
||||
|
||||
all: $(TARGET)
|
||||
.PHONY: all
|
||||
|
||||
$(TARGET): $(OBJ)
|
||||
$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
|
||||
|
||||
ifeq ($(LOCAL),1)
|
||||
%.o: %.c | $(CRIU_INCLUDE)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(CRIU_LIB)/libcriu.so:
|
||||
$(MAKE) -C ../../ install-lib \
|
||||
DESTDIR=$(INSTALL_DIR) PREFIX=/ LIBDIR=/lib \
|
||||
SKIP_PIP_INSTALL=1
|
||||
|
||||
$(CRIU_INCLUDE): $(CRIU_LIB)/libcriu.so
|
||||
@:
|
||||
else
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
endif
|
||||
|
||||
ifeq ($(LOCAL),1)
|
||||
CRIU_BIN := $(CURDIR)/../../criu/criu
|
||||
else
|
||||
CRIU_BIN := criu
|
||||
endif
|
||||
|
||||
CRIU_SERVICE_SOCKET ?= $(CURDIR)/criu-service.socket
|
||||
CRIU_SERVICE_LOG ?= $(CURDIR)/criu-service.log
|
||||
CRIU_SERVICE_PIDFILE ?= $(CURDIR)/criu-service.pid
|
||||
|
||||
start-service:
|
||||
$(CRIU_BIN) service --address $(CRIU_SERVICE_SOCKET) \
|
||||
--log-file $(CRIU_SERVICE_LOG) \
|
||||
--pidfile $(CRIU_SERVICE_PIDFILE) -d -v4
|
||||
@echo "CRIU service started on $(CRIU_SERVICE_SOCKET) (PID $$(cat $(CRIU_SERVICE_PIDFILE)))"
|
||||
.PHONY: start-service
|
||||
|
||||
stop-service:
|
||||
@if [ -s $(CRIU_SERVICE_PIDFILE) ]; then \
|
||||
kill $$(cat $(CRIU_SERVICE_PIDFILE)) 2>/dev/null || true; \
|
||||
rm -f $(CRIU_SERVICE_PIDFILE); \
|
||||
else \
|
||||
echo "CRIU service not running (no pidfile at $(CRIU_SERVICE_PIDFILE))"; \
|
||||
fi
|
||||
.PHONY: stop-service
|
||||
|
||||
clean:
|
||||
rm -f $(TARGET) $(OBJ)
|
||||
.PHONY: clean
|
||||
|
||||
distclean: clean
|
||||
rm -rf $(INSTALL_DIR)
|
||||
.PHONY: distclean
|
||||
104
contrib/criu-service-client/README.md
Normal file
104
contrib/criu-service-client/README.md
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
# CRIU service client
|
||||
|
||||
An example CRIU service client that uses `libcriu` to checkpoint and restore
|
||||
processes. It demonstrates how to use the libcriu C API as an alternative to
|
||||
invoking the `criu` command-line tool directly.
|
||||
|
||||
The option names mirror those of the `criu` CLI so that switching between the
|
||||
two is straightforward.
|
||||
|
||||
## Building
|
||||
|
||||
By default, `make` builds `libcriu` from the source tree so there is
|
||||
no need to install anything first:
|
||||
|
||||
```
|
||||
make
|
||||
```
|
||||
|
||||
If you already have `libcriu` installed on your system (via the
|
||||
`criu-devel` RPM or `libcriu-dev` DEB package), you can skip the
|
||||
source build:
|
||||
|
||||
```
|
||||
make LOCAL=0
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
criu-service-client <dump|pre-dump|restore> [OPTIONS]
|
||||
```
|
||||
|
||||
### Dump
|
||||
|
||||
Checkpoint a running process into an image directory:
|
||||
|
||||
```
|
||||
sudo criu-service-client dump -t <PID> -D /tmp/imgs -j -v4
|
||||
```
|
||||
|
||||
### Pre-dump
|
||||
|
||||
Pre-dump captures only the modified memory pages while the process keeps
|
||||
running. This reduces final dump downtime by transferring most memory ahead
|
||||
of time. Use `--prev-images-dir` on subsequent pre-dumps or the final dump
|
||||
to point to the previous image directory (relative to `-D`):
|
||||
|
||||
```
|
||||
mkdir -p /tmp/imgs/1 /tmp/imgs/2 /tmp/imgs/3
|
||||
|
||||
sudo criu-service-client pre-dump -t <PID> -D /tmp/imgs/1 --track-mem -v4
|
||||
sudo criu-service-client pre-dump -t <PID> -D /tmp/imgs/2 --prev-images-dir ../1 --track-mem -v4
|
||||
sudo criu-service-client dump -t <PID> -D /tmp/imgs/3 --prev-images-dir ../2 --track-mem -j -v4
|
||||
```
|
||||
|
||||
### Restore
|
||||
|
||||
Restore a process from a previously created checkpoint:
|
||||
|
||||
```
|
||||
sudo criu-service-client restore -D /tmp/imgs -j -v4
|
||||
```
|
||||
|
||||
### Connecting to a CRIU service
|
||||
|
||||
By default the client launches `criu` internally via libcriu. To talk to an
|
||||
already-running `criu service` daemon, start the service first and then pass
|
||||
`--service-address`:
|
||||
|
||||
```
|
||||
sudo make start-service
|
||||
sudo criu-service-client dump -t <PID> -D /tmp/imgs --service-address ./criu-service.socket
|
||||
```
|
||||
|
||||
The `start-service` target daemonizes `criu service` with a pidfile. Stop
|
||||
it with `make stop-service`. The socket, log, and pidfile paths are
|
||||
configurable:
|
||||
|
||||
```
|
||||
sudo make start-service CRIU_SERVICE_SOCKET=/var/run/criu.socket CRIU_SERVICE_LOG=/var/log/criu.log
|
||||
sudo make stop-service
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Run the test suite (requires root for dump/restore operations):
|
||||
|
||||
```
|
||||
sudo make test
|
||||
```
|
||||
|
||||
This runs three tests:
|
||||
|
||||
- **run.sh** - iterative pre-dump, dump, and restore using a loop workload
|
||||
- **run-shell-job.py** - dump and restore of a process with a controlling
|
||||
terminal (`--shell-job`)
|
||||
- **run-service.sh** - pre-dump, dump, and restore through a `criu service`
|
||||
daemon (`--service-address`)
|
||||
|
||||
To test against the system-installed CRIU instead of building from source:
|
||||
|
||||
```
|
||||
sudo make LOCAL=0 test
|
||||
```
|
||||
384
contrib/criu-service-client/criu-service-client.c
Normal file
384
contrib/criu-service-client/criu-service-client.c
Normal file
|
|
@ -0,0 +1,384 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <limits.h>
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
#include <signal.h>
|
||||
#include <termios.h>
|
||||
#include <string.h>
|
||||
#include <sys/wait.h>
|
||||
#include <criu/criu.h>
|
||||
|
||||
enum action {
|
||||
ACT_DUMP,
|
||||
ACT_PRE_DUMP,
|
||||
ACT_RESTORE
|
||||
};
|
||||
|
||||
static struct option long_opts[] = {
|
||||
{ "tree", required_argument, 0, 't' },
|
||||
{ "images-dir", required_argument, 0, 'D' },
|
||||
{ "prev-images-dir", required_argument, 0, 7 },
|
||||
{ "shell-job", no_argument, 0, 'j' },
|
||||
{ "leave-running", no_argument, 0, 'R' },
|
||||
{ "tcp-established", no_argument, 0, 1 },
|
||||
{ "tcp-close", no_argument, 0, 6 },
|
||||
{ "ext-unix-sk", no_argument, 0, 'x' },
|
||||
{ "file-locks", no_argument, 0, 'l' },
|
||||
{ "manage-cgroups", no_argument, 0, 2 },
|
||||
{ "track-mem", no_argument, 0, 3 },
|
||||
{ "auto-dedup", no_argument, 0, 4 },
|
||||
{ "log-file", required_argument, 0, 'o' },
|
||||
{ "verbosity", optional_argument, 0, 'v' },
|
||||
{ "service-address", required_argument, 0, 5 },
|
||||
{ "help", no_argument, 0, 'h' },
|
||||
{ 0, 0, 0, 0 }
|
||||
};
|
||||
|
||||
static void print_usage(FILE *out)
|
||||
{
|
||||
fprintf(out, "Usage: criu-service-client <dump|pre-dump|restore> [OPTIONS]\n\n");
|
||||
fprintf(out, "Options:\n");
|
||||
fprintf(out, " -t, --tree <PID> Target PID to checkpoint (dump/pre-dump only)\n");
|
||||
fprintf(out, " -D, --images-dir <DIR> Directory to store/load checkpoint images\n");
|
||||
fprintf(out, " --prev-images-dir <DIR> Path to parent images from a previous dump\n");
|
||||
fprintf(out, " -j, --shell-job Allow dumping processes with TTY\n");
|
||||
fprintf(out, " -R, --leave-running Leave the process running after dump\n");
|
||||
fprintf(out, " --tcp-established Dump established TCP connections\n");
|
||||
fprintf(out, " --tcp-close Close TCP connections on restore\n");
|
||||
fprintf(out, " -x, --ext-unix-sk Dump external Unix sockets\n");
|
||||
fprintf(out, " -l, --file-locks Dump file locks\n");
|
||||
fprintf(out, " --manage-cgroups Manage cgroups\n");
|
||||
fprintf(out, " --track-mem Track memory changes\n");
|
||||
fprintf(out, " --auto-dedup Deduplicate memory pages automatically\n");
|
||||
fprintf(out, " -o, --log-file <FILE> Log file path (default: criu.log)\n");
|
||||
fprintf(out, " -v[N], --verbosity[=N] Log level 0-4 (default: 2); -v raises by 1\n");
|
||||
fprintf(out, " --service-address <SOCK> CRIU service Unix socket path\n");
|
||||
fprintf(out, " -h, --help Show this help message\n");
|
||||
}
|
||||
|
||||
static void print_criu_error(const char *op, int ret)
|
||||
{
|
||||
const char *msg;
|
||||
|
||||
switch (ret) {
|
||||
case -EBADE:
|
||||
msg = "RPC returned failure (check the CRIU log)";
|
||||
break;
|
||||
case -ECONNREFUSED:
|
||||
msg = "Unable to connect to CRIU service";
|
||||
break;
|
||||
case -ECOMM:
|
||||
msg = "Unable to send/recv message to/from CRIU";
|
||||
break;
|
||||
case -EINVAL:
|
||||
msg = "CRIU rejected the request (bad sequence or unsupported request type)";
|
||||
break;
|
||||
case -EBADMSG:
|
||||
msg = "Unexpected response from CRIU (consider updating CRIU)";
|
||||
break;
|
||||
default:
|
||||
msg = "Unknown error";
|
||||
break;
|
||||
}
|
||||
|
||||
if (errno)
|
||||
fprintf(stderr, "CRIU %s failed: %s (ret=%d, errno=%d: %s)\n",
|
||||
op, msg, ret, errno, strerror(errno));
|
||||
else
|
||||
fprintf(stderr, "CRIU %s failed: %s (ret=%d)\n",
|
||||
op, msg, ret);
|
||||
}
|
||||
|
||||
static int parse_int(const char *str, const char *name, int *val)
|
||||
{
|
||||
char *end;
|
||||
long v;
|
||||
|
||||
errno = 0;
|
||||
v = strtol(str, &end, 10);
|
||||
if (errno || *end != '\0' || end == str || v < INT_MIN || v > INT_MAX) {
|
||||
fprintf(stderr, "Invalid value for %s: '%s'\n", name, str);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*val = (int)v;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
enum action action;
|
||||
int opt, option_index = 0;
|
||||
int pid = -1;
|
||||
const char *images_dir = NULL;
|
||||
const char *prev_images_dir = NULL;
|
||||
const char *log_file = "criu.log";
|
||||
int log_level = CRIU_LOG_WARN;
|
||||
const char *service_socket = NULL;
|
||||
int dirfd = -1;
|
||||
int ret = 1;
|
||||
|
||||
bool shell_job = false;
|
||||
bool leave_running = false;
|
||||
bool tcp_established = false;
|
||||
bool tcp_close = false;
|
||||
bool ext_unix_sk = false;
|
||||
bool file_locks = false;
|
||||
bool manage_cgroups = false;
|
||||
bool track_mem = false;
|
||||
bool auto_dedup = false;
|
||||
|
||||
if (argc < 2) {
|
||||
print_usage(stderr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strcmp(argv[1], "dump") == 0) {
|
||||
action = ACT_DUMP;
|
||||
} else if (strcmp(argv[1], "pre-dump") == 0) {
|
||||
action = ACT_PRE_DUMP;
|
||||
} else if (strcmp(argv[1], "restore") == 0) {
|
||||
action = ACT_RESTORE;
|
||||
} else if (strcmp(argv[1], "help") == 0 || strcmp(argv[1], "--help") == 0) {
|
||||
print_usage(stdout);
|
||||
return 0;
|
||||
} else {
|
||||
fprintf(stderr, "Unknown command '%s'; expected dump, pre-dump, restore, or help\n",
|
||||
argv[1]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Skip the subcommand so getopt_long() only sees the options. */
|
||||
argc--;
|
||||
argv++;
|
||||
|
||||
while ((opt = getopt_long(argc, argv, "t:D:jRxlo:v::h", long_opts,
|
||||
&option_index)) != -1) {
|
||||
switch (opt) {
|
||||
case 't':
|
||||
if (parse_int(optarg, "tree", &pid))
|
||||
return 1;
|
||||
if (pid <= 0) {
|
||||
fprintf(stderr, "PID must be positive\n");
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'D':
|
||||
images_dir = optarg;
|
||||
break;
|
||||
case 'j':
|
||||
shell_job = true;
|
||||
break;
|
||||
case 'R':
|
||||
leave_running = true;
|
||||
break;
|
||||
case 1:
|
||||
tcp_established = true;
|
||||
break;
|
||||
case 6:
|
||||
tcp_close = true;
|
||||
break;
|
||||
case 'x':
|
||||
ext_unix_sk = true;
|
||||
break;
|
||||
case 'l':
|
||||
file_locks = true;
|
||||
break;
|
||||
case 2:
|
||||
manage_cgroups = true;
|
||||
break;
|
||||
case 3:
|
||||
track_mem = true;
|
||||
break;
|
||||
case 4:
|
||||
auto_dedup = true;
|
||||
break;
|
||||
case 7:
|
||||
prev_images_dir = optarg;
|
||||
break;
|
||||
case 'o':
|
||||
log_file = optarg;
|
||||
break;
|
||||
case 'v':
|
||||
if (optarg) {
|
||||
if (optarg[0] == 'v')
|
||||
log_level += strlen(optarg) + 1;
|
||||
else if (parse_int(optarg, "verbosity", &log_level))
|
||||
return 1;
|
||||
} else {
|
||||
log_level++;
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
service_socket = optarg;
|
||||
break;
|
||||
case 'h':
|
||||
print_usage(stdout);
|
||||
return 0;
|
||||
default:
|
||||
print_usage(stderr);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!images_dir) {
|
||||
fprintf(stderr, "Images directory required (-D)\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (action != ACT_RESTORE && pid <= 0) {
|
||||
fprintf(stderr, "%s requires -t <pid>\n",
|
||||
action == ACT_DUMP ? "Dump" : "Pre-dump");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (log_level < CRIU_LOG_MSG || log_level > CRIU_LOG_DEBUG) {
|
||||
fprintf(stderr, "Verbosity must be in the range 0-4\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (criu_init_opts() < 0) {
|
||||
perror("criu_init_opts");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (service_socket && criu_set_service_address(service_socket)) {
|
||||
perror("criu_set_service_address");
|
||||
goto out;
|
||||
}
|
||||
|
||||
dirfd = open(images_dir, O_RDONLY | O_DIRECTORY);
|
||||
if (dirfd < 0) {
|
||||
fprintf(stderr, "Failed to open images dir '%s': %s\n",
|
||||
images_dir, strerror(errno));
|
||||
goto out;
|
||||
}
|
||||
|
||||
criu_set_images_dir_fd(dirfd);
|
||||
if (prev_images_dir && criu_set_parent_images(prev_images_dir)) {
|
||||
perror("criu_set_parent_images");
|
||||
goto out;
|
||||
}
|
||||
if (criu_set_log_file(log_file)) {
|
||||
perror("criu_set_log_file");
|
||||
goto out;
|
||||
}
|
||||
criu_set_log_level(log_level);
|
||||
|
||||
if (shell_job)
|
||||
criu_set_shell_job(true);
|
||||
if (leave_running)
|
||||
criu_set_leave_running(true);
|
||||
if (tcp_established)
|
||||
criu_set_tcp_established(true);
|
||||
if (tcp_close)
|
||||
criu_set_tcp_close(true);
|
||||
if (ext_unix_sk)
|
||||
criu_set_ext_unix_sk(true);
|
||||
if (file_locks)
|
||||
criu_set_file_locks(true);
|
||||
if (manage_cgroups)
|
||||
criu_set_manage_cgroups(true);
|
||||
if (track_mem)
|
||||
criu_set_track_mem(true);
|
||||
if (auto_dedup)
|
||||
criu_set_auto_dedup(true);
|
||||
|
||||
switch (action) {
|
||||
case ACT_PRE_DUMP:
|
||||
criu_set_pid(pid);
|
||||
fprintf(stderr, "Pre-dumping PID %d to '%s'\n", pid, images_dir);
|
||||
ret = criu_pre_dump();
|
||||
break;
|
||||
case ACT_DUMP:
|
||||
criu_set_pid(pid);
|
||||
fprintf(stderr, "Dumping PID %d to '%s'\n", pid, images_dir);
|
||||
ret = criu_dump();
|
||||
break;
|
||||
case ACT_RESTORE:
|
||||
fprintf(stderr, "Restoring from '%s'\n", images_dir);
|
||||
ret = criu_restore_child();
|
||||
if (ret <= 0)
|
||||
break;
|
||||
|
||||
pid = ret;
|
||||
ret = 0;
|
||||
|
||||
/*
|
||||
* When the restored process shares our session and
|
||||
* stdin is a terminal, hand the terminal over to the
|
||||
* restored process so it can read input and receive
|
||||
* signals (Ctrl-C, etc.). We temporarily ignore
|
||||
* SIGTTOU because tcsetpgrp() would otherwise stop
|
||||
* this process since it is no longer in the foreground.
|
||||
*
|
||||
* Skip this when the restored process lives in a
|
||||
* different session since tcsetpgrp() only works
|
||||
* within the same session.
|
||||
*/
|
||||
if (shell_job && isatty(STDIN_FILENO) &&
|
||||
getsid(pid) == getsid(0)) {
|
||||
struct sigaction sa, old_sa;
|
||||
pid_t pgrp;
|
||||
|
||||
pgrp = getpgid(pid);
|
||||
if (pgrp < 0) {
|
||||
perror("getpgid");
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
sa.sa_handler = SIG_IGN;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = 0;
|
||||
if (sigaction(SIGTTOU, &sa, &old_sa) < 0) {
|
||||
perror("sigaction");
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (tcsetpgrp(STDIN_FILENO, pgrp) < 0)
|
||||
perror("tcsetpgrp");
|
||||
|
||||
sigaction(SIGTTOU, &old_sa, NULL);
|
||||
}
|
||||
|
||||
if (waitpid(pid, &ret, 0) < 0) {
|
||||
perror("waitpid");
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (WIFEXITED(ret))
|
||||
ret = WEXITSTATUS(ret);
|
||||
else if (WIFSIGNALED(ret))
|
||||
ret = 128 + WTERMSIG(ret);
|
||||
else
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
const char *names[] = {
|
||||
[ACT_DUMP] = "dump",
|
||||
[ACT_PRE_DUMP] = "pre-dump",
|
||||
[ACT_RESTORE] = "restore",
|
||||
};
|
||||
|
||||
print_criu_error(names[action], ret);
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
out:
|
||||
if (dirfd >= 0)
|
||||
close(dirfd);
|
||||
return ret;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue