summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--man/machinectl.xml15
-rw-r--r--src/machine/machinectl.c170
-rwxr-xr-xtest/TEST-74-AUX-UTILS/test.sh4
-rwxr-xr-xtest/units/testsuite-74.machinectl.sh43
4 files changed, 228 insertions, 4 deletions
diff --git a/man/machinectl.xml b/man/machinectl.xml
index 2ecbe0f0ab..1585a0896d 100644
--- a/man/machinectl.xml
+++ b/man/machinectl.xml
@@ -377,6 +377,21 @@
</varlistentry>
<varlistentry>
+ <term><command>edit</command> <replaceable>NAME|FILE</replaceable></term>
+
+ <listitem><para>Edit the settings file of the specified machines. For the format of the settings file, refer to <citerefentry
+ project='man-pages'><refentrytitle>systemd.nspawn</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+ If an existing settings file of the given machine can't be found, <command>edit</command> automatically
+ create a new settings file from scratch under <filename>/etc/</filename></para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><command>cat</command> <replaceable>NAME|FILE</replaceable></term>
+
+ <listitem><para>Show the settings file of the specified machines.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><command>clone</command> <replaceable>NAME</replaceable> <replaceable>NAME</replaceable></term>
<listitem><para>Clones a container or VM image. The arguments specify the name of the image to clone and the
diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c
index e9956466ce..6a29a32bcd 100644
--- a/src/machine/machinectl.c
+++ b/src/machine/machinectl.c
@@ -27,6 +27,7 @@
#include "cgroup-util.h"
#include "constants.h"
#include "copy.h"
+#include "edit-util.h"
#include "env-util.h"
#include "fd-util.h"
#include "format-table.h"
@@ -1412,6 +1413,163 @@ static int shell_machine(int argc, char *argv[], void *userdata) {
return process_forward(event, &forward, master, 0, machine);
}
+static int normalize_nspawn_filename(const char *name, char **ret_file) {
+ _cleanup_free_ char *file = NULL;
+
+ assert(name);
+ assert(ret_file);
+
+ if (!endswith(name, ".nspawn"))
+ file = strjoin(name, ".nspawn");
+ else
+ file = strdup(name);
+ if (!file)
+ return log_oom();
+
+ if (!filename_is_valid(file))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid settings file name '%s'.", file);
+
+ *ret_file = TAKE_PTR(file);
+ return 0;
+}
+
+static int get_settings_path(const char *name, char **ret_path) {
+ assert(name);
+ assert(ret_path);
+
+ FOREACH_STRING(i, "/etc/systemd/nspawn", "/run/systemd/nspawn", "/var/lib/machines") {
+ _cleanup_free_ char *path = NULL;
+
+ path = path_join(i, name);
+ if (!path)
+ return -ENOMEM;
+
+ if (access(path, F_OK) >= 0) {
+ *ret_path = TAKE_PTR(path);
+ return 0;
+ }
+ }
+
+ return -ENOENT;
+}
+
+static int edit_settings(int argc, char *argv[], void *userdata) {
+ _cleanup_(edit_file_context_done) EditFileContext context = {
+ .remove_parent = false,
+ };
+ int r;
+
+ if (!on_tty())
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit machine settings if not on a tty.");
+
+ if (arg_transport != BUS_TRANSPORT_LOCAL)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Edit is only supported on the host machine.");
+
+ r = mac_selinux_init();
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(name, strv_skip(argv, 1)) {
+ _cleanup_free_ char *file = NULL, *path = NULL;
+
+ if (path_is_absolute(*name)) {
+ if (!path_is_safe(*name))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Invalid settings file path '%s'.",
+ *name);
+
+ r = edit_files_add(&context, *name, NULL, NULL);
+ if (r < 0)
+ return r;
+ continue;
+ }
+
+ r = normalize_nspawn_filename(*name, &file);
+ if (r < 0)
+ return r;
+
+ r = get_settings_path(file, &path);
+ if (r == -ENOENT) {
+ log_debug("No existing settings file for machine '%s' found, creating a new file.", *name);
+
+ path = path_join("/etc/systemd/nspawn", file);
+ if (!path)
+ return log_oom();
+
+ r = edit_files_add(&context, path, NULL, NULL);
+ if (r < 0)
+ return r;
+ continue;
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to get the path of the settings file: %m");
+
+ if (path_startswith(path, "/var/lib/machines")) {
+ _cleanup_free_ char *new_path = NULL;
+
+ new_path = path_join("/etc/systemd/nspawn", file);
+ if (!new_path)
+ return log_oom();
+
+ r = edit_files_add(&context, new_path, path, NULL);
+ } else
+ r = edit_files_add(&context, path, NULL, NULL);
+ if (r < 0)
+ return r;
+ }
+
+ return do_edit_files_and_install(&context);
+}
+
+static int cat_settings(int argc, char *argv[], void *userdata) {
+ int r = 0;
+
+ if (arg_transport != BUS_TRANSPORT_LOCAL)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Cat is only supported on the host machine.");
+
+ pager_open(arg_pager_flags);
+
+ STRV_FOREACH(name, strv_skip(argv, 1)) {
+ _cleanup_free_ char *file = NULL, *path = NULL;
+ int q;
+
+ if (path_is_absolute(*name)) {
+ if (!path_is_safe(*name))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Invalid settings file path '%s'.",
+ *name);
+
+ q = cat_files(*name, /* dropins = */ NULL, /* flags = */ 0);
+ if (q < 0)
+ return r < 0 ? r : q;
+ continue;
+ }
+
+ q = normalize_nspawn_filename(*name, &file);
+ if (q < 0)
+ return r < 0 ? r : q;
+
+ q = get_settings_path(file, &path);
+ if (q == -ENOENT) {
+ log_error_errno(q, "No settings file found for machine '%s'.", *name);
+ r = r < 0 ? r : q;
+ continue;
+ }
+ if (q < 0) {
+ log_error_errno(q, "Failed to get the path of the settings file: %m");
+ return r < 0 ? r : q;
+ }
+
+ q = cat_files(path, /* dropins = */ NULL, /* flags = */ 0);
+ if (q < 0)
+ return r < 0 ? r : q;
+ }
+
+ return r;
+}
+
static int remove_image(int argc, char *argv[], void *userdata) {
sd_bus *bus = ASSERT_PTR(userdata);
int r;
@@ -2443,18 +2601,20 @@ static int help(int argc, char *argv[], void *userdata) {
" kill NAME... Send signal to processes of a VM/container\n"
" copy-to NAME PATH [PATH] Copy files from the host to a container\n"
" copy-from NAME PATH [PATH] Copy files from a container to the host\n"
- " bind NAME PATH [PATH] Bind mount a path from the host into a container\n\n"
- "Image Commands:\n"
+ " bind NAME PATH [PATH] Bind mount a path from the host into a container\n"
+ "\nImage Commands:\n"
" list-images Show available container and VM images\n"
" image-status [NAME...] Show image details\n"
" show-image [NAME...] Show properties of image\n"
+ " edit NAME|FILE... Edit settings of one or more VMs/containers\n"
+ " cat NAME|FILE... Show settings of one or more VMs/containers\n"
" clone NAME NAME Clone an image\n"
" rename NAME NAME Rename an image\n"
" read-only NAME [BOOL] Mark or unmark image read-only\n"
" remove NAME... Remove an image\n"
" set-limit [NAME] BYTES Set image or pool size limit (disk quota)\n"
- " clean Remove hidden (or all) images\n\n"
- "Image Transfer Commands:\n"
+ " clean Remove hidden (or all) images\n"
+ "\nImage Transfer Commands:\n"
" pull-tar URL [NAME] Download a TAR container image\n"
" pull-raw URL [NAME] Download a RAW container or VM image\n"
" import-tar FILE [NAME] Import a local TAR container image\n"
@@ -2800,6 +2960,8 @@ static int machinectl_main(int argc, char *argv[], sd_bus *bus) {
{ "login", VERB_ANY, 2, 0, login_machine },
{ "shell", VERB_ANY, VERB_ANY, 0, shell_machine },
{ "bind", 3, 4, 0, bind_mount },
+ { "edit", 2, VERB_ANY, 0, edit_settings },
+ { "cat", 2, VERB_ANY, 0, cat_settings },
{ "copy-to", 3, 4, 0, copy_files },
{ "copy-from", 3, 4, 0, copy_files },
{ "remove", 2, VERB_ANY, 0, remove_image },
diff --git a/test/TEST-74-AUX-UTILS/test.sh b/test/TEST-74-AUX-UTILS/test.sh
index f422c89141..94bb159464 100755
--- a/test/TEST-74-AUX-UTILS/test.sh
+++ b/test/TEST-74-AUX-UTILS/test.sh
@@ -7,4 +7,8 @@ TEST_DESCRIPTION="Tests for auxiliary utilities"
# shellcheck source=test/test-functions
. "${TEST_BASE_DIR:?}/test-functions"
+test_append_files() (
+ image_install script
+)
+
do_test "$@"
diff --git a/test/units/testsuite-74.machinectl.sh b/test/units/testsuite-74.machinectl.sh
new file mode 100755
index 0000000000..4adb1cd637
--- /dev/null
+++ b/test/units/testsuite-74.machinectl.sh
@@ -0,0 +1,43 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2016
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/assert.sh
+. "$(dirname "$0")"/assert.sh
+
+: >/failed
+
+at_exit() {
+ if [[ -v NSPAWN_NAME && -e "/var/lib/machines/$NSPAWN_NAME" ]]; then
+ rm -fvr "/var/lib/machines/$NSPAWN_NAME" "/etc/systemd/nspawn/$NSPAWN_NAME" "new"
+ fi
+
+ return 0
+}
+
+trap at_exit EXIT
+
+export NSPAWN_NAME="machinectl-test-$RANDOM.nspawn"
+cat >"/var/lib/machines/$NSPAWN_NAME" <<\EOF
+[Exec]
+Boot=true
+EOF
+
+EDITOR='true' script -ec 'machinectl edit "$NSPAWN_NAME"' /dev/null
+[ -f "/etc/systemd/nspawn/$NSPAWN_NAME" ]
+cmp "/var/lib/machines/$NSPAWN_NAME" "/etc/systemd/nspawn/$NSPAWN_NAME"
+
+cat >new <<\EOF
+[Exec]
+Boot=false
+EOF
+
+script -ec 'machinectl cat "$PWD/new"' /dev/null
+
+EDITOR='mv new' script -ec 'machinectl edit "$NSPAWN_NAME"' /dev/null
+printf '%s\n' '[Exec]' 'Boot=false' | cmp - "/etc/systemd/nspawn/$NSPAWN_NAME"
+
+touch /testok
+rm /failed