diff --git a/Documentation/criu.txt b/Documentation/criu.txt index d764dbce3..38df6c98f 100644 --- a/Documentation/criu.txt +++ b/Documentation/criu.txt @@ -378,6 +378,60 @@ mount -t cgroup -o devices,freezer none devices,freezer Allows to link unlinked files back, if possible (modifies filesystem during *restore*). +*-c, --compress*:: + Enable LZ4 per-page compression of memory pages during *dump*. + Each system page is compressed as an independent LZ4 block. + Zero-filled pages are detected and stored with no data; pages that + do not compress well are stored raw to avoid decompression overhead + on *restore*. The compression choice made at *dump* time is recorded + in the inventory image and applied automatically on *restore*. + Compatible with *--page-server* when *--tls* is not used, *--stream*, + *--lazy-pages*, iterative checkpointing, *--auto-dedup*, and the *dedup* + command. + +*--compress-region* 'size':: + Enable LZ4 region compression of memory pages during *dump*. + A run of consecutive pages totalling 'size' bytes is compressed as + a single LZ4 block, which yields a better ratio and faster dump + on heap-shaped data than per-page compression at the cost of + slightly slower restore on partial reads. 'size' accepts K/M/G + suffixes (e.g. *256K*, *1M*); it must be a multiple of the system + page size and at most 4M. Mutually exclusive with *--compress*. + Currently supports only the local image path (no + *--page-server* / *--stream*). Hole-punching deduplication is + skipped for compressed images since compressed pages are + variably-sized. + +*--compress-acceleration* 'N':: + Set the LZ4 acceleration level for page compression (1 to 65537). + Controls how thoroughly the compressor searches for matching byte + sequences. The default value of 1 gives the best compression + ratio. Higher values skip more match candidates, resulting in + faster compression but larger output. Decompression speed is not + affected. Implies *--compress* unless *--compress-region* is set. + +*--decompress-threads* 'N':: + Set worker concurrency for LZ4 decoding and eligible large zero fills, + including the calling restore thread. The default value of 1 keeps each + LZ4 decode serial and handles zero blocks without a worker pool; + independent private, shmem, and memfd requests can still run concurrently. + A value of 0 lets CRIU choose the concurrency for each batch. CRIU starts + with the CPUs allowed by its affinity mask, then limits the width by the + number and decoded size of the blocks in that batch and by the restore-wide + CPU budget. Small batches and zero runs with fewer than two blocks remain + serial. Values above 1 set an upper bound on aggregate worker concurrency + instead of using the automatic CPU count. Explicit requests above the + available CPU count are reduced with a warning. The accepted range is 0 + through 1024; this validation ceiling does not determine the automatic + concurrency. + Encoded staging memory is bounded independently of the thread count. + Restore keeps at most two active 32 MiB encoded input buffers. When a + local reader can reserve a second buffer without waiting, its calling thread + reads the next payload while pool workers decode the current one. If + another restore request already owns the second buffer, reading remains + synchronous. The option can also be set through a configuration file + (*decompress-threads 0*). + *--timeout* 'number':: Set a time limit in seconds for collecting tasks during the dump operation. The timeout is 10 seconds by default. diff --git a/Makefile b/Makefile index 95a48175a..6680f2d66 100644 --- a/Makefile +++ b/Makefile @@ -479,6 +479,7 @@ shellcheck: shellcheck -x test/others/config-file/*.sh shellcheck -x test/others/action-script/*.sh shellcheck -x contrib/criu-service-client/test/*.sh + shellcheck -x test/others/compression/*/*.sh codespell: codespell diff --git a/criu/config.c b/criu/config.c index ea75701ad..f6f4fc79f 100644 --- a/criu/config.c +++ b/criu/config.c @@ -1,6 +1,8 @@ #include +#include #include #include +#include #include #include #include @@ -13,6 +15,7 @@ #include "cgroup.h" #include "cgroup-props.h" #include "common/bug.h" +#include "compression.h" #include "cpu.h" #include "crtools.h" #include "cr_options.h" @@ -551,6 +554,41 @@ static size_t parse_size(char *optarg) return (size_t)atol(optarg); } +static int parse_size_strict(const char *value, size_t *size) +{ + unsigned long long number, multiplier = 1; + char *end; + + if (!value[0] || value[0] < '0' || value[0] > '9') + return -1; + + errno = 0; + number = strtoull(value, &end, 10); + if (errno == ERANGE || end == value) + return -1; + + if (*end) { + switch (*end++) { + case 'K': + multiplier = 1024; + break; + case 'M': + multiplier = 1024 * 1024; + break; + case 'G': + multiplier = 1024ULL * 1024 * 1024; + break; + default: + return -1; + } + } + if (*end || number > SIZE_MAX / multiplier) + return -1; + + *size = (size_t)(number * multiplier); + return 0; +} + static int parse_join_ns(const char *ptr) { char *aux, *ns_file, *extra_opts = NULL; @@ -634,7 +672,7 @@ int parse_options(int argc, char **argv, bool *usage_error, bool *has_exec_cmd, "no-" OPT_NAME, no_argument, SAVE_TO, false \ } - static const char short_opts[] = "dSsRt:hD:o:v::x::Vr:jJ:lW:L:M:"; + static const char short_opts[] = "dSscRt:hD:o:v::x::Vr:jJ:lW:L:M:"; static struct option long_opts[] = { { "tree", required_argument, 0, 't' }, { "leave-stopped", no_argument, 0, 's' }, @@ -722,6 +760,10 @@ int parse_options(int argc, char **argv, bool *usage_error, bool *has_exec_cmd, BOOL_OPT("unprivileged", &opts.unprivileged), BOOL_OPT("ghost-fiemap", &opts.ghost_fiemap), BOOL_OPT(OPT_ALLOW_UPROBES, &opts.allow_uprobes), + { "compress", no_argument, 0, 'c' }, + { "compress-acceleration", required_argument, 0, 1102 }, + { "compress-region", required_argument, 0, 1103 }, + { "decompress-threads", required_argument, 0, 1104 }, {}, }; @@ -831,6 +873,58 @@ int parse_options(int argc, char **argv, bool *usage_error, bool *has_exec_cmd, } else opts.log_level++; break; + case 'c': + if (opts.compress_mode == COMPRESS_REGION) { + pr_err("--compress conflicts with --compress-region\n"); + return 1; + } + opts.compress_mode = COMPRESS_PER_PAGE; + break; + case 1102: { + char *endptr; + long accel = strtol(optarg, &endptr, 10); + + if (*endptr != '\0' || accel < 1 || accel > LZ4_MAX_ACCELERATION) { + pr_err("Invalid --compress-acceleration value '%s' (must be 1..%d)\n", + optarg, LZ4_MAX_ACCELERATION); + return 1; + } + opts.compress_acceleration = accel; + break; + } + case 1103: { + size_t sz; + + if (parse_size_strict(optarg, &sz) || sz == 0 || + sz % PAGE_SIZE != 0 || sz > MAX_REGION_SIZE) { + pr_err("Invalid --compress-region '%s' (must be a multiple of %lu, max %lu)\n", + optarg, (unsigned long)PAGE_SIZE, + MAX_REGION_SIZE); + return 1; + } + if (opts.compress_mode == COMPRESS_PER_PAGE) { + pr_err("--compress-region conflicts with --compress\n"); + return 1; + } + opts.compress_region_size = sz; + opts.compress_mode = COMPRESS_REGION; + break; + } + case 1104: { + char *endptr; + long n; + + errno = 0; + n = strtol(optarg, &endptr, 10); + if (errno == ERANGE || endptr == optarg || *endptr != '\0' || + n < 0 || n > 1024) { + pr_err("Invalid --decompress-threads value '%s' (must be 0..1024)\n", + optarg); + return 1; + } + opts.decompress_threads = (unsigned int)n; + break; + } case 1043: { int fd; @@ -1097,6 +1191,76 @@ bad_arg: int check_options(void) { + /* + * --compress-acceleration (CLI) or compress_acceleration (RPC) on + * their own imply per-page compression. Resolve that here rather + * than while parsing each option, so that passing acceleration + * before --compress-region is not mistaken for a conflict with an + * implicit --compress. + */ + if (opts.compress_acceleration && opts.compress_mode == COMPRESS_OFF) + opts.compress_mode = COMPRESS_PER_PAGE; + + /* + * Compression is selected by the dump client and encoded in each page + * server wire command. Letting the server select a mode independently + * can make the payload disagree with the inventory written by the client. + */ + if (opts.mode == CR_PAGE_SERVER && opts.compress_mode != COMPRESS_OFF) { + pr_err("Memory page compression options apply to the dump client, not the page server\n"); + return 1; + } + + if (opts.compress_mode) { +#ifndef CONFIG_LZ4 + pr_err("Memory page compression requires CRIU built with LZ4 support (CONFIG_LZ4)\n"); + return 1; +#else + if (opts.compress_mode == COMPRESS_REGION) { + if (opts.compress_region_size == 0) + opts.compress_region_size = DEFAULT_REGION_SIZE; + if (opts.compress_region_size % PAGE_SIZE != 0 || + opts.compress_region_size > MAX_REGION_SIZE) { + pr_err("Invalid compress region size %u\n", + opts.compress_region_size); + return 1; + } + pr_debug("Region compression of memory pages is enabled (region=%u bytes)\n", + opts.compress_region_size); + } else { + pr_debug("Per-page compression of memory pages is enabled\n"); + } +#endif + } + + /* + * Region compression is currently only implemented for the local + * dump and restore paths. The page-server + * and image-streamer wire formats are per-page; combining them with + * --compress-region would produce an image the receiver cannot read. + * Reject the combination early. + */ + if (opts.compress_mode == COMPRESS_REGION) { + if (opts.use_page_server || opts.addr) { + pr_err("--compress-region is not supported with --page-server\n"); + return 1; + } + if (opts.stream) { + pr_err("--compress-region is not supported with --stream\n"); + return 1; + } + } + + /* + * The compressed page-server sender writes its records straight to + * the socket and does not route them through the TLS helpers, so a + * TLS page-server would receive plaintext into the encrypted stream. + * Reject the combination until compressed sends learn to use TLS. + */ + if (opts.compress_mode && opts.tls && (opts.use_page_server || opts.addr)) { + pr_err("Memory page compression is not supported with a TLS page-server\n"); + return 1; + } if (opts.tcp_established_ok) pr_info("Will dump/restore TCP connections\n"); if (opts.tcp_skip_in_flight) diff --git a/criu/cr-check.c b/criu/cr-check.c index d7aa43076..67a49ee28 100644 --- a/criu/cr-check.c +++ b/criu/cr-check.c @@ -1809,6 +1809,16 @@ static int check_binfmt_misc_sandboxing(void) return 0; } +static int check_compress(void) +{ +#ifdef CONFIG_LZ4 + return 0; +#else + pr_info("LZ4 compression support is not compiled in\n"); + return -1; +#endif +} + struct feature_list { char *name; int (*func)(void); @@ -1860,6 +1870,7 @@ static struct feature_list feature_list[] = { { "overlayfs_maps", check_overlayfs_maps }, { "pagemap_scan_guard_pages", check_pagemap_scan_guard_pages }, { "binfmt_misc_sandboxing", check_binfmt_misc_sandboxing }, + { "compress", check_compress }, { NULL, NULL }, }; diff --git a/criu/cr-service.c b/criu/cr-service.c index f88934b61..dc4b9804d 100644 --- a/criu/cr-service.c +++ b/criu/cr-service.c @@ -47,6 +47,7 @@ #include "cr-errno.h" #include "namespaces.h" +#include "compression.h" unsigned int service_sk_ino = -1; @@ -837,6 +838,64 @@ static int setup_opts_from_req(int sk, CriuOpts *req) if (req->mntns_compat_mode) opts.mntns_compat_mode = true; + if (req->has_compress) { + if (req->compress > COMPRESS_REGION) { + pr_err("Invalid compress value %u\n", req->compress); + goto err; + } + opts.compress_mode = req->compress; + if (req->compress == COMPRESS_OFF) { + /* + * An explicit RPC setting has precedence over values loaded + * from the service configuration. libcriu clears the related + * request fields when compression is disabled; reject a raw + * RPC request which asks for both settings at once. + */ + if (req->has_compress_acceleration || + req->has_compress_region_size) { + pr_err("compress=off conflicts with compression tuning options\n"); + goto err; + } + opts.compress_acceleration = 0; + opts.compress_region_size = 0; + } + } + + if (req->has_compress_acceleration) { + if (req->compress_acceleration < 1 || + req->compress_acceleration > LZ4_MAX_ACCELERATION) { + pr_err("Invalid compress_acceleration value %u (must be 1..%d)\n", + req->compress_acceleration, LZ4_MAX_ACCELERATION); + goto err; + } + opts.compress_acceleration = req->compress_acceleration; + } + + if (req->has_compress_region_size) { + if (req->compress_region_size == 0 || + req->compress_region_size % PAGE_SIZE != 0 || + req->compress_region_size > MAX_REGION_SIZE) { + pr_err("Invalid compress_region_size value %u\n", + req->compress_region_size); + goto err; + } + if (opts.compress_mode == COMPRESS_PER_PAGE) { + pr_err("compress_region_size conflicts with compress=per-page\n"); + goto err; + } + opts.compress_region_size = req->compress_region_size; + opts.compress_mode = COMPRESS_REGION; + } + + if (req->has_decompress_threads) { + if (req->decompress_threads > 1024) { + pr_err("Invalid decompress_threads value %u (must be 0..1024)\n", + req->decompress_threads); + goto err; + } + opts.decompress_threads = req->decompress_threads; + } + if (check_options()) goto err; @@ -1227,6 +1286,8 @@ static int handle_feature_check(int sk, CriuReq *msg) feat.lazy_pages = false; feat.has_pidfd_store = 1; feat.pidfd_store = false; + feat.has_mem_compression = 1; + feat.mem_compression = false; pid = fork(); if (pid < 0) { @@ -1249,6 +1310,13 @@ static int handle_feature_check(int sk, CriuReq *msg) if ((msg->features->has_pidfd_store == 1) && (msg->features->pidfd_store == true)) feat.pidfd_store = kdat.has_pidfd_getfd && kdat.has_pidfd_open; + if ((msg->features->has_mem_compression == 1) && + (msg->features->mem_compression == true)) { +#ifdef CONFIG_LZ4 + feat.mem_compression = true; +#endif + } + resp.features = &feat; resp.type = msg->type; /* The feature check is working, actual results are in resp.features */ diff --git a/criu/crtools.c b/criu/crtools.c index 95c2c1f29..112b8e86d 100644 --- a/criu/crtools.c +++ b/criu/crtools.c @@ -556,6 +556,23 @@ usage: " will be punched from the image\n" " --pre-dump-mode splice - parasite based pre-dumping (default)\n" " read - process_vm_readv syscall based pre-dumping\n" +#ifdef CONFIG_LZ4 + " -c|--compress enable LZ4 per-page compression of memory pages\n" + " --compress-region size\n" + " enable memory page compression for given region size;\n" + " size accepts K/M/G suffixes (e.g. 256K, 1M);\n" + " valid range: page-size multiples up to 4M\n" + " --compress-acceleration N\n" + " LZ4 acceleration (default is 1; max is 65537).\n" + " Higher values favor speed over compression ratio.\n" + " Implies --compress unless --compress-region is set.\n" + " --decompress-threads N\n" + " worker concurrency for LZ4 decode and zero fill\n" + " (default: 1 = serial per request; 0 = auto;\n" + " N > 1 = aggregate worker limit).\n" + " Valid range: 0..1024; affinity, batch work, and the\n" + " restore-wide budget may reduce active width.\n" +#endif "\n" "Page/Service server options:\n" " --address ADDR address of server or service\n" diff --git a/images/rpc.proto b/images/rpc.proto index c63f33026..b7bf62ea4 100644 --- a/images/rpc.proto +++ b/images/rpc.proto @@ -151,6 +151,22 @@ message criu_opts { optional bool display_stats = 70; optional bool log_to_stderr = 71; optional criu_image_io_mode image_io_mode = 72 [default = IMAGE_IO_WRITEBACK]; + /* + * Memory page compression mode (enum compress_mode): + * 0 = off, 1 = per-page, 2 = region. Default 0. + */ + optional uint32 compress = 73; + optional uint32 compress_acceleration = 74; + optional uint32 compress_region_size = 75; + /* + * Worker concurrency for LZ4 decoding and eligible large zero fills. + * 0 selects automatic concurrency; 1, the default, keeps LZ4 decoding + * serial and disables zero-fill workers; values above 1 limit aggregate + * worker concurrency. Active concurrency also depends on available CPUs, + * useful batch work, and the restore-wide CPU budget. Valid range: 0 + * through 1024. Restore-time only. + */ + optional uint32 decompress_threads = 76 [default = 1]; /* optional bool check_mounts = 128; */ } @@ -198,6 +214,8 @@ message criu_features { optional bool mem_track = 1; optional bool lazy_pages = 2; optional bool pidfd_store = 3; + /* Memory page compression support is available in this CRIU build. */ + optional bool mem_compression = 4; } /* diff --git a/lib/c/criu.c b/lib/c/criu.c index 01afb829b..36b17f7c9 100644 --- a/lib/c/criu.c +++ b/lib/c/criu.c @@ -376,6 +376,85 @@ void criu_set_track_mem(bool track_mem) criu_local_set_track_mem(global_opts, track_mem); } +int criu_local_set_compress(criu_opts *opts, enum criu_compress_mode mode) +{ + if (mode < CRIU_COMPRESS_OFF || mode > CRIU_COMPRESS_REGION) + return -EINVAL; + + opts->rpc->has_compress = true; + opts->rpc->compress = mode; + if (mode != CRIU_COMPRESS_REGION) { + opts->rpc->has_compress_region_size = false; + opts->rpc->compress_region_size = 0; + } + if (mode == CRIU_COMPRESS_OFF) { + opts->rpc->has_compress_acceleration = false; + opts->rpc->compress_acceleration = 0; + } + return 0; +} + +int criu_set_compress(enum criu_compress_mode mode) +{ + return criu_local_set_compress(global_opts, mode); +} + +int criu_local_set_compress_acceleration(criu_opts *opts, + unsigned int acceleration) +{ + if (acceleration < 1 || acceleration > CRIU_COMPRESS_MAX_ACCELERATION) + return -EINVAL; + + opts->rpc->has_compress_acceleration = true; + opts->rpc->compress_acceleration = acceleration; + if (!opts->rpc->has_compress || + opts->rpc->compress == CRIU_COMPRESS_OFF) { + opts->rpc->has_compress = true; + opts->rpc->compress = CRIU_COMPRESS_PER_PAGE; + } + return 0; +} + +int criu_set_compress_acceleration(unsigned int acceleration) +{ + return criu_local_set_compress_acceleration(global_opts, acceleration); +} + +int criu_local_set_compress_region_size(criu_opts *opts, unsigned int bytes) +{ + long page_size = sysconf(_SC_PAGESIZE); + + if (page_size <= 0 || !bytes || bytes > CRIU_COMPRESS_MAX_REGION_SIZE || + bytes % (unsigned long)page_size) + return -EINVAL; + + opts->rpc->has_compress_region_size = true; + opts->rpc->compress_region_size = bytes; + opts->rpc->has_compress = true; + opts->rpc->compress = CRIU_COMPRESS_REGION; + return 0; +} + +int criu_set_compress_region_size(unsigned int bytes) +{ + return criu_local_set_compress_region_size(global_opts, bytes); +} + +int criu_local_set_decompress_threads(criu_opts *opts, unsigned int threads) +{ + if (threads > CRIU_DECOMPRESS_MAX_THREADS) + return -EINVAL; + + opts->rpc->has_decompress_threads = true; + opts->rpc->decompress_threads = threads; + return 0; +} + +int criu_set_decompress_threads(unsigned int threads) +{ + return criu_local_set_decompress_threads(global_opts, threads); +} + void criu_local_set_auto_dedup(criu_opts *opts, bool auto_dedup) { opts->rpc->has_auto_dedup = true; @@ -2007,6 +2086,10 @@ int criu_local_feature_check(criu_opts *opts, struct criu_feature_check *feature criu_features.has_pidfd_store = true; criu_features.pidfd_store = true; } + if (features_copy.mem_compression) { + criu_features.has_mem_compression = true; + criu_features.mem_compression = true; + } req.features = &criu_features; ret = send_req_and_recv_resp(opts, &req, &resp); @@ -2025,6 +2108,9 @@ int criu_local_feature_check(criu_opts *opts, struct criu_feature_check *feature if (resp->features->has_pidfd_store) { features_copy.pidfd_store = resp->features->pidfd_store; } + if (resp->features->has_mem_compression) { + features_copy.mem_compression = resp->features->mem_compression; + } memcpy(features, &features_copy, size); } else { ret = -EBADE; diff --git a/lib/c/criu.h b/lib/c/criu.h index 81bbfd5c8..8eaf95cbd 100644 --- a/lib/c/criu.h +++ b/lib/c/criu.h @@ -60,6 +60,17 @@ enum criu_image_io_mode { CRIU_IMAGE_IO_DIRECT = 1, }; +enum criu_compress_mode { + CRIU_COMPRESS_OFF = 0, + CRIU_COMPRESS_PER_PAGE = 1, + CRIU_COMPRESS_REGION = 2, +}; + +#define CRIU_COMPRESS_MAX_ACCELERATION 65537U +#define CRIU_COMPRESS_MAX_REGION_SIZE (4U * 1024U * 1024U) +/* Maximum explicit setting; automatic concurrency is not capped by it. */ +#define CRIU_DECOMPRESS_MAX_THREADS 1024U + int criu_set_service_address(const char *path); void criu_set_service_fd(int fd); int criu_set_service_binary(const char *path); @@ -90,6 +101,17 @@ void criu_set_unprivileged(bool unprivileged); void criu_set_orphan_pts_master(bool orphan_pts_master); void criu_set_file_locks(bool file_locks); void criu_set_track_mem(bool track_mem); +int criu_set_compress(enum criu_compress_mode mode); +int criu_set_compress_acceleration(unsigned int acceleration); +int criu_set_compress_region_size(unsigned int bytes); +/* + * Worker concurrency for LZ4 decoding and eligible large zero fills: + * 0 = auto, 1 = serial/no zero-fill workers (default), N > 1 = aggregate + * worker limit. + * CPU affinity, useful batch work, and the shared budget may reduce active + * concurrency. + */ +int criu_set_decompress_threads(unsigned int threads); void criu_set_auto_dedup(bool auto_dedup); void criu_set_force_irmap(bool force_irmap); void criu_set_link_remap(bool link_remap); @@ -253,6 +275,11 @@ void criu_local_set_skip_file_rwx_check(criu_opts *opts, bool skip_file_rwx_chec void criu_local_set_orphan_pts_master(criu_opts *opts, bool orphan_pts_master); void criu_local_set_file_locks(criu_opts *opts, bool file_locks); void criu_local_set_track_mem(criu_opts *opts, bool track_mem); +int criu_local_set_compress(criu_opts *opts, enum criu_compress_mode mode); +int criu_local_set_compress_acceleration(criu_opts *opts, unsigned int acceleration); +int criu_local_set_compress_region_size(criu_opts *opts, unsigned int bytes); +/* Uses the same worker-concurrency values as criu_set_decompress_threads(). */ +int criu_local_set_decompress_threads(criu_opts *opts, unsigned int threads); void criu_local_set_auto_dedup(criu_opts *opts, bool auto_dedup); void criu_local_set_force_irmap(criu_opts *opts, bool force_irmap); void criu_local_set_link_remap(criu_opts *opts, bool link_remap); @@ -327,6 +354,7 @@ struct criu_feature_check { bool mem_track; bool lazy_pages; bool pidfd_store; + bool mem_compression; }; int criu_feature_check(struct criu_feature_check *features, size_t size); diff --git a/scripts/ci/run-ci-tests.sh b/scripts/ci/run-ci-tests.sh index 073996373..424d4ca15 100755 --- a/scripts/ci/run-ci-tests.sh +++ b/scripts/ci/run-ci-tests.sh @@ -386,6 +386,7 @@ run_non_shardable_tests() { # config file parser and parameter testing make -C test/others/config-file run + make -C test/others/compression/page-server run # action script testing make -C test/others/action-script run diff --git a/test/others/compression/page-server/Makefile b/test/others/compression/page-server/Makefile new file mode 100644 index 000000000..594edc070 --- /dev/null +++ b/test/others/compression/page-server/Makefile @@ -0,0 +1,3 @@ +run: + ./run.sh +.PHONY: run diff --git a/test/others/compression/page-server/run.sh b/test/others/compression/page-server/run.sh new file mode 100755 index 000000000..45ff4f364 --- /dev/null +++ b/test/others/compression/page-server/run.sh @@ -0,0 +1,118 @@ +#!/bin/bash + +# Compression is a dump-client setting carried by page-server wire commands. +# A page server must reject local compression settings instead of silently +# changing how ordinary PS_IOV_ADD_F payloads are stored. + +set -euo pipefail + +# shellcheck source=test/others/env.sh +source ../../env.sh + +LOG="page-server-options.$$.log" + +function cleanup { + rm -f "$LOG" +} + +trap cleanup EXIT + +function fail { + echo "FAIL: $*" + exit 1 +} + +function check_rejected { + local name="$1" + shift + local status + + rm -f "$LOG" + set +e + timeout --signal=KILL 5 "${CRIU}" page-server --no-default-config \ + -D . -o "$LOG" --port 54321 "$@" + status=$? + set -e + + if [ "$status" -ne 1 ]; then + fail "$name returned $status instead of rejecting the option" + fi + grep -q "compression options apply to the dump client" "$LOG" || \ + fail "$name did not report the page-server compression error" +} + +function check_invalid_region { + local value="$1" + local status + + rm -f "$LOG" + set +e + timeout --foreground --kill-after=1s 5s \ + "${CRIU}" page-server --no-default-config -D . \ + --port 54321 --compress-region="$value" > "$LOG" 2>&1 + status=$? + set -e + + if [ "$status" -ne 1 ]; then + fail "malformed region '$value' returned $status" + fi + grep -q "Invalid --compress-region" "$LOG" || \ + fail "malformed region '$value' was not rejected by its parser" +} + +function check_invalid_threads { + local value="$1" + local status + + rm -f "$LOG" + set +e + timeout --foreground --kill-after=1s 5s \ + "${CRIU}" page-server --no-default-config -D . --port 54321 \ + --decompress-threads="$value" > "$LOG" 2>&1 + status=$? + set -e + + if [ "$status" -ne 1 ]; then + fail "malformed decompression thread count '$value' returned $status" + fi + grep -q "Invalid --decompress-threads" "$LOG" || \ + fail "malformed decompression thread count '$value' was not rejected" +} + +function check_valid_threads { + local value="$1" + local port="$2" + local status + + rm -f "$LOG" + set +e + timeout --foreground --kill-after=1s 2s \ + "${CRIU}" page-server --no-default-config -D . --port "$port" \ + --decompress-threads="$value" > "$LOG" 2>&1 + status=$? + set -e + + # The option parses; the server waits for connections until the + # timeout ends it. + if [ "$status" -ne 124 ] && [ "$status" -ne 137 ]; then + fail "valid decompression thread count '$value' returned $status" + fi + if grep -q "Invalid --decompress-threads" "$LOG"; then + fail "valid decompression thread count '$value' was rejected" + fi +} + +check_rejected compress --compress +check_rejected acceleration --compress-acceleration 2 +check_rejected region --compress-region 64K +check_invalid_region 64Kjunk +check_invalid_region 64MgarbageK +check_invalid_region 64KB +check_invalid_region -64K +check_invalid_region 18446744073709551616K +check_invalid_threads "" +# Check both special values: 0 selects automatic concurrency and 1, the default, is serial per request. +check_valid_threads 0 54322 +check_valid_threads 1 54323 + +echo "Test PASSED" diff --git a/test/others/libcriu/run.sh b/test/others/libcriu/run.sh index 0278d08e3..8421c2435 100755 --- a/test/others/libcriu/run.sh +++ b/test/others/libcriu/run.sh @@ -51,6 +51,9 @@ run_test() { fi } +if criu check --feature compress > /dev/null; then + export CRIU_FEATURE_COMPRESS=1 +fi run_test test_sub run_test test_self run_test test_notify diff --git a/test/others/libcriu/test_feature_check.c b/test/others/libcriu/test_feature_check.c index d88e0de23..86e80cbae 100644 --- a/test/others/libcriu/test_feature_check.c +++ b/test/others/libcriu/test_feature_check.c @@ -17,10 +17,12 @@ int main(int argc, char **argv) bool mem_track = 0; bool lazy_pages = 0; bool pidfd_store = 0; + bool mem_compression = 0; struct criu_feature_check features = { .mem_track = true, .lazy_pages = true, .pidfd_store = true, + .mem_compression = true, }; printf("--- Start feature check ---\n"); @@ -39,6 +41,10 @@ int main(int argc, char **argv) if (env) { pidfd_store = true; } + env = getenv("CRIU_FEATURE_COMPRESS"); + if (env) { + mem_compression = true; + } ret = criu_feature_check(&features, sizeof(features) + 1); printf(" `- passing too large structure to libcriu should return -1: %d\n", ret); @@ -60,6 +66,10 @@ int main(int argc, char **argv) printf(" `- pidfd_store: %d - expected : %d\n", features.pidfd_store, pidfd_store); if (features.pidfd_store != pidfd_store) return -1; + printf(" `- mem_compression: %d - expected : %d\n", + features.mem_compression, mem_compression); + if (features.mem_compression != mem_compression) + return -1; return 0; } diff --git a/test/others/libcriu/test_rpc_config.c b/test/others/libcriu/test_rpc_config.c index 529f13637..5cf59fd8a 100644 --- a/test/others/libcriu/test_rpc_config.c +++ b/test/others/libcriu/test_rpc_config.c @@ -67,15 +67,18 @@ static int create_criu_config_file(void) } fprintf(fp, "log-file=%s\n", log_file); + fprintf(fp, "compress-acceleration=2\n"); fflush(fp); fclose(fp); return 0; } -static int check_log_file(void) +static int check_log_file(const char *forbidden) { struct stat st; + char line[512]; + FILE *fp; if (stat(log_file, &st) < 0) { perror("Config file does not exist"); @@ -87,6 +90,20 @@ static int check_log_file(void) return -1; } + fp = fopen(log_file, "r"); + if (!fp) { + perror("Failed to open log file"); + return -1; + } + while (forbidden && fgets(line, sizeof(line), fp)) { + if (strstr(line, forbidden)) { + fprintf(stderr, "Unexpected log message: %s", line); + fclose(fp); + return -1; + } + } + fclose(fp); + unlink(log_file); return 0; } @@ -169,6 +186,10 @@ int main(int argc, char **argv) printf("Setting dump RPC config file: %s\n", conf_file); criu_set_config_file(conf_file); criu_set_log_file("dump.log"); + if (criu_set_compress(CRIU_COMPRESS_OFF)) { + fprintf(stderr, "Failed to disable compression\n"); + goto cleanup; + } ret = criu_dump(); if (ret < 0) { @@ -181,13 +202,14 @@ int main(int argc, char **argv) printf(" `- Dump succeeded\n"); waitpid(pid, NULL, 0); - if (check_log_file()) { + if (check_log_file("compression of memory pages is enabled")) { printf("Error: log file not overwritten by RPC config file\n"); goto cleanup; } printf("--- Restore loop ---\n"); criu_init_opts(); + criu_set_service_binary(argv[1]); criu_set_images_dir_fd(img_fd); criu_set_log_level(CRIU_LOG_DEBUG); @@ -195,6 +217,10 @@ int main(int argc, char **argv) printf("Setting restore RPC config file: %s\n", conf_file); criu_set_config_file(conf_file); criu_set_log_file("restore.log"); + if (criu_set_compress(CRIU_COMPRESS_OFF)) { + fprintf(stderr, "Failed to disable compression\n"); + goto cleanup; + } pid = criu_restore_child(); if (pid <= 0) { @@ -206,7 +232,7 @@ int main(int argc, char **argv) printf(" `- Restore returned pid %d\n", pid); kill(pid, SIGUSR1); - if (check_log_file()) { + if (check_log_file("compression of memory pages is enabled")) { printf("Error: log file not overwritten by RPC config file\n"); goto cleanup; }