Skip to content

Commit ef8a9f1

Browse files
committed
add systemd-clone tool
1 parent 5b193a8 commit ef8a9f1

5 files changed

Lines changed: 359 additions & 0 deletions

File tree

meson.build

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ conf.set_quoted('SYSTEMD_USERWORK_PATH', libexecdir / 'syst
291291
conf.set_quoted('SYSTEMD_MOUNTWORK_PATH', libexecdir / 'systemd-mountwork')
292292
conf.set_quoted('SYSTEMD_NSRESOURCEWORK_PATH', libexecdir / 'systemd-nsresourcework')
293293
conf.set_quoted('SYSTEMD_VERITYSETUP_PATH', libexecdir / 'systemd-veritysetup')
294+
conf.set_quoted('SYSTEMD_CLONE_PATH', bindir / 'systemd-clone')
294295
conf.set_quoted('SYSTEM_CONFIG_UNIT_DIR', pkgsysconfdir / 'system')
295296
conf.set_quoted('SYSTEM_DATA_UNIT_DIR', systemunitdir)
296297
conf.set_quoted('SYSTEM_ENV_GENERATOR_DIR', systemenvgeneratordir)
@@ -2351,6 +2352,7 @@ subdir('src/debug-generator')
23512352
subdir('src/delta')
23522353
subdir('src/detect-virt')
23532354
subdir('src/dissect')
2355+
subdir('src/clone')
23542356
subdir('src/environment-d-generator')
23552357
subdir('src/escape')
23562358
subdir('src/factory-reset')

src/clone/clone-ioctl.c

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2+
3+
#include <fcntl.h>
4+
#include <linux/dm-ioctl.h>
5+
#include <sys/ioctl.h>
6+
#include <sys/stat.h>
7+
8+
#include "clone-ioctl.h"
9+
#include "device-private.h"
10+
#include "fd-util.h"
11+
#include "log.h"
12+
#include "memory-util.h" /* memzero() */
13+
#include "sd-device.h"
14+
#include "stdio-util.h" /* xsprintf() */
15+
#include "string-util.h"
16+
17+
static int get_size(const char *dev_path, uint64_t *ret_size) {
18+
_cleanup_(sd_device_unrefp) sd_device *dev = NULL;
19+
uint64_t size;
20+
int r;
21+
22+
assert(dev_path);
23+
assert(ret_size);
24+
25+
r = sd_device_new_from_devname(&dev, dev_path);
26+
if (r < 0)
27+
return log_error_errno(r, "Failed to create device from '%s': %m", dev_path);
28+
29+
r = device_get_sysattr_u64(dev, "size", &size);
30+
if (r < 0)
31+
return log_error_errno(r, "Failed to get device size for '%s': %m", dev_path);
32+
33+
/* sysfs 'size' is in 512-byte sectors */
34+
*ret_size = size * 512;
35+
return 0;
36+
}
37+
38+
static int dm_ioctl_run(const char *name, uint32_t cmd, struct dm_ioctl *data, size_t data_size) {
39+
_cleanup_close_ int fd = -EBADF;
40+
struct dm_ioctl *dm = data;
41+
42+
assert(name);
43+
assert(data);
44+
assert(data_size >= sizeof(struct dm_ioctl));
45+
46+
dm->version[0] = DM_VERSION_MAJOR;
47+
dm->version[1] = DM_VERSION_MINOR;
48+
dm->version[2] = DM_VERSION_PATCHLEVEL;
49+
dm->data_size = data_size;
50+
51+
assert(strlen(name) < sizeof_field(struct dm_ioctl, name));
52+
strncpy_exact(dm->name, name, sizeof(dm->name));
53+
54+
fd = open("/dev/mapper/control", O_RDWR|O_CLOEXEC);
55+
if (fd < 0)
56+
return log_error_errno(errno, "Failed to open /dev/mapper/control: %m");
57+
58+
if (ioctl(fd, cmd, dm) < 0)
59+
return log_error_errno(errno, "DM ioctl failed: %m");
60+
61+
return 0;
62+
}
63+
64+
static int dm_clone_create(const char *name) {
65+
assert(name);
66+
67+
struct dm_ioctl dm = {};
68+
return dm_ioctl_run(name, DM_DEV_CREATE, &dm, sizeof(struct dm_ioctl));
69+
}
70+
71+
static int dm_clone_load_table(const char *name, uint64_t size_sectors, const char *target_params) {
72+
char *params_buf;
73+
size_t params_len, dm_size;
74+
struct dm_ioctl *dm;
75+
struct dm_target_spec *tgt;
76+
77+
assert(name);
78+
assert(target_params);
79+
80+
params_len = strlen(target_params) + 1;
81+
dm_size = sizeof(struct dm_ioctl) + sizeof(struct dm_target_spec) + params_len;
82+
dm = alloca0(dm_size);
83+
84+
dm->data_start = sizeof(struct dm_ioctl);
85+
dm->target_count = 1;
86+
87+
tgt = (struct dm_target_spec *) ((uint8_t *) dm + dm->data_start);
88+
tgt->sector_start = 0;
89+
tgt->length = size_sectors;
90+
strncpy(tgt->target_type, "clone", sizeof(tgt->target_type));
91+
tgt->next = 0;
92+
93+
params_buf = (char *) tgt + sizeof(struct dm_target_spec);
94+
strcpy(params_buf, target_params);
95+
tgt->status = 0;
96+
97+
return dm_ioctl_run(name, DM_TABLE_LOAD, dm, dm_size);
98+
}
99+
100+
static int dm_clone_activate(const char *name) {
101+
assert(name);
102+
103+
struct dm_ioctl dm = {};
104+
105+
return dm_ioctl_run(name, DM_DEV_SUSPEND, &dm, sizeof(struct dm_ioctl));
106+
}
107+
108+
int dm_clone_create_device(
109+
const char *name,
110+
const char *source_dev,
111+
const char *dest_dev,
112+
const char *metadata_dev) {
113+
114+
uint64_t src_dev_size_sectors, src_dev_size;
115+
char target_params[256];
116+
int r;
117+
118+
assert(name);
119+
assert(source_dev);
120+
assert(dest_dev);
121+
assert(metadata_dev);
122+
123+
r = get_size(source_dev, &src_dev_size);
124+
if (r < 0)
125+
return r;
126+
127+
src_dev_size_sectors = src_dev_size / 512;
128+
129+
/* dm-clone target params: <metadata_dev> <dest_dev> <source_dev> <region_size> <hydration_threshold> [options]
130+
* 8 = region size in sectors (4KB regions with 512-byte sectors)
131+
* 1 = hydration threshold (regions to hydrate per batch)
132+
* no_hydration = don't start automatic background hydration */
133+
xsprintf(target_params, "%s %s %s 8 1 no_hydration", metadata_dev, dest_dev, source_dev);
134+
135+
r = dm_clone_create(name);
136+
if (r < 0)
137+
return r;
138+
139+
r = dm_clone_load_table(name, src_dev_size_sectors, target_params);
140+
if (r < 0)
141+
return r;
142+
143+
r = dm_clone_activate(name);
144+
if (r < 0)
145+
return r;
146+
147+
return 0;
148+
}
149+
150+
int dm_clone_send_message(const char *name, const char *message) {
151+
struct dm_ioctl *dm;
152+
struct dm_target_msg *msg;
153+
size_t dm_size, msg_len;
154+
155+
assert(name);
156+
assert(message);
157+
158+
msg_len = strlen(message) + 1;
159+
dm_size = sizeof(struct dm_ioctl) + sizeof(struct dm_target_msg) + msg_len;
160+
dm = alloca0(dm_size);
161+
162+
dm->data_start = sizeof(struct dm_ioctl);
163+
164+
msg = (struct dm_target_msg *) ((char *) dm + dm->data_start);
165+
strcpy(msg->message, message);
166+
167+
return dm_ioctl_run(name, DM_TARGET_MSG, dm, dm_size);
168+
}

src/clone/clone-ioctl.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2+
#pragma once
3+
4+
#include <inttypes.h>
5+
6+
int dm_clone_create_device(
7+
const char *name,
8+
const char *source_dev,
9+
const char *dest_dev,
10+
const char *metadata_dev);
11+
12+
int dm_clone_send_message(const char *name, const char *message);
13+

src/clone/clone.c

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2+
#include <getopt.h>
3+
#include <stdio.h>
4+
#include <sys/stat.h>
5+
#include <unistd.h> /* access */
6+
7+
#include "alloc-util.h"
8+
#include "argv-util.h"
9+
#include "build.h"
10+
#include "clone-ioctl.h"
11+
#include "log.h"
12+
#include "main-func.h"
13+
#include "pretty-print.h"
14+
#include "verbs.h"
15+
#include "path-util.h" /* path_join */
16+
#include "time-util.h" /* USEC_PER_SEC */
17+
#include "udev-util.h" /* device_wait_for_devlink */
18+
19+
/* dm-clone device creation workflow:
20+
* 1. Create the dm-clone device
21+
* 2. Enable background hydration
22+
* 3. (Optional) Replace with linear mapping to finalize */
23+
static int clone_device(const char *clone_name, const char *source_dev, const char *dest_dev,
24+
const char *metadata_dev) {
25+
26+
_cleanup_free_ char *clone_dev_path = NULL;
27+
int r;
28+
29+
assert(clone_name);
30+
assert(source_dev);
31+
assert(dest_dev);
32+
assert(metadata_dev);
33+
34+
/* create clone device path to check if clone device already exists */
35+
clone_dev_path = path_join("/dev/mapper", clone_name);
36+
if (!clone_dev_path)
37+
return log_oom();
38+
39+
if (access(clone_dev_path, F_OK) >= 0)
40+
return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Device '%s' already exists.", clone_dev_path);
41+
42+
r = dm_clone_create_device(clone_name, source_dev, dest_dev, metadata_dev);
43+
if (r < 0)
44+
return log_error_errno(r, "Failed to create dm-clone device: %m");
45+
46+
/* Wait for udev to create /dev/mapper/<name> */
47+
r = device_wait_for_devlink(clone_dev_path, "block", 10 * USEC_PER_SEC, NULL);
48+
if (r < 0)
49+
return log_error_errno(r, "Failed to wait for device %s: %m", clone_dev_path);
50+
51+
r = dm_clone_send_message(clone_name, "enable_hydration");
52+
if (r < 0)
53+
return log_error_errno(r, "Failed to send dm message: %m");
54+
55+
return 0;
56+
}
57+
58+
/* Arguments: systemd-clone add NAME SOURCE-DEVICE DST_DEVICE META-DEVICE [OPTIONS] */
59+
static int verb_add(int argc, char *argv[], void *userdata) {
60+
int r;
61+
62+
assert(argc >= 5 && argc <= 6);
63+
64+
const char *name = ASSERT_PTR(argv[1]),
65+
*src_dev = ASSERT_PTR(argv[2]),
66+
*dst_dev = ASSERT_PTR(argv[3]),
67+
*meta_dev = ASSERT_PTR(argv[4]);
68+
69+
log_debug("%s %s %s %s %s opts=%s ", __func__,
70+
name, src_dev, dst_dev, meta_dev, "");
71+
72+
r = clone_device(name, src_dev, dst_dev, meta_dev);
73+
if (r < 0)
74+
return r;
75+
76+
return 0;
77+
}
78+
79+
static int verb_remove(int argc, char *argv[], void *userdata) {
80+
81+
return 0;
82+
}
83+
84+
static int help(void) {
85+
_cleanup_free_ char *link = NULL;
86+
int r;
87+
88+
r = terminal_urlify_man("systemd-clone", "8", &link);
89+
if (r < 0)
90+
return log_oom();
91+
92+
printf("%1$s add NAME SOURCE-DEVICE DST-DEVICE META-DEVICE [OPTIONS] \n"
93+
"%1$s remove VOLUME\n\n"
94+
"%2$sAdd or remove a dm clone device.%3$s\n\n"
95+
" -h --help Show this help\n"
96+
" --version Show package version\n"
97+
"\nSee the %4$s for details.\n",
98+
program_invocation_short_name,
99+
ansi_highlight(),
100+
ansi_normal(),
101+
link);
102+
103+
return 0;
104+
}
105+
106+
static int parse_argv(int argc, char *argv[]) {
107+
enum {
108+
ARG_VERSION = 0x100,
109+
};
110+
111+
static const struct option options[] = {
112+
{ "help", no_argument, NULL, 'h' },
113+
{ "version", no_argument, NULL, ARG_VERSION },
114+
{}
115+
};
116+
117+
int c;
118+
119+
assert(argc >= 0);
120+
assert(argv);
121+
122+
if (argv_looks_like_help(argc, argv))
123+
return help();
124+
125+
while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
126+
switch (c) {
127+
128+
case 'h':
129+
return help();
130+
131+
case ARG_VERSION:
132+
return version();
133+
134+
case '?':
135+
return -EINVAL;
136+
137+
default:
138+
assert_not_reached();
139+
}
140+
141+
return 1;
142+
}
143+
144+
static int run(int argc, char *argv[]) {
145+
int r;
146+
147+
log_setup();
148+
umask(0022);
149+
150+
r = parse_argv(argc, argv);
151+
if (r <= 0)
152+
return r;
153+
154+
static const Verb verbs[] = {
155+
{ "add", 5, 6, 0, verb_add },
156+
{ "remove", 1, 1, 0, verb_remove },
157+
{}
158+
};
159+
return dispatch_verb(argc, argv, verbs, NULL);
160+
}
161+
162+
DEFINE_MAIN_FUNCTION(run);

src/clone/meson.build

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# SPDX-License-Identifier: LGPL-2.1-or-later
2+
3+
systemd_clone_sources = files(
4+
'clone-ioctl.c',
5+
'clone.c',
6+
)
7+
8+
executables += [
9+
executable_template + {
10+
'name' : 'systemd-clone',
11+
'public' : true,
12+
'sources' : systemd_clone_sources,
13+
},
14+
]

0 commit comments

Comments
 (0)