crtools/rpc/libcriu: Expose page compression

The compression paths have no supported user or RPC controls, and
clients cannot discover whether CRIU was built with LZ4.

Expose per-page and region compression, LZ4 acceleration, and worker
concurrency for LZ4 decoding and eligible large zero fills through the
CLI, configuration files, RPC, and libcriu. Add compression to the
feature-check API and document each interface.

Define --decompress-threads=0 as automatic worker concurrency and one as
the serial default with zero-fill workers disabled. Values above one
bound aggregate worker concurrency. Automatic mode derives a useful
width from CPU affinity and the work in each batch. Reduce explicit
requests above the available CPU count with a warning.

Document the independent two-slot encoded-input budget. A local restore
overlaps its next read only when it can reserve the second slot without
waiting; otherwise it keeps synchronous I/O.

Use the compression mode as the source of truth and validate every
related value. An explicit RPC OFF clears ambient compression settings
and rejects conflicting fields. Reject combinations unsupported by page
server, stream, TLS, and image-format paths, and cover option and RPC
precedence in the tests.

Assisted-by: Codex:GPT-5
Assisted-by: Claude:claude-fable-5
Signed-off-by: Radostin Stoyanov <rstoyanov@fedoraproject.org>
This commit is contained in:
Radostin Stoyanov 2026-07-11 12:13:14 +01:00
parent 85ac63b772
commit 3441f66b24
15 changed files with 612 additions and 4 deletions

View file

@ -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.

View file

@ -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

View file

@ -1,6 +1,8 @@
#include <ctype.h>
#include <errno.h>
#include <getopt.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -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)

View file

@ -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 },
};

View file

@ -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 */

View file

@ -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"

View file

@ -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;
}
/*

View file

@ -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;

View file

@ -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);

View file

@ -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

View file

@ -0,0 +1,3 @@
run:
./run.sh
.PHONY: run

View file

@ -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"

View file

@ -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

View file

@ -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;
}

View file

@ -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;
}