mirror of
https://github.com/systemd/systemd.git
synced 2024-12-22 17:35:35 +03:00
repart: add --image= switch
This is similar to the --image= switch in the other tools, like systemd-sysusers or systemd-tmpfiles, i.e. it apply the configuration from the image to the image. This is particularly useful for downloading minimized GPT image, and then extending it to the desired size via: # systemd-repart --image=foo.image --size=5G
This commit is contained in:
parent
8e5f3cecdf
commit
252d626711
@ -486,7 +486,11 @@
|
||||
<para>The copy operation is executed before the file system is registered in the partition table,
|
||||
thus ensuring that a file system populated this way only ever exists fully initialized.</para>
|
||||
|
||||
<para>This option cannot be combined with <varname>CopyBlocks=</varname>.</para></listitem>
|
||||
<para>This option cannot be combined with <varname>CopyBlocks=</varname>.</para>
|
||||
|
||||
<para>When <command>systemd-repart</command> is invoked with the <option>--image=</option> or
|
||||
<option>--root=</option> command line switches the source paths specified are taken relative to the
|
||||
specified root directory or disk image root.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
|
@ -40,16 +40,17 @@
|
||||
<citerefentry><refentrytitle>repart.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
|
||||
</para>
|
||||
|
||||
<para>If invoked with no arguments, it operates on the block device backing the root file system partition
|
||||
of the OS, thus growing and adding partitions of the booted OS image itself. When called in the initial
|
||||
RAM disk it operates on the block device backing <filename>/sysroot/</filename> instead, i.e. on the
|
||||
block device the system will soon transition into. The <filename>systemd-repart.service</filename>
|
||||
service is generally run at boot in the initial RAM disk, in order to augment the partition table of the
|
||||
OS before its partitions are mounted. <command>systemd-repart</command> (mostly) operates in a purely
|
||||
incremental mode: it only grows existing and adds new partitions; it does not shrink, delete or move
|
||||
existing partitions. The service is intended to be run on every boot, but when it detects that the
|
||||
partition table already matches the installed <filename>repart.d/*.conf</filename> configuration
|
||||
files, it executes no operation.</para>
|
||||
<para>If invoked with no arguments, it operates on the block device backing the root file system
|
||||
partition of the running OS, thus growing and adding partitions of the booted OS image itself. If
|
||||
<varname>--image=</varname> is used it will operate on the specified image file. When called in the
|
||||
<literal>initrd</literal> it operates on the block device backing <filename>/sysroot/</filename> instead,
|
||||
i.e. on the block device the system will soon transition into. The
|
||||
<filename>systemd-repart.service</filename> service is generally run at boot in the initial RAM disk, in
|
||||
order to augment the partition table of the OS before its partitions are
|
||||
mounted. <command>systemd-repart</command> (mostly) operates in a purely incremental mode: it only grows
|
||||
existing and adds new partitions; it does not shrink, delete or move existing partitions. The service is
|
||||
intended to be run on every boot, but when it detects that the partition table already matches the
|
||||
installed <filename>repart.d/*.conf</filename> configuration files, it executes no operation.</para>
|
||||
|
||||
<para><command>systemd-repart</command> is intended to be used when deploying OS images, to automatically
|
||||
adjust them to the system they are running on, during first boot. This way the deployed image can be
|
||||
@ -251,13 +252,21 @@
|
||||
<term><option>--root=</option></term>
|
||||
|
||||
<listitem><para>Takes a path to a directory to use as root file system when searching for
|
||||
<filename>repart.d/*.conf</filename> files and for the machine ID file to use as seed. By default
|
||||
when invoked on the regular system this defaults to the host's root file system
|
||||
<filename>repart.d/*.conf</filename> files, for the machine ID file to use as seed and for the
|
||||
<varname>CopyFiles=</varname> and <varname>CopyBlocks=</varname> source files and directories. By
|
||||
default when invoked on the regular system this defaults to the host's root file system
|
||||
<filename>/</filename>. If invoked from the initial RAM disk this defaults to
|
||||
<filename>/sysroot/</filename>, so that the tool operates on the configuration and machine ID stored
|
||||
in the root file system later transitioned into itself.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--image=</option></term>
|
||||
|
||||
<listitem><para>Takes a path to a disk image file or device to mount and use in a similar fashion to
|
||||
<option>--root=</option>, see above.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--seed=</option></term>
|
||||
|
||||
|
@ -92,6 +92,7 @@ static enum {
|
||||
static bool arg_dry_run = true;
|
||||
static const char *arg_node = NULL;
|
||||
static char *arg_root = NULL;
|
||||
static char *arg_image = NULL;
|
||||
static char *arg_definitions = NULL;
|
||||
static bool arg_discard = true;
|
||||
static bool arg_can_factory_reset = false;
|
||||
@ -110,6 +111,7 @@ static char *arg_tpm2_device = NULL;
|
||||
static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_definitions, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_key, erase_and_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
|
||||
@ -3483,6 +3485,7 @@ static int help(void) {
|
||||
" them\n"
|
||||
" --can-factory-reset Test whether factory reset is defined\n"
|
||||
" --root=PATH Operate relative to root path\n"
|
||||
" --image=PATH Operate relative to image file\n"
|
||||
" --definitions=DIR Find partition definitions in specified directory\n"
|
||||
" --key-file=PATH Key to use when encrypting partitions\n"
|
||||
" --tpm2-device=PATH Path to TPM2 device node to use\n"
|
||||
@ -3513,6 +3516,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
ARG_FACTORY_RESET,
|
||||
ARG_CAN_FACTORY_RESET,
|
||||
ARG_ROOT,
|
||||
ARG_IMAGE,
|
||||
ARG_SEED,
|
||||
ARG_PRETTY,
|
||||
ARG_DEFINITIONS,
|
||||
@ -3534,6 +3538,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
{ "factory-reset", required_argument, NULL, ARG_FACTORY_RESET },
|
||||
{ "can-factory-reset", no_argument, NULL, ARG_CAN_FACTORY_RESET },
|
||||
{ "root", required_argument, NULL, ARG_ROOT },
|
||||
{ "image", required_argument, NULL, ARG_IMAGE },
|
||||
{ "seed", required_argument, NULL, ARG_SEED },
|
||||
{ "pretty", required_argument, NULL, ARG_PRETTY },
|
||||
{ "definitions", required_argument, NULL, ARG_DEFINITIONS },
|
||||
@ -3613,7 +3618,13 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
break;
|
||||
|
||||
case ARG_ROOT:
|
||||
r = parse_path_argument(optarg, false, &arg_root);
|
||||
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_IMAGE:
|
||||
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
@ -3763,9 +3774,18 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"If --empty=create is specified, --size= must be specified, too.");
|
||||
|
||||
if (arg_image && arg_root)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported.");
|
||||
else if (!arg_image && !arg_root && in_initrd()) {
|
||||
/* Default to operation on /sysroot when invoked in the initrd! */
|
||||
arg_root = strdup("/sysroot");
|
||||
if (!arg_root)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
arg_node = argc > optind ? argv[optind] : NULL;
|
||||
|
||||
if (IN_SET(arg_empty, EMPTY_FORCE, EMPTY_REQUIRE, EMPTY_CREATE) && !arg_node)
|
||||
if (IN_SET(arg_empty, EMPTY_FORCE, EMPTY_REQUIRE, EMPTY_CREATE) && !arg_node && !arg_image)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"A path to a device node or loopback file must be specified when --empty=force, --empty=require or --empty=create are used.");
|
||||
|
||||
@ -3838,39 +3858,44 @@ static int remove_efi_variable_factory_reset(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int acquire_root_devno(const char *p, int mode, char **ret, int *ret_fd) {
|
||||
static int acquire_root_devno(
|
||||
const char *p,
|
||||
const char *root,
|
||||
int mode,
|
||||
char **ret,
|
||||
int *ret_fd) {
|
||||
|
||||
_cleanup_free_ char *found_path = NULL;
|
||||
dev_t devno, fd_devno = MODE_INVALID;
|
||||
_cleanup_close_ int fd = -1;
|
||||
struct stat st;
|
||||
dev_t devno, fd_devno = MODE_INVALID;
|
||||
int r;
|
||||
|
||||
assert(p);
|
||||
assert(ret);
|
||||
assert(ret_fd);
|
||||
|
||||
fd = open(p, mode);
|
||||
fd = chase_symlinks_and_open(p, root, CHASE_PREFIX_ROOT, mode, &found_path);
|
||||
if (fd < 0)
|
||||
return -errno;
|
||||
return fd;
|
||||
|
||||
if (fstat(fd, &st) < 0)
|
||||
return -errno;
|
||||
|
||||
if (S_ISREG(st.st_mode)) {
|
||||
char *s;
|
||||
|
||||
s = strdup(p);
|
||||
if (!s)
|
||||
return log_oom();
|
||||
|
||||
*ret = s;
|
||||
*ret = TAKE_PTR(found_path);
|
||||
*ret_fd = TAKE_FD(fd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (S_ISBLK(st.st_mode))
|
||||
if (S_ISBLK(st.st_mode)) {
|
||||
/* Refuse referencing explicit block devices if a root dir is specified, after all we should
|
||||
* be able to leave the image the root path constraints us to. */
|
||||
if (root)
|
||||
return -EPERM;
|
||||
|
||||
fd_devno = devno = st.st_rdev;
|
||||
else if (S_ISDIR(st.st_mode)) {
|
||||
} else if (S_ISDIR(st.st_mode)) {
|
||||
|
||||
devno = st.st_dev;
|
||||
if (major(devno) == 0) {
|
||||
@ -3930,7 +3955,9 @@ static int find_root(char **ret, int *ret_fd) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = acquire_root_devno(arg_node, O_RDONLY|O_CLOEXEC, ret, ret_fd);
|
||||
/* Note that we don't specify a root argument here: if the user explicitly configured a node
|
||||
* we'll take it relative to the host, not the image */
|
||||
r = acquire_root_devno(arg_node, NULL, O_RDONLY|O_CLOEXEC, ret, ret_fd);
|
||||
if (r == -EUCLEAN)
|
||||
return btrfs_log_dev_root(LOG_ERR, r, arg_node);
|
||||
if (r < 0)
|
||||
@ -3958,7 +3985,7 @@ static int find_root(char **ret, int *ret_fd) {
|
||||
} else
|
||||
p = t;
|
||||
|
||||
r = acquire_root_devno(p, O_RDONLY|O_DIRECTORY|O_CLOEXEC, ret, ret_fd);
|
||||
r = acquire_root_devno(p, arg_root, O_RDONLY|O_DIRECTORY|O_CLOEXEC, ret, ret_fd);
|
||||
if (r < 0) {
|
||||
if (r == -EUCLEAN)
|
||||
return btrfs_log_dev_root(LOG_ERR, r, p);
|
||||
@ -4005,9 +4032,15 @@ static int resize_pt(int fd) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int resize_backing_fd(const char *node, int *fd) {
|
||||
static int resize_backing_fd(
|
||||
const char *node, /* The primary way we access the disk image to operate on */
|
||||
int *fd, /* An O_RDONLY fd referring to that inode */
|
||||
const char *backing_file, /* If the above refers to a loopback device, the backing regular file for that, which we can grow */
|
||||
LoopDevice *loop_device) {
|
||||
|
||||
char buf1[FORMAT_BYTES_MAX], buf2[FORMAT_BYTES_MAX];
|
||||
_cleanup_close_ int writable_fd = -1;
|
||||
uint64_t current_size;
|
||||
struct stat st;
|
||||
int r;
|
||||
|
||||
@ -4028,25 +4061,64 @@ static int resize_backing_fd(const char *node, int *fd) {
|
||||
if (fstat(*fd, &st) < 0)
|
||||
return log_error_errno(errno, "Failed to stat '%s': %m", node);
|
||||
|
||||
r = stat_verify_regular(&st);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Specified path '%s' is not a regular file, cannot resize: %m", node);
|
||||
if (S_ISBLK(st.st_mode)) {
|
||||
if (!backing_file)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADF), "Cannot resize block device '%s'.", node);
|
||||
|
||||
assert_se(format_bytes(buf1, sizeof(buf1), st.st_size));
|
||||
assert(loop_device);
|
||||
|
||||
if (ioctl(*fd, BLKGETSIZE64, ¤t_size) < 0)
|
||||
return log_error_errno(errno, "Failed to determine size of block device %s: %m", node);
|
||||
} else {
|
||||
r = stat_verify_regular(&st);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Specified path '%s' is not a regular file or loopback block device, cannot resize: %m", node);
|
||||
|
||||
assert(!backing_file);
|
||||
assert(!loop_device);
|
||||
current_size = st.st_size;
|
||||
}
|
||||
|
||||
assert_se(format_bytes(buf1, sizeof(buf1), current_size));
|
||||
assert_se(format_bytes(buf2, sizeof(buf2), arg_size));
|
||||
|
||||
if ((uint64_t) st.st_size >= arg_size) {
|
||||
if (current_size >= arg_size) {
|
||||
log_info("File '%s' already is of requested size or larger, not growing. (%s >= %s)", node, buf1, buf2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The file descriptor is read-only. In order to grow the file we need to have a writable fd. We
|
||||
* reopen the file for that temporarily. We keep the writable fd only open for this operation though,
|
||||
* as fdisk can't accept it anyway. */
|
||||
if (S_ISBLK(st.st_mode)) {
|
||||
assert(backing_file);
|
||||
|
||||
writable_fd = fd_reopen(*fd, O_WRONLY|O_CLOEXEC);
|
||||
if (writable_fd < 0)
|
||||
return log_error_errno(writable_fd, "Failed to reopen backing file '%s' writable: %m", node);
|
||||
/* This is a loopback device. We can't really grow those directly, but we can grow the
|
||||
* backing file, hence let's do that. */
|
||||
|
||||
writable_fd = open(backing_file, O_WRONLY|O_CLOEXEC|O_NONBLOCK);
|
||||
if (writable_fd < 0)
|
||||
return log_error_errno(errno, "Failed to open backing file '%s': %m", backing_file);
|
||||
|
||||
if (fstat(writable_fd, &st) < 0)
|
||||
return log_error_errno(errno, "Failed to stat() backing file '%s': %m", backing_file);
|
||||
|
||||
r = stat_verify_regular(&st);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Backing file '%s' of block device is not a regular file: %m", backing_file);
|
||||
|
||||
if ((uint64_t) st.st_size != current_size)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Size of backing file '%s' of loopback block device '%s' don't match, refusing.", node, backing_file);
|
||||
} else {
|
||||
assert(S_ISREG(st.st_mode));
|
||||
assert(!backing_file);
|
||||
|
||||
/* The file descriptor is read-only. In order to grow the file we need to have a writable fd. We
|
||||
* reopen the file for that temporarily. We keep the writable fd only open for this operation though,
|
||||
* as fdisk can't accept it anyway. */
|
||||
|
||||
writable_fd = fd_reopen(*fd, O_WRONLY|O_CLOEXEC);
|
||||
if (writable_fd < 0)
|
||||
return log_error_errno(writable_fd, "Failed to reopen backing file '%s' writable: %m", node);
|
||||
}
|
||||
|
||||
if (!arg_discard) {
|
||||
if (fallocate(writable_fd, 0, 0, arg_size) < 0) {
|
||||
@ -4057,16 +4129,12 @@ static int resize_backing_fd(const char *node, int *fd) {
|
||||
/* Fallback to truncation, if fallocate() is not supported. */
|
||||
log_debug("Backing file system does not support fallocate(), falling back to ftruncate().");
|
||||
} else {
|
||||
r = resize_pt(writable_fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (st.st_size == 0) /* Likely regular file just created by us */
|
||||
if (current_size == 0) /* Likely regular file just created by us */
|
||||
log_info("Allocated %s for '%s'.", buf2, node);
|
||||
else
|
||||
log_info("File '%s' grown from %s to %s by allocation.", node, buf1, buf2);
|
||||
|
||||
return 1;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
@ -4074,14 +4142,21 @@ static int resize_backing_fd(const char *node, int *fd) {
|
||||
return log_error_errno(errno, "Failed to grow '%s' from %s to %s by truncation: %m",
|
||||
node, buf1, buf2);
|
||||
|
||||
if (current_size == 0) /* Likely regular file just created by us */
|
||||
log_info("Sized '%s' to %s.", node, buf2);
|
||||
else
|
||||
log_info("File '%s' grown from %s to %s by truncation.", node, buf1, buf2);
|
||||
|
||||
done:
|
||||
r = resize_pt(writable_fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (st.st_size == 0) /* Likely regular file just created by us */
|
||||
log_info("Sized '%s' to %s.", node, buf2);
|
||||
else
|
||||
log_info("File '%s' grown from %s to %s by truncation.", node, buf1, buf2);
|
||||
if (loop_device) {
|
||||
r = loop_device_refresh_size(loop_device, UINT64_MAX, arg_size);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to update loop device size: %m");
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
@ -4116,23 +4191,19 @@ static int determine_auto_size(Context *c) {
|
||||
}
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
|
||||
_cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL;
|
||||
_cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
|
||||
_cleanup_(context_freep) Context* context = NULL;
|
||||
_cleanup_free_ char *node = NULL;
|
||||
_cleanup_close_ int backing_fd = -1;
|
||||
bool from_scratch;
|
||||
bool from_scratch, node_is_our_loop = false;
|
||||
int r;
|
||||
|
||||
log_show_color(true);
|
||||
log_parse_environment();
|
||||
log_open();
|
||||
|
||||
if (in_initrd()) {
|
||||
/* Default to operation on /sysroot when invoked in the initrd! */
|
||||
arg_root = strdup("/sysroot");
|
||||
if (!arg_root)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
r = parse_argv(argc, argv);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
@ -4145,6 +4216,40 @@ static int run(int argc, char *argv[]) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (arg_image) {
|
||||
assert(!arg_root);
|
||||
|
||||
/* Mount this strictly read-only: we shall modify the partition table, not the file
|
||||
* systems */
|
||||
r = mount_image_privately_interactively(
|
||||
arg_image,
|
||||
DISSECT_IMAGE_MOUNT_READ_ONLY |
|
||||
(arg_node ? DISSECT_IMAGE_DEVICE_READ_ONLY : 0) | /* If a different node to make changes to is specified let's open the device in read-only mode) */
|
||||
DISSECT_IMAGE_GPT_ONLY |
|
||||
DISSECT_IMAGE_RELAX_VAR_CHECK |
|
||||
DISSECT_IMAGE_USR_NO_ROOT |
|
||||
DISSECT_IMAGE_REQUIRE_ROOT,
|
||||
&mounted_dir,
|
||||
&loop_device,
|
||||
&decrypted_image);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
arg_root = strdup(mounted_dir);
|
||||
if (!arg_root)
|
||||
return log_oom();
|
||||
|
||||
if (!arg_node) {
|
||||
arg_node = strdup(loop_device->node);
|
||||
if (!arg_node)
|
||||
return log_oom();
|
||||
|
||||
/* Remember that the the device we are about to manipulate is actually the one we
|
||||
* allocated here, and thus to increase its backing file we know what to do */
|
||||
node_is_our_loop = true;
|
||||
}
|
||||
}
|
||||
|
||||
context = context_new(arg_seed);
|
||||
if (!context)
|
||||
return log_oom();
|
||||
@ -4163,7 +4268,11 @@ static int run(int argc, char *argv[]) {
|
||||
return r;
|
||||
|
||||
if (arg_size != UINT64_MAX) {
|
||||
r = resize_backing_fd(node, &backing_fd);
|
||||
r = resize_backing_fd(
|
||||
node,
|
||||
&backing_fd,
|
||||
node_is_our_loop ? arg_image : NULL,
|
||||
node_is_our_loop ? loop_device : NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
@ -4225,7 +4334,11 @@ static int run(int argc, char *argv[]) {
|
||||
context_unload_partition_table(context);
|
||||
|
||||
assert_se(arg_size != UINT64_MAX);
|
||||
r = resize_backing_fd(node, &backing_fd);
|
||||
r = resize_backing_fd(
|
||||
node,
|
||||
&backing_fd,
|
||||
node_is_our_loop ? arg_image : NULL,
|
||||
node_is_our_loop ? loop_device : NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user