return show_table(table, "machines");
}
-static int verb_list_images(int argc, char *argv[], uintptr_t _data, void *userdata) {
-
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
- _cleanup_(table_unrefp) Table *table = NULL;
- sd_bus *bus = ASSERT_PTR(userdata);
- int r;
-
- pager_open(arg_pager_flags);
-
- r = bus_call_method(bus, bus_machine_mgr, "ListImages", &error, &reply, NULL);
- if (r < 0)
- return log_error_errno(r, "Could not get images: %s", bus_error_message(&error, r));
-
- table = table_new("name", "type", "ro", "usage", "created", "modified");
- if (!table)
- return log_oom();
-
- if (arg_full)
- table_set_width(table, 0);
-
- (void) table_set_align_percent(table, TABLE_HEADER_CELL(3), 100);
-
- r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssbttto)");
- if (r < 0)
- return bus_log_parse_error(r);
-
- for (;;) {
- uint64_t crtime, mtime, size;
- const char *name, *type;
- int ro_int;
-
- r = sd_bus_message_read(reply, "(ssbttto)", &name, &type, &ro_int, &crtime, &mtime, &size, NULL);
- if (r < 0)
- return bus_log_parse_error(r);
- if (r == 0)
- break;
-
- if (name[0] == '.' && !arg_all)
- continue;
-
- r = table_add_many(table,
- TABLE_STRING, name,
- TABLE_STRING, type,
- TABLE_BOOLEAN, ro_int,
- TABLE_SET_COLOR, ro_int ? ansi_highlight_red() : NULL,
- TABLE_SIZE, size,
- TABLE_TIMESTAMP, crtime,
- TABLE_TIMESTAMP, mtime);
- if (r < 0)
- return table_log_add_error(r);
- }
-
- r = sd_bus_message_exit_container(reply);
- if (r < 0)
- return bus_log_parse_error(r);
-
- return show_table(table, "images");
-}
-
static int show_unit_cgroup(
sd_bus *bus,
const char *unit,
}
static int show_machine_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) {
-
static const struct bus_properties_map map[] = {
{ "Name", "s", NULL, offsetof(MachineStatusInfo, name) },
{ "Class", "s", NULL, offsetof(MachineStatusInfo, class) },
return r;
}
-static int print_image_hostname(sd_bus *bus, const char *name) {
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
- const char *hn;
+static int make_service_name(const char *name, char **ret) {
int r;
- r = bus_call_method(bus, bus_machine_mgr, "GetImageHostname", NULL, &reply, "s", name);
- if (r < 0)
- return r;
+ assert(name);
+ assert(ret);
+ assert(arg_runner >= 0 && arg_runner < _RUNNER_MAX);
- r = sd_bus_message_read(reply, "s", &hn);
- if (r < 0)
- return r;
+ if (!hostname_is_valid(name, 0))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Invalid machine name %s.", name);
- if (!isempty(hn))
- printf("\tHostname: %s\n", hn);
+ r = unit_name_build(machine_runner_unit_prefix_table[arg_runner], name, ".service", ret);
+ if (r < 0)
+ return log_error_errno(r, "Failed to build unit name: %m");
return 0;
}
-static int print_image_machine_id(sd_bus *bus, const char *name) {
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
- sd_id128_t id;
+static int image_exists(sd_bus *bus, const char *name) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
- r = bus_call_method(bus, bus_machine_mgr, "GetImageMachineID", NULL, &reply, "s", name);
- if (r < 0)
- return r;
+ assert(bus);
+ assert(name);
- r = bus_message_read_id128(reply, &id);
- if (r < 0)
- return r;
+ r = bus_call_method(bus, bus_machine_mgr, "GetImage", &error, NULL, "s", name);
+ if (r < 0) {
+ if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_IMAGE))
+ return 0;
- if (!sd_id128_is_null(id))
- printf(" Machine ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(id));
+ return log_error_errno(r, "Failed to check whether image %s exists: %s", name, bus_error_message(&error, r));
+ }
- return 0;
+ return 1;
}
-static int print_image_machine_info(sd_bus *bus, const char *name) {
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+static int verb_start_machine(int argc, char *argv[], uintptr_t _data, void *userdata) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
+ sd_bus *bus = ASSERT_PTR(userdata);
int r;
- r = bus_call_method(bus, bus_machine_mgr, "GetImageMachineInfo", NULL, &reply, "s", name);
- if (r < 0)
- return r;
+ (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+ ask_password_agent_open_if_enabled(arg_transport, arg_ask_password);
- r = sd_bus_message_enter_container(reply, 'a', "{ss}");
+ r = bus_wait_for_jobs_new(bus, &w);
if (r < 0)
- return r;
+ return log_error_errno(r, "Could not watch jobs: %m");
- for (;;) {
- const char *p, *q;
+ for (int i = 1; i < argc; i++) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_free_ char *unit = NULL;
+ const char *object;
- r = sd_bus_message_read(reply, "{ss}", &p, &q);
+ r = make_service_name(argv[i], &unit);
+ if (r < 0)
+ return r;
+
+ r = image_exists(bus, argv[i]);
if (r < 0)
return r;
if (r == 0)
- break;
+ return log_error_errno(SYNTHETIC_ERRNO(ENXIO),
+ "Machine image '%s' does not exist.",
+ argv[i]);
- if (streq(p, "DEPLOYMENT"))
- printf(" Deployment: %s\n", q);
+ r = bus_call_method(
+ bus,
+ bus_systemd_mgr,
+ "StartUnit",
+ &error,
+ &reply,
+ "ss", unit, "fail");
+ if (r < 0)
+ return log_error_errno(r, "Failed to start unit: %s", bus_error_message(&error, r));
+
+ r = sd_bus_message_read(reply, "o", &object);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ r = bus_wait_for_jobs_add(w, object);
+ if (r < 0)
+ return log_oom();
}
- r = sd_bus_message_exit_container(reply);
+ r = bus_wait_for_jobs(w, arg_quiet, NULL);
if (r < 0)
return r;
return 0;
}
-typedef struct ImageStatusInfo {
- const char *name;
- const char *path;
- const char *type;
- bool read_only;
- usec_t crtime;
- usec_t mtime;
- uint64_t usage;
- uint64_t limit;
- uint64_t usage_exclusive;
- uint64_t limit_exclusive;
-} ImageStatusInfo;
-
-static void print_image_status_info(sd_bus *bus, ImageStatusInfo *i) {
- assert(bus);
- assert(i);
+static int parse_machine_uid(const char *spec, const char **machine, char **uid) {
+ /*
+ * Whatever is specified in the spec takes priority over global arguments.
+ */
+ char *_uid = NULL;
+ const char *_machine = NULL;
- if (i->name) {
- fputs(i->name, stdout);
- putchar('\n');
- }
+ assert(uid);
+ assert(machine);
- if (i->type)
- printf("\t Type: %s\n", i->type);
+ if (spec) {
+ const char *at;
- if (i->path)
- printf("\t Path: %s\n", i->path);
+ at = strchr(spec, '@');
+ if (at) {
+ if (at == spec)
+ /* Do the same as ssh and refuse "@host". */
+ return -EINVAL;
- (void) print_image_hostname(bus, i->name);
- (void) print_image_machine_id(bus, i->name);
- (void) print_image_machine_info(bus, i->name);
+ _machine = at + 1;
+ _uid = strndup(spec, at - spec);
+ if (!_uid)
+ return -ENOMEM;
+ } else
+ _machine = spec;
+ };
- print_os_release(bus, "GetImageOSRelease", i->name, "\t OS: ");
+ if (arg_uid && !_uid) {
+ _uid = strdup(arg_uid);
+ if (!_uid)
+ return -ENOMEM;
+ }
- printf("\t RO: %s%s%s\n",
- i->read_only ? ansi_highlight_red() : "",
- i->read_only ? "read-only" : "writable",
- i->read_only ? ansi_normal() : "");
+ *uid = _uid;
+ *machine = isempty(_machine) ? ".host" : _machine;
+ return 0;
+}
- if (timestamp_is_set(i->crtime))
- printf("\t Created: %s; %s\n",
- FORMAT_TIMESTAMP(i->crtime), FORMAT_TIMESTAMP_RELATIVE(i->crtime));
+static int process_forward(sd_event *event, sd_bus_slot *machine_removed_slot, int master, PTYForwardFlags flags, const char *name) {
+ int r;
- if (timestamp_is_set(i->mtime))
- printf("\tModified: %s; %s\n",
- FORMAT_TIMESTAMP(i->mtime), FORMAT_TIMESTAMP_RELATIVE(i->mtime));
+ assert(event);
+ assert(machine_removed_slot);
+ assert(master >= 0);
+ assert(name);
- if (i->usage != UINT64_MAX) {
- if (i->usage_exclusive != i->usage && i->usage_exclusive != UINT64_MAX)
- printf("\t Usage: %s (exclusive: %s)\n",
- FORMAT_BYTES(i->usage), FORMAT_BYTES(i->usage_exclusive));
+ if (!arg_quiet) {
+ if (streq(name, ".host"))
+ log_info("Connected to the local host. Press ^] three times within 1s to exit session.");
else
- printf("\t Usage: %s\n", FORMAT_BYTES(i->usage));
+ log_info("Connected to machine %s. Press ^] three times within 1s to exit session.", name);
}
- if (i->limit != UINT64_MAX) {
- if (i->limit_exclusive != i->limit && i->limit_exclusive != UINT64_MAX)
- printf("\t Limit: %s (exclusive: %s)\n",
- FORMAT_BYTES(i->limit), FORMAT_BYTES(i->limit_exclusive));
- else
- printf("\t Limit: %s\n", FORMAT_BYTES(i->limit));
+ _cleanup_(osc_context_closep) sd_id128_t osc_context_id = SD_ID128_NULL;
+ if (!terminal_is_dumb()) {
+ r = osc_context_open_container(name, /* ret_seq= */ NULL, &osc_context_id);
+ if (r < 0)
+ return r;
}
-}
-
-static int show_image_info(sd_bus *bus, const char *path, bool *new_line) {
- static const struct bus_properties_map map[] = {
- { "Name", "s", NULL, offsetof(ImageStatusInfo, name) },
- { "Path", "s", NULL, offsetof(ImageStatusInfo, path) },
- { "Type", "s", NULL, offsetof(ImageStatusInfo, type) },
- { "ReadOnly", "b", NULL, offsetof(ImageStatusInfo, read_only) },
- { "CreationTimestamp", "t", NULL, offsetof(ImageStatusInfo, crtime) },
- { "ModificationTimestamp", "t", NULL, offsetof(ImageStatusInfo, mtime) },
- { "Usage", "t", NULL, offsetof(ImageStatusInfo, usage) },
- { "Limit", "t", NULL, offsetof(ImageStatusInfo, limit) },
- { "UsageExclusive", "t", NULL, offsetof(ImageStatusInfo, usage_exclusive) },
- { "LimitExclusive", "t", NULL, offsetof(ImageStatusInfo, limit_exclusive) },
- {}
- };
+ r = sd_event_set_signal_exit(event, true);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enable SIGINT/SITERM handling: %m");
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
- ImageStatusInfo info = {};
- int r;
+ _cleanup_(pty_forward_freep) PTYForward *forward = NULL;
+ r = pty_forward_new(event, master, flags, &forward);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create PTY forwarder: %m");
- assert(bus);
- assert(path);
- assert(new_line);
+ /* No userdata should not set previously. */
+ assert_se(!sd_bus_slot_set_userdata(machine_removed_slot, forward));
- r = bus_map_all_properties(bus,
- "org.freedesktop.machine1",
- path,
- map,
- BUS_MAP_BOOLEAN_AS_BOOL,
- &error,
- &m,
- &info);
+ r = sd_event_loop(event);
if (r < 0)
- return log_error_errno(r, "Could not get properties: %s", bus_error_message(&error, r));
+ return log_error_errno(r, "Failed to run event loop: %m");
- if (*new_line)
- printf("\n");
- *new_line = true;
+ bool machine_died =
+ FLAGS_SET(flags, PTY_FORWARD_IGNORE_VHANGUP) &&
+ !pty_forward_vhangup_honored(forward);
- print_image_status_info(bus, &info);
+ if (!arg_quiet) {
+ if (machine_died)
+ log_info("Machine %s terminated.", name);
+ else if (streq(name, ".host"))
+ log_info("Connection to the local host terminated.");
+ else
+ log_info("Connection to machine %s terminated.", name);
+ }
- return r;
+ return 0;
}
-typedef struct PoolStatusInfo {
- const char *path;
- uint64_t usage;
- uint64_t limit;
-} PoolStatusInfo;
+static int on_machine_removed(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
+ PTYForward *forward = ASSERT_PTR(userdata);
+ int r;
-static void print_pool_status_info(sd_bus *bus, PoolStatusInfo *i) {
- if (i->path)
- printf("\t Path: %s\n", i->path);
+ assert(m);
- if (i->usage != UINT64_MAX)
- printf("\t Usage: %s\n", FORMAT_BYTES(i->usage));
+ /* Tell the forwarder to exit on the next vhangup(), so that we still flush out what might be queued
+ * and exit then. */
- if (i->limit != UINT64_MAX)
- printf("\t Limit: %s\n", FORMAT_BYTES(i->limit));
-}
+ r = pty_forward_honor_vhangup(forward);
+ if (r < 0) {
+ /* On error, quit immediately. */
+ log_error_errno(r, "Failed to make PTY forwarder honor vhangup(): %m");
+ (void) sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), EXIT_FAILURE);
+ }
-static int show_pool_info(sd_bus *bus) {
+ return 0;
+}
- static const struct bus_properties_map map[] = {
- { "PoolPath", "s", NULL, offsetof(PoolStatusInfo, path) },
- { "PoolUsage", "t", NULL, offsetof(PoolStatusInfo, usage) },
- { "PoolLimit", "t", NULL, offsetof(PoolStatusInfo, limit) },
- {}
- };
+static int verb_login_machine(int argc, char *argv[], uintptr_t _data, void *userdata) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL;
+ _cleanup_(sd_event_unrefp) sd_event *event = NULL;
+ int master = -1, r;
+ sd_bus *bus = ASSERT_PTR(userdata);
+ const char *match, *machine;
- PoolStatusInfo info = {
- .usage = UINT64_MAX,
- .limit = UINT64_MAX,
- };
+ if (!strv_isempty(arg_setenv) || arg_uid)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--setenv= and --uid= are not supported for 'login'. Use 'shell' instead.");
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
- int r;
+ if (!IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE))
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Login only supported on local machines.");
- assert(bus);
+ (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
- r = bus_map_all_properties(bus,
- "org.freedesktop.machine1",
- "/org/freedesktop/machine1",
- map,
- 0,
- &error,
- &m,
- &info);
+ r = sd_event_default(&event);
if (r < 0)
- return log_error_errno(r, "Could not get properties: %s", bus_error_message(&error, r));
-
- print_pool_status_info(bus, &info);
+ return log_error_errno(r, "Failed to get event loop: %m");
- return 0;
-}
+ r = sd_bus_attach_event(bus, event, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to attach bus to event loop: %m");
-static int show_image_properties(sd_bus *bus, const char *path, bool *new_line) {
- int r;
+ machine = argc < 2 || isempty(argv[1]) ? ".host" : argv[1];
- assert(bus);
- assert(path);
- assert(new_line);
+ match = strjoina("type='signal',"
+ "sender='org.freedesktop.machine1',"
+ "path='/org/freedesktop/machine1',",
+ "interface='org.freedesktop.machine1.Manager',"
+ "member='MachineRemoved',"
+ "arg0='", machine, "'");
- if (*new_line)
- printf("\n");
+ r = sd_bus_add_match_async(bus, &slot, match, on_machine_removed, NULL, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to request machine removal match: %m");
- *new_line = true;
+ r = bus_call_method(bus, bus_machine_mgr, "OpenMachineLogin", &error, &reply, "s", machine);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get login PTY: %s", bus_error_message(&error, r));
- r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, NULL, arg_property, arg_print_flags, NULL);
+ r = sd_bus_message_read(reply, "hs", &master, NULL);
if (r < 0)
- log_error_errno(r, "Could not get properties: %m");
+ return bus_log_parse_error(r);
- return r;
+ return process_forward(event, slot, master, PTY_FORWARD_IGNORE_VHANGUP, machine);
}
-static int verb_show_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
+static int verb_shell_machine(int argc, char *argv[], uintptr_t _data, void *userdata) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- bool properties, new_line = false;
+ _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL;
+ _cleanup_(sd_event_unrefp) sd_event *event = NULL;
+ int master = -1, r;
sd_bus *bus = ASSERT_PTR(userdata);
- int r = 0;
+ const char *match, *machine, *path;
+ _cleanup_free_ char *uid = NULL;
- properties = !strstr(argv[0], "status");
+ if (!IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE))
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Shell only supported on local machines.");
- pager_open(arg_pager_flags);
+ if (terminal_is_dumb()) {
+ /* Set TERM=dumb if we are running on a dumb terminal or with a pipe.
+ * Otherwise, we will get unwanted OSC sequences. */
+ if (!strv_find_prefix(arg_setenv, "TERM="))
+ if (strv_extend(&arg_setenv, "TERM=dumb") < 0)
+ return log_oom();
+ } else
+ /* Pass $TERM & Co. to shell session, if not explicitly specified. */
+ FOREACH_STRING(v, "TERM=", "COLORTERM=", "NO_COLOR=") {
+ if (strv_find_prefix(arg_setenv, v))
+ continue;
- if (argc <= 1) {
+ const char *t = strv_find_prefix(environ, v);
+ if (!t)
+ continue;
- /* If no argument is specified, inspect the manager
- * itself */
+ if (strv_extend(&arg_setenv, t) < 0)
+ return log_oom();
+ }
- if (properties)
- r = show_image_properties(bus, "/org/freedesktop/machine1", &new_line);
- else
- r = show_pool_info(bus);
- if (r < 0)
- return r;
- }
+ (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
- for (int i = 1; i < argc; i++) {
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
- const char *path = NULL;
+ r = sd_event_default(&event);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get event loop: %m");
- r = bus_call_method(bus, bus_machine_mgr, "GetImage", &error, &reply, "s", argv[i]);
- if (r < 0)
- return log_error_errno(r, "Could not get path to image: %s", bus_error_message(&error, r));
+ r = sd_bus_attach_event(bus, event, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to attach bus to event loop: %m");
- r = sd_bus_message_read(reply, "o", &path);
- if (r < 0)
- return bus_log_parse_error(r);
+ r = parse_machine_uid(argc >= 2 ? argv[1] : NULL, &machine, &uid);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse machine specification: %m");
- if (properties)
- r = show_image_properties(bus, path, &new_line);
- else
- r = show_image_info(bus, path, &new_line);
- }
+ match = strjoina("type='signal',"
+ "sender='org.freedesktop.machine1',"
+ "path='/org/freedesktop/machine1',",
+ "interface='org.freedesktop.machine1.Manager',"
+ "member='MachineRemoved',"
+ "arg0='", machine, "'");
- return r;
-}
+ r = sd_bus_add_match_async(bus, &slot, match, on_machine_removed, NULL, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to request machine removal match: %m");
-static int verb_kill_machine(int argc, char *argv[], uintptr_t _data, void *userdata) {
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- sd_bus *bus = ASSERT_PTR(userdata);
- int r;
+ r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "OpenMachineShell");
+ if (r < 0)
+ return bus_log_create_error(r);
- (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+ path = argc < 3 || isempty(argv[2]) ? NULL : argv[2];
- if (!arg_kill_whom)
- arg_kill_whom = "all";
+ r = sd_bus_message_append(m, "sss", machine, uid, path);
+ if (r < 0)
+ return bus_log_create_error(r);
- for (int i = 1; i < argc; i++) {
- r = bus_call_method(
- bus,
- bus_machine_mgr,
- "KillMachine",
- &error,
- NULL,
- "ssi", argv[i], arg_kill_whom, arg_signal);
- if (r < 0)
- return log_error_errno(r, "Could not kill machine: %s", bus_error_message(&error, r));
- }
+ r = sd_bus_message_append_strv(m, strv_length(argv) <= 3 ? NULL : argv + 2);
+ if (r < 0)
+ return bus_log_create_error(r);
- return 0;
+ r = sd_bus_message_append_strv(m, arg_setenv);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_call(bus, m, 0, &error, &reply);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get shell PTY: %s", bus_error_message(&error, r));
+
+ r = sd_bus_message_read(reply, "hs", &master, NULL);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ return process_forward(event, slot, master, /* flags= */ 0, machine);
}
-static int verb_machine_control_one(const char *machine_name, const char *method);
+static int verb_poweroff_machine(int argc, char *argv[], uintptr_t data, void *userdata);
-static int verb_reboot_machine(int argc, char *argv[], uintptr_t data, void *userdata) {
+static int verb_enable_machine(int argc, char *argv[], uintptr_t data, void *userdata) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ const char *method;
sd_bus *bus = ASSERT_PTR(userdata);
int r;
+ bool enable;
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+ enable = streq(argv[0], "enable");
+ method = enable ? "EnableUnitFiles" : "DisableUnitFiles";
+
+ r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, method);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_open_container(m, 'a', "s");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ if (enable) {
+ r = sd_bus_message_append(m, "s", "machines.target");
+ if (r < 0)
+ return bus_log_create_error(r);
+ }
+
for (int i = 1; i < argc; i++) {
- r = verb_machine_control_one(argv[i], "io.systemd.MachineInstance.Reboot");
- if (r >= 0)
- continue;
- if (r != -EOPNOTSUPP)
+ _cleanup_free_ char *unit = NULL;
+
+ r = make_service_name(argv[i], &unit);
+ if (r < 0)
return r;
- /* Container fallback: SIGINT to init (sysvinit + systemd compatible) */
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- r = bus_call_method(bus, bus_machine_mgr, "KillMachine", &error, NULL,
- "ssi", argv[i], "leader", (int32_t) SIGINT);
+ r = image_exists(bus, argv[i]);
if (r < 0)
- return log_error_errno(r, "Could not reboot machine '%s': %s", argv[i], bus_error_message(&error, r));
- }
+ return r;
+ if (r == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(ENXIO),
+ "Machine image '%s' does not exist.",
+ argv[i]);
- return 0;
-}
+ r = sd_bus_message_append(m, "s", unit);
+ if (r < 0)
+ return bus_log_create_error(r);
+ }
-static int verb_poweroff_machine(int argc, char *argv[], uintptr_t data, void *userdata) {
- sd_bus *bus = ASSERT_PTR(userdata);
- int r;
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
- (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+ if (enable)
+ r = sd_bus_message_append(m, "bb", false, false);
+ else
+ r = sd_bus_message_append(m, "b", false);
+ if (r < 0)
+ return bus_log_create_error(r);
- for (int i = 1; i < argc; i++) {
- /* VM with varlink control socket: QMP graceful powerdown */
- r = verb_machine_control_one(argv[i], "io.systemd.MachineInstance.PowerOff");
- if (r >= 0)
- continue;
- if (r != -EOPNOTSUPP)
- return r;
+ r = sd_bus_call(bus, m, 0, &error, &reply);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enable or disable unit: %s", bus_error_message(&error, r));
- /* Not a VM: signal-based poweroff */
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- r = bus_call_method(bus, bus_machine_mgr, "KillMachine", &error, NULL,
- "ssi", argv[i], "leader", (int32_t) (SIGRTMIN+4));
+ if (enable) {
+ r = sd_bus_message_read(reply, "b", NULL);
if (r < 0)
- return log_error_errno(r, "Could not kill machine: %s", bus_error_message(&error, r));
+ return bus_log_parse_error(r);
}
- return 0;
-}
+ r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet);
+ if (r < 0)
+ return r;
-static int verb_terminate_machine(int argc, char *argv[], uintptr_t _data, void *userdata) {
- sd_bus *bus = ASSERT_PTR(userdata);
- int r;
+ r = bus_service_manager_reload(bus);
+ if (r < 0)
+ return r;
- (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+ if (arg_now) {
+ _cleanup_strv_free_ char **new_args = NULL;
- for (int i = 1; i < argc; i++) {
- /* VM with varlink control socket: QMP quit (immediate termination) */
- r = verb_machine_control_one(argv[i], "io.systemd.MachineInstance.Terminate");
- if (r >= 0)
- continue;
- if (r != -EOPNOTSUPP)
- return r;
+ new_args = strv_new(enable ? "start" : "poweroff");
+ if (!new_args)
+ return log_oom();
- /* Not a VM or no varlink socket: fall back to machined */
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- r = bus_call_method(bus, bus_machine_mgr, "TerminateMachine", &error, NULL, "s", argv[i]);
+ r = strv_extend_strv(&new_args, argv + 1, /* filter_duplicates= */ false);
if (r < 0)
- return log_error_errno(r, "Could not terminate machine: %s", bus_error_message(&error, r));
+ return log_oom();
+
+ if (enable)
+ return verb_start_machine(strv_length(new_args), new_args, data, userdata);
+
+ return verb_poweroff_machine(strv_length(new_args), new_args, data, userdata);
}
return 0;
return 0;
}
+static int verb_poweroff_machine(int argc, char *argv[], uintptr_t data, void *userdata) {
+ sd_bus *bus = ASSERT_PTR(userdata);
+ int r;
+
+ (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+ for (int i = 1; i < argc; i++) {
+ /* VM with varlink control socket: QMP graceful powerdown */
+ r = verb_machine_control_one(argv[i], "io.systemd.MachineInstance.PowerOff");
+ if (r >= 0)
+ continue;
+ if (r != -EOPNOTSUPP)
+ return r;
+
+ /* Not a VM: signal-based poweroff */
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ r = bus_call_method(bus, bus_machine_mgr, "KillMachine", &error, NULL,
+ "ssi", argv[i], "leader", (int32_t) (SIGRTMIN+4));
+ if (r < 0)
+ return log_error_errno(r, "Could not kill machine: %s", bus_error_message(&error, r));
+ }
+
+ return 0;
+}
+
+static int verb_reboot_machine(int argc, char *argv[], uintptr_t data, void *userdata) {
+ sd_bus *bus = ASSERT_PTR(userdata);
+ int r;
+
+ (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+ for (int i = 1; i < argc; i++) {
+ r = verb_machine_control_one(argv[i], "io.systemd.MachineInstance.Reboot");
+ if (r >= 0)
+ continue;
+ if (r != -EOPNOTSUPP)
+ return r;
+
+ /* Container fallback: SIGINT to init (sysvinit + systemd compatible) */
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ r = bus_call_method(bus, bus_machine_mgr, "KillMachine", &error, NULL,
+ "ssi", argv[i], "leader", (int32_t) SIGINT);
+ if (r < 0)
+ return log_error_errno(r, "Could not reboot machine '%s': %s", argv[i], bus_error_message(&error, r));
+ }
+
+ return 0;
+}
+
static int verb_vm_control(int argc, char *argv[], const char *method) {
int r;
return verb_vm_control(argc, argv, "io.systemd.MachineInstance.Resume");
}
-static const char *select_copy_method(bool copy_from, bool force) {
+static int verb_terminate_machine(int argc, char *argv[], uintptr_t _data, void *userdata) {
+ sd_bus *bus = ASSERT_PTR(userdata);
+ int r;
+
+ (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+ for (int i = 1; i < argc; i++) {
+ /* VM with varlink control socket: QMP quit (immediate termination) */
+ r = verb_machine_control_one(argv[i], "io.systemd.MachineInstance.Terminate");
+ if (r >= 0)
+ continue;
+ if (r != -EOPNOTSUPP)
+ return r;
+
+ /* Not a VM or no varlink socket: fall back to machined */
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ r = bus_call_method(bus, bus_machine_mgr, "TerminateMachine", &error, NULL, "s", argv[i]);
+ if (r < 0)
+ return log_error_errno(r, "Could not terminate machine: %s", bus_error_message(&error, r));
+ }
+
+ return 0;
+}
+
+static int verb_kill_machine(int argc, char *argv[], uintptr_t _data, void *userdata) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ sd_bus *bus = ASSERT_PTR(userdata);
+ int r;
+
+ (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+ if (!arg_kill_whom)
+ arg_kill_whom = "all";
+
+ for (int i = 1; i < argc; i++) {
+ r = bus_call_method(
+ bus,
+ bus_machine_mgr,
+ "KillMachine",
+ &error,
+ NULL,
+ "ssi", argv[i], arg_kill_whom, arg_signal);
+ if (r < 0)
+ return log_error_errno(r, "Could not kill machine: %s", bus_error_message(&error, r));
+ }
+
+ return 0;
+}
+
+static const char* select_copy_method(bool copy_from, bool force) {
if (force)
return copy_from ? "CopyFromMachineWithFlags" : "CopyToMachineWithFlags";
else
return 0;
}
-static int verb_bind_volume(int argc, char *argv[], uintptr_t _data, void *userdata) {
+static int verb_bind_mount(int argc, char *argv[], uintptr_t _data, void *userdata) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ sd_bus *bus = ASSERT_PTR(userdata);
int r;
- if (arg_transport != BUS_TRANSPORT_LOCAL)
- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
- "bind-volume is only supported on the local transport.");
-
- _cleanup_(bind_volume_freep) BindVolume *bv = NULL;
- r = bind_volume_parse(argv[2], &bv);
- if (r < 0)
- return log_error_errno(r, "Failed to parse bind-volume argument '%s': %m", argv[2]);
-
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
- /* Locate and connect to the target machine before acquiring storage, so a missing
- * machine doesn't trigger 'create=new' side effects on the StorageProvider. */
- _cleanup_free_ char *address = NULL;
- r = machine_get_control_address(argv[1], &address);
- if (r == -EOPNOTSUPP)
- return log_error_errno(r, "Machine '%s' does not expose a varlink control socket.", argv[1]);
- if (r < 0)
- return r;
-
- _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
- r = sd_varlink_connect_address(&vl, address);
+ r = bus_call_method(
+ bus,
+ bus_machine_mgr,
+ "BindMountMachine",
+ &error,
+ NULL,
+ "sssbb",
+ argv[1],
+ argv[2],
+ argv[3],
+ arg_read_only,
+ arg_mkdir);
if (r < 0)
- return log_error_errno(r, "Failed to connect to machine control socket %s: %m", address);
+ return log_error_errno(r, "Failed to bind mount: %s", bus_error_message(&error, r));
- r = sd_varlink_set_allow_fd_passing_output(vl, true);
- if (r < 0)
- return log_error_errno(r, "Failed to enable fd passing on varlink connection: %m");
+ return 0;
+}
- _cleanup_(storage_acquire_reply_done) StorageAcquireReply reply = STORAGE_ACQUIRE_REPLY_INIT;
- _cleanup_free_ char *acquire_error_id = NULL;
- r = storage_acquire_volume(arg_runtime_scope, bv, arg_ask_password, &acquire_error_id, &reply);
- if (r < 0) {
- if (acquire_error_id)
- return log_error_errno(r, "Failed to acquire storage volume '%s:%s' from provider: %s",
- bv->provider, bv->volume, acquire_error_id);
- return log_error_errno(r, "Failed to acquire storage volume '%s:%s' from provider: %m",
- bv->provider, bv->volume);
- }
+static int verb_list_images(int argc, char *argv[], uintptr_t _data, void *userdata) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(table_unrefp) Table *table = NULL;
+ sd_bus *bus = ASSERT_PTR(userdata);
+ int r;
- int fd_index = sd_varlink_push_fd(vl, reply.fd);
- if (fd_index < 0)
- return log_error_errno(fd_index, "Failed to push storage fd onto varlink connection: %m");
- TAKE_FD(reply.fd);
+ pager_open(arg_pager_flags);
- _cleanup_free_ char *name = strjoin(bv->provider, ":", bv->volume);
- if (!name)
+ r = bus_call_method(bus, bus_machine_mgr, "ListImages", &error, &reply, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Could not get images: %s", bus_error_message(&error, r));
+
+ table = table_new("name", "type", "ro", "usage", "created", "modified");
+ if (!table)
return log_oom();
- sd_json_variant *vl_reply = NULL;
- const char *error_id = NULL;
- r = sd_varlink_callbo(
- vl,
- "io.systemd.MachineInstance.AddStorage",
- &vl_reply, &error_id,
- SD_JSON_BUILD_PAIR_INTEGER("fileDescriptorIndex", fd_index),
- SD_JSON_BUILD_PAIR_STRING("name", name),
- JSON_BUILD_PAIR_STRING_NON_EMPTY("config", bv->config));
+ if (arg_full)
+ table_set_width(table, 0);
+
+ (void) table_set_align_percent(table, TABLE_HEADER_CELL(3), 100);
+
+ r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssbttto)");
if (r < 0)
- return log_error_errno(r, "Failed to call io.systemd.MachineInstance.AddStorage: %m");
- if (error_id)
- return log_error_errno(sd_varlink_error_to_errno(error_id, vl_reply),
- "AddStorage failed for '%s': %s", name, error_id);
+ return bus_log_parse_error(r);
- return 0;
-}
+ for (;;) {
+ uint64_t crtime, mtime, size;
+ const char *name, *type;
+ int ro_int;
-static int verb_unbind_volume(int argc, char *argv[], uintptr_t _data, void *userdata) {
- int r;
+ r = sd_bus_message_read(reply, "(ssbttto)", &name, &type, &ro_int, &crtime, &mtime, &size, NULL);
+ if (r < 0)
+ return bus_log_parse_error(r);
+ if (r == 0)
+ break;
- if (arg_transport != BUS_TRANSPORT_LOCAL)
- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
- "unbind-volume is only supported on the local transport.");
+ if (name[0] == '.' && !arg_all)
+ continue;
- r = machine_storage_name_split(argv[2], /* ret_provider= */ NULL, /* ret_volume= */ NULL);
- if (r == -ENOMEM)
- return log_oom();
+ r = table_add_many(table,
+ TABLE_STRING, name,
+ TABLE_STRING, type,
+ TABLE_BOOLEAN, ro_int,
+ TABLE_SET_COLOR, ro_int ? ansi_highlight_red() : NULL,
+ TABLE_SIZE, size,
+ TABLE_TIMESTAMP, crtime,
+ TABLE_TIMESTAMP, mtime);
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+
+ r = sd_bus_message_exit_container(reply);
if (r < 0)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Invalid unbind-volume name '%s', expected '<provider>:<volume>'.", argv[2]);
+ return bus_log_parse_error(r);
- (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+ return show_table(table, "images");
+}
- _cleanup_free_ char *address = NULL;
- r = machine_get_control_address(argv[1], &address);
- if (r == -EOPNOTSUPP)
- return log_error_errno(r, "Machine '%s' does not expose a varlink control socket.", argv[1]);
+static int print_image_hostname(sd_bus *bus, const char *name) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ const char *hn;
+ int r;
+
+ r = bus_call_method(bus, bus_machine_mgr, "GetImageHostname", NULL, &reply, "s", name);
if (r < 0)
return r;
- _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
- r = sd_varlink_connect_address(&vl, address);
+ r = sd_bus_message_read(reply, "s", &hn);
if (r < 0)
- return log_error_errno(r, "Failed to connect to machine control socket %s: %m", address);
+ return r;
- sd_json_variant *reply = NULL;
- const char *error_id = NULL;
- r = sd_varlink_callbo(
- vl,
- "io.systemd.MachineInstance.RemoveStorage",
- &reply, &error_id,
- SD_JSON_BUILD_PAIR_STRING("name", argv[2]));
- if (r < 0)
- return log_error_errno(r, "Failed to call io.systemd.MachineInstance.RemoveStorage: %m");
- if (error_id)
- return log_error_errno(sd_varlink_error_to_errno(error_id, reply),
- "RemoveStorage failed for '%s': %s", argv[2], error_id);
+ if (!isempty(hn))
+ printf("\tHostname: %s\n", hn);
return 0;
}
-static int verb_bind_mount(int argc, char *argv[], uintptr_t _data, void *userdata) {
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- sd_bus *bus = ASSERT_PTR(userdata);
+static int print_image_machine_id(sd_bus *bus, const char *name) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ sd_id128_t id;
int r;
- (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+ r = bus_call_method(bus, bus_machine_mgr, "GetImageMachineID", NULL, &reply, "s", name);
+ if (r < 0)
+ return r;
- r = bus_call_method(
- bus,
- bus_machine_mgr,
- "BindMountMachine",
- &error,
- NULL,
- "sssbb",
- argv[1],
- argv[2],
- argv[3],
- arg_read_only,
- arg_mkdir);
+ r = bus_message_read_id128(reply, &id);
if (r < 0)
- return log_error_errno(r, "Failed to bind mount: %s", bus_error_message(&error, r));
+ return r;
+
+ if (!sd_id128_is_null(id))
+ printf(" Machine ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(id));
return 0;
}
-static int on_machine_removed(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
- PTYForward *forward = ASSERT_PTR(userdata);
+static int print_image_machine_info(sd_bus *bus, const char *name) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
int r;
- assert(m);
+ r = bus_call_method(bus, bus_machine_mgr, "GetImageMachineInfo", NULL, &reply, "s", name);
+ if (r < 0)
+ return r;
- /* Tell the forwarder to exit on the next vhangup(), so that we still flush out what might be queued
- * and exit then. */
+ r = sd_bus_message_enter_container(reply, 'a', "{ss}");
+ if (r < 0)
+ return r;
- r = pty_forward_honor_vhangup(forward);
- if (r < 0) {
- /* On error, quit immediately. */
- log_error_errno(r, "Failed to make PTY forwarder honor vhangup(): %m");
- (void) sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), EXIT_FAILURE);
+ for (;;) {
+ const char *p, *q;
+
+ r = sd_bus_message_read(reply, "{ss}", &p, &q);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ if (streq(p, "DEPLOYMENT"))
+ printf(" Deployment: %s\n", q);
}
+ r = sd_bus_message_exit_container(reply);
+ if (r < 0)
+ return r;
+
return 0;
}
-static int process_forward(sd_event *event, sd_bus_slot *machine_removed_slot, int master, PTYForwardFlags flags, const char *name) {
- int r;
+typedef struct ImageStatusInfo {
+ const char *name;
+ const char *path;
+ const char *type;
+ bool read_only;
+ usec_t crtime;
+ usec_t mtime;
+ uint64_t usage;
+ uint64_t limit;
+ uint64_t usage_exclusive;
+ uint64_t limit_exclusive;
+} ImageStatusInfo;
- assert(event);
- assert(machine_removed_slot);
- assert(master >= 0);
- assert(name);
+static void print_image_status_info(sd_bus *bus, ImageStatusInfo *i) {
+ assert(bus);
+ assert(i);
- if (!arg_quiet) {
- if (streq(name, ".host"))
- log_info("Connected to the local host. Press ^] three times within 1s to exit session.");
- else
- log_info("Connected to machine %s. Press ^] three times within 1s to exit session.", name);
+ if (i->name) {
+ fputs(i->name, stdout);
+ putchar('\n');
}
- _cleanup_(osc_context_closep) sd_id128_t osc_context_id = SD_ID128_NULL;
- if (!terminal_is_dumb()) {
- r = osc_context_open_container(name, /* ret_seq= */ NULL, &osc_context_id);
- if (r < 0)
- return r;
- }
+ if (i->type)
+ printf("\t Type: %s\n", i->type);
- r = sd_event_set_signal_exit(event, true);
- if (r < 0)
- return log_error_errno(r, "Failed to enable SIGINT/SITERM handling: %m");
+ if (i->path)
+ printf("\t Path: %s\n", i->path);
- _cleanup_(pty_forward_freep) PTYForward *forward = NULL;
- r = pty_forward_new(event, master, flags, &forward);
- if (r < 0)
- return log_error_errno(r, "Failed to create PTY forwarder: %m");
+ (void) print_image_hostname(bus, i->name);
+ (void) print_image_machine_id(bus, i->name);
+ (void) print_image_machine_info(bus, i->name);
- /* No userdata should not set previously. */
- assert_se(!sd_bus_slot_set_userdata(machine_removed_slot, forward));
+ print_os_release(bus, "GetImageOSRelease", i->name, "\t OS: ");
- r = sd_event_loop(event);
- if (r < 0)
- return log_error_errno(r, "Failed to run event loop: %m");
+ printf("\t RO: %s%s%s\n",
+ i->read_only ? ansi_highlight_red() : "",
+ i->read_only ? "read-only" : "writable",
+ i->read_only ? ansi_normal() : "");
- bool machine_died =
- FLAGS_SET(flags, PTY_FORWARD_IGNORE_VHANGUP) &&
- !pty_forward_vhangup_honored(forward);
+ if (timestamp_is_set(i->crtime))
+ printf("\t Created: %s; %s\n",
+ FORMAT_TIMESTAMP(i->crtime), FORMAT_TIMESTAMP_RELATIVE(i->crtime));
- if (!arg_quiet) {
- if (machine_died)
- log_info("Machine %s terminated.", name);
- else if (streq(name, ".host"))
- log_info("Connection to the local host terminated.");
+ if (timestamp_is_set(i->mtime))
+ printf("\tModified: %s; %s\n",
+ FORMAT_TIMESTAMP(i->mtime), FORMAT_TIMESTAMP_RELATIVE(i->mtime));
+
+ if (i->usage != UINT64_MAX) {
+ if (i->usage_exclusive != i->usage && i->usage_exclusive != UINT64_MAX)
+ printf("\t Usage: %s (exclusive: %s)\n",
+ FORMAT_BYTES(i->usage), FORMAT_BYTES(i->usage_exclusive));
else
- log_info("Connection to machine %s terminated.", name);
+ printf("\t Usage: %s\n", FORMAT_BYTES(i->usage));
}
- return 0;
+ if (i->limit != UINT64_MAX) {
+ if (i->limit_exclusive != i->limit && i->limit_exclusive != UINT64_MAX)
+ printf("\t Limit: %s (exclusive: %s)\n",
+ FORMAT_BYTES(i->limit), FORMAT_BYTES(i->limit_exclusive));
+ else
+ printf("\t Limit: %s\n", FORMAT_BYTES(i->limit));
+ }
}
-static int parse_machine_uid(const char *spec, const char **machine, char **uid) {
- /*
- * Whatever is specified in the spec takes priority over global arguments.
- */
- char *_uid = NULL;
- const char *_machine = NULL;
+static int show_image_info(sd_bus *bus, const char *path, bool *new_line) {
+ static const struct bus_properties_map map[] = {
+ { "Name", "s", NULL, offsetof(ImageStatusInfo, name) },
+ { "Path", "s", NULL, offsetof(ImageStatusInfo, path) },
+ { "Type", "s", NULL, offsetof(ImageStatusInfo, type) },
+ { "ReadOnly", "b", NULL, offsetof(ImageStatusInfo, read_only) },
+ { "CreationTimestamp", "t", NULL, offsetof(ImageStatusInfo, crtime) },
+ { "ModificationTimestamp", "t", NULL, offsetof(ImageStatusInfo, mtime) },
+ { "Usage", "t", NULL, offsetof(ImageStatusInfo, usage) },
+ { "Limit", "t", NULL, offsetof(ImageStatusInfo, limit) },
+ { "UsageExclusive", "t", NULL, offsetof(ImageStatusInfo, usage_exclusive) },
+ { "LimitExclusive", "t", NULL, offsetof(ImageStatusInfo, limit_exclusive) },
+ {}
+ };
- assert(uid);
- assert(machine);
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ ImageStatusInfo info = {};
+ int r;
- if (spec) {
- const char *at;
+ assert(bus);
+ assert(path);
+ assert(new_line);
- at = strchr(spec, '@');
- if (at) {
- if (at == spec)
- /* Do the same as ssh and refuse "@host". */
- return -EINVAL;
+ r = bus_map_all_properties(bus,
+ "org.freedesktop.machine1",
+ path,
+ map,
+ BUS_MAP_BOOLEAN_AS_BOOL,
+ &error,
+ &m,
+ &info);
+ if (r < 0)
+ return log_error_errno(r, "Could not get properties: %s", bus_error_message(&error, r));
- _machine = at + 1;
- _uid = strndup(spec, at - spec);
- if (!_uid)
- return -ENOMEM;
- } else
- _machine = spec;
- };
+ if (*new_line)
+ printf("\n");
+ *new_line = true;
- if (arg_uid && !_uid) {
- _uid = strdup(arg_uid);
- if (!_uid)
- return -ENOMEM;
- }
+ print_image_status_info(bus, &info);
- *uid = _uid;
- *machine = isempty(_machine) ? ".host" : _machine;
- return 0;
+ return r;
}
-static int verb_login_machine(int argc, char *argv[], uintptr_t _data, void *userdata) {
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL;
- _cleanup_(sd_event_unrefp) sd_event *event = NULL;
- int master = -1, r;
- sd_bus *bus = ASSERT_PTR(userdata);
- const char *match, *machine;
-
- if (!strv_isempty(arg_setenv) || arg_uid)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "--setenv= and --uid= are not supported for 'login'. Use 'shell' instead.");
+typedef struct PoolStatusInfo {
+ const char *path;
+ uint64_t usage;
+ uint64_t limit;
+} PoolStatusInfo;
- if (!IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE))
- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
- "Login only supported on local machines.");
+static void print_pool_status_info(sd_bus *bus, PoolStatusInfo *i) {
+ if (i->path)
+ printf("\t Path: %s\n", i->path);
- (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+ if (i->usage != UINT64_MAX)
+ printf("\t Usage: %s\n", FORMAT_BYTES(i->usage));
- r = sd_event_default(&event);
- if (r < 0)
- return log_error_errno(r, "Failed to get event loop: %m");
+ if (i->limit != UINT64_MAX)
+ printf("\t Limit: %s\n", FORMAT_BYTES(i->limit));
+}
- r = sd_bus_attach_event(bus, event, 0);
- if (r < 0)
- return log_error_errno(r, "Failed to attach bus to event loop: %m");
+static int show_pool_info(sd_bus *bus) {
+ static const struct bus_properties_map map[] = {
+ { "PoolPath", "s", NULL, offsetof(PoolStatusInfo, path) },
+ { "PoolUsage", "t", NULL, offsetof(PoolStatusInfo, usage) },
+ { "PoolLimit", "t", NULL, offsetof(PoolStatusInfo, limit) },
+ {}
+ };
- machine = argc < 2 || isempty(argv[1]) ? ".host" : argv[1];
+ PoolStatusInfo info = {
+ .usage = UINT64_MAX,
+ .limit = UINT64_MAX,
+ };
- match = strjoina("type='signal',"
- "sender='org.freedesktop.machine1',"
- "path='/org/freedesktop/machine1',",
- "interface='org.freedesktop.machine1.Manager',"
- "member='MachineRemoved',"
- "arg0='", machine, "'");
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ int r;
- r = sd_bus_add_match_async(bus, &slot, match, on_machine_removed, NULL, NULL);
- if (r < 0)
- return log_error_errno(r, "Failed to request machine removal match: %m");
+ assert(bus);
- r = bus_call_method(bus, bus_machine_mgr, "OpenMachineLogin", &error, &reply, "s", machine);
+ r = bus_map_all_properties(bus,
+ "org.freedesktop.machine1",
+ "/org/freedesktop/machine1",
+ map,
+ 0,
+ &error,
+ &m,
+ &info);
if (r < 0)
- return log_error_errno(r, "Failed to get login PTY: %s", bus_error_message(&error, r));
+ return log_error_errno(r, "Could not get properties: %s", bus_error_message(&error, r));
- r = sd_bus_message_read(reply, "hs", &master, NULL);
- if (r < 0)
- return bus_log_parse_error(r);
+ print_pool_status_info(bus, &info);
- return process_forward(event, slot, master, PTY_FORWARD_IGNORE_VHANGUP, machine);
+ return 0;
}
-static int verb_shell_machine(int argc, char *argv[], uintptr_t _data, void *userdata) {
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL;
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL;
- _cleanup_(sd_event_unrefp) sd_event *event = NULL;
- int master = -1, r;
- sd_bus *bus = ASSERT_PTR(userdata);
- const char *match, *machine, *path;
- _cleanup_free_ char *uid = NULL;
-
- if (!IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE))
- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
- "Shell only supported on local machines.");
-
- if (terminal_is_dumb()) {
- /* Set TERM=dumb if we are running on a dumb terminal or with a pipe.
- * Otherwise, we will get unwanted OSC sequences. */
- if (!strv_find_prefix(arg_setenv, "TERM="))
- if (strv_extend(&arg_setenv, "TERM=dumb") < 0)
- return log_oom();
- } else
- /* Pass $TERM & Co. to shell session, if not explicitly specified. */
- FOREACH_STRING(v, "TERM=", "COLORTERM=", "NO_COLOR=") {
- if (strv_find_prefix(arg_setenv, v))
- continue;
+static int show_image_properties(sd_bus *bus, const char *path, bool *new_line) {
+ int r;
- const char *t = strv_find_prefix(environ, v);
- if (!t)
- continue;
+ assert(bus);
+ assert(path);
+ assert(new_line);
- if (strv_extend(&arg_setenv, t) < 0)
- return log_oom();
- }
+ if (*new_line)
+ printf("\n");
- (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+ *new_line = true;
- r = sd_event_default(&event);
+ r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, NULL, arg_property, arg_print_flags, NULL);
if (r < 0)
- return log_error_errno(r, "Failed to get event loop: %m");
+ log_error_errno(r, "Could not get properties: %m");
- r = sd_bus_attach_event(bus, event, 0);
- if (r < 0)
- return log_error_errno(r, "Failed to attach bus to event loop: %m");
+ return r;
+}
- r = parse_machine_uid(argc >= 2 ? argv[1] : NULL, &machine, &uid);
- if (r < 0)
- return log_error_errno(r, "Failed to parse machine specification: %m");
+static int verb_show_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ bool properties, new_line = false;
+ sd_bus *bus = ASSERT_PTR(userdata);
+ int r = 0;
- match = strjoina("type='signal',"
- "sender='org.freedesktop.machine1',"
- "path='/org/freedesktop/machine1',",
- "interface='org.freedesktop.machine1.Manager',"
- "member='MachineRemoved',"
- "arg0='", machine, "'");
+ properties = !strstr(argv[0], "status");
- r = sd_bus_add_match_async(bus, &slot, match, on_machine_removed, NULL, NULL);
- if (r < 0)
- return log_error_errno(r, "Failed to request machine removal match: %m");
+ pager_open(arg_pager_flags);
- r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "OpenMachineShell");
- if (r < 0)
- return bus_log_create_error(r);
+ if (argc <= 1) {
- path = argc < 3 || isempty(argv[2]) ? NULL : argv[2];
+ /* If no argument is specified, inspect the manager
+ * itself */
- r = sd_bus_message_append(m, "sss", machine, uid, path);
- if (r < 0)
- return bus_log_create_error(r);
+ if (properties)
+ r = show_image_properties(bus, "/org/freedesktop/machine1", &new_line);
+ else
+ r = show_pool_info(bus);
+ if (r < 0)
+ return r;
+ }
- r = sd_bus_message_append_strv(m, strv_length(argv) <= 3 ? NULL : argv + 2);
- if (r < 0)
- return bus_log_create_error(r);
+ for (int i = 1; i < argc; i++) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ const char *path = NULL;
- r = sd_bus_message_append_strv(m, arg_setenv);
- if (r < 0)
- return bus_log_create_error(r);
+ r = bus_call_method(bus, bus_machine_mgr, "GetImage", &error, &reply, "s", argv[i]);
+ if (r < 0)
+ return log_error_errno(r, "Could not get path to image: %s", bus_error_message(&error, r));
- r = sd_bus_call(bus, m, 0, &error, &reply);
- if (r < 0)
- return log_error_errno(r, "Failed to get shell PTY: %s", bus_error_message(&error, r));
+ r = sd_bus_message_read(reply, "o", &path);
+ if (r < 0)
+ return bus_log_parse_error(r);
- r = sd_bus_message_read(reply, "hs", &master, NULL);
- if (r < 0)
- return bus_log_parse_error(r);
+ if (properties)
+ r = show_image_properties(bus, path, &new_line);
+ else
+ r = show_image_info(bus, path, &new_line);
+ }
- return process_forward(event, slot, master, /* flags= */ 0, machine);
+ return r;
}
static int normalize_nspawn_filename(const char *name, char **ret_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= */ CAT_FORMAT_HAS_SECTIONS);
- if (q < 0)
- return r < 0 ? r : q;
- }
-
- return r;
-}
-
-static int verb_remove_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
- sd_bus *bus = ASSERT_PTR(userdata);
- int r;
-
- (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
-
- for (int i = 1; i < argc; i++) {
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
-
- r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "RemoveImage");
- if (r < 0)
- return bus_log_create_error(r);
-
- r = sd_bus_message_append(m, "s", argv[i]);
- if (r < 0)
- return bus_log_create_error(r);
-
- /* This is a slow operation, hence turn off any method call timeouts */
- r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL);
- if (r < 0)
- return log_error_errno(r, "Could not remove image: %s", bus_error_message(&error, r));
- }
-
- return 0;
-}
-
-static int verb_rename_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- sd_bus *bus = ASSERT_PTR(userdata);
- int r;
-
- (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
-
- r = bus_call_method(
- bus,
- bus_machine_mgr,
- "RenameImage",
- &error,
- NULL,
- "ss", argv[1], argv[2]);
- if (r < 0)
- return log_error_errno(r, "Could not rename image: %s", bus_error_message(&error, r));
-
- return 0;
-}
-
-static int verb_clone_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
- sd_bus *bus = ASSERT_PTR(userdata);
- int r;
-
- (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
-
- r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "CloneImage");
- if (r < 0)
- return bus_log_create_error(r);
-
- r = sd_bus_message_append(m, "ssb", argv[1], argv[2], arg_read_only);
- if (r < 0)
- return bus_log_create_error(r);
-
- /* This is a slow operation, hence turn off any method call timeouts */
- r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL);
- if (r < 0)
- return log_error_errno(r, "Could not clone image: %s", bus_error_message(&error, r));
-
- return 0;
-}
-
-static int verb_read_only_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- sd_bus *bus = ASSERT_PTR(userdata);
- int b = true, r;
-
- if (argc > 2) {
- b = parse_boolean(argv[2]);
- if (b < 0)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Failed to parse boolean argument: %s",
- argv[2]);
- }
-
- (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
-
- r = bus_call_method(bus, bus_machine_mgr, "MarkImageReadOnly", &error, NULL, "sb", argv[1], b);
- if (r < 0)
- return log_error_errno(r, "Could not mark image read-only: %s", bus_error_message(&error, r));
-
- return 0;
-}
-
-static int image_exists(sd_bus *bus, const char *name) {
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- int r;
-
- assert(bus);
- assert(name);
-
- r = bus_call_method(bus, bus_machine_mgr, "GetImage", &error, NULL, "s", name);
- if (r < 0) {
- if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_IMAGE))
- return 0;
-
- return log_error_errno(r, "Failed to check whether image %s exists: %s", name, bus_error_message(&error, r));
- }
-
- return 1;
-}
-
-static int make_service_name(const char *name, char **ret) {
- int r;
-
- assert(name);
- assert(ret);
- assert(arg_runner >= 0 && arg_runner < _RUNNER_MAX);
-
- if (!hostname_is_valid(name, 0))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Invalid machine name %s.", name);
-
- r = unit_name_build(machine_runner_unit_prefix_table[arg_runner], name, ".service", ret);
- if (r < 0)
- return log_error_errno(r, "Failed to build unit name: %m");
-
- return 0;
-}
-
-static int verb_start_machine(int argc, char *argv[], uintptr_t _data, void *userdata) {
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
- sd_bus *bus = ASSERT_PTR(userdata);
- int r;
-
- (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
- ask_password_agent_open_if_enabled(arg_transport, arg_ask_password);
-
- r = bus_wait_for_jobs_new(bus, &w);
- if (r < 0)
- return log_error_errno(r, "Could not watch jobs: %m");
-
- for (int i = 1; i < argc; i++) {
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
- _cleanup_free_ char *unit = NULL;
- const char *object;
-
- r = make_service_name(argv[i], &unit);
- if (r < 0)
- return r;
-
- r = image_exists(bus, argv[i]);
- if (r < 0)
- return r;
- if (r == 0)
- return log_error_errno(SYNTHETIC_ERRNO(ENXIO),
- "Machine image '%s' does not exist.",
- argv[i]);
-
- r = bus_call_method(
- bus,
- bus_systemd_mgr,
- "StartUnit",
- &error,
- &reply,
- "ss", unit, "fail");
- if (r < 0)
- return log_error_errno(r, "Failed to start unit: %s", bus_error_message(&error, r));
-
- r = sd_bus_message_read(reply, "o", &object);
- if (r < 0)
- return bus_log_parse_error(r);
-
- r = bus_wait_for_jobs_add(w, object);
- if (r < 0)
- return log_oom();
- }
+ 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;
+ }
- r = bus_wait_for_jobs(w, arg_quiet, NULL);
- if (r < 0)
- return r;
+ q = cat_files(path, /* dropins= */ NULL, /* flags= */ CAT_FORMAT_HAS_SECTIONS);
+ if (q < 0)
+ return r < 0 ? r : q;
+ }
- return 0;
+ return r;
}
-static int verb_enable_machine(int argc, char *argv[], uintptr_t data, void *userdata) {
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
+static int verb_clone_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- const char *method;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
sd_bus *bus = ASSERT_PTR(userdata);
int r;
- bool enable;
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
- enable = streq(argv[0], "enable");
- method = enable ? "EnableUnitFiles" : "DisableUnitFiles";
-
- r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, method);
+ r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "CloneImage");
if (r < 0)
return bus_log_create_error(r);
- r = sd_bus_message_open_container(m, 'a', "s");
+ r = sd_bus_message_append(m, "ssb", argv[1], argv[2], arg_read_only);
if (r < 0)
return bus_log_create_error(r);
- if (enable) {
- r = sd_bus_message_append(m, "s", "machines.target");
- if (r < 0)
- return bus_log_create_error(r);
- }
-
- for (int i = 1; i < argc; i++) {
- _cleanup_free_ char *unit = NULL;
+ /* This is a slow operation, hence turn off any method call timeouts */
+ r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Could not clone image: %s", bus_error_message(&error, r));
- r = make_service_name(argv[i], &unit);
- if (r < 0)
- return r;
+ return 0;
+}
- r = image_exists(bus, argv[i]);
- if (r < 0)
- return r;
- if (r == 0)
- return log_error_errno(SYNTHETIC_ERRNO(ENXIO),
- "Machine image '%s' does not exist.",
- argv[i]);
+static int verb_rename_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ sd_bus *bus = ASSERT_PTR(userdata);
+ int r;
- r = sd_bus_message_append(m, "s", unit);
- if (r < 0)
- return bus_log_create_error(r);
- }
+ (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
- r = sd_bus_message_close_container(m);
+ r = bus_call_method(
+ bus,
+ bus_machine_mgr,
+ "RenameImage",
+ &error,
+ NULL,
+ "ss", argv[1], argv[2]);
if (r < 0)
- return bus_log_create_error(r);
+ return log_error_errno(r, "Could not rename image: %s", bus_error_message(&error, r));
- if (enable)
- r = sd_bus_message_append(m, "bb", false, false);
- else
- r = sd_bus_message_append(m, "b", false);
- if (r < 0)
- return bus_log_create_error(r);
+ return 0;
+}
- r = sd_bus_call(bus, m, 0, &error, &reply);
- if (r < 0)
- return log_error_errno(r, "Failed to enable or disable unit: %s", bus_error_message(&error, r));
+static int verb_read_only_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ sd_bus *bus = ASSERT_PTR(userdata);
+ int b = true, r;
- if (enable) {
- r = sd_bus_message_read(reply, "b", NULL);
- if (r < 0)
- return bus_log_parse_error(r);
+ if (argc > 2) {
+ b = parse_boolean(argv[2]);
+ if (b < 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Failed to parse boolean argument: %s",
+ argv[2]);
}
- r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet);
- if (r < 0)
- return r;
+ (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
- r = bus_service_manager_reload(bus);
+ r = bus_call_method(bus, bus_machine_mgr, "MarkImageReadOnly", &error, NULL, "sb", argv[1], b);
if (r < 0)
- return r;
+ return log_error_errno(r, "Could not mark image read-only: %s", bus_error_message(&error, r));
- if (arg_now) {
- _cleanup_strv_free_ char **new_args = NULL;
+ return 0;
+}
- new_args = strv_new(enable ? "start" : "poweroff");
- if (!new_args)
- return log_oom();
+static int verb_remove_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
+ sd_bus *bus = ASSERT_PTR(userdata);
+ int r;
- r = strv_extend_strv(&new_args, argv + 1, /* filter_duplicates= */ false);
+ (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+ for (int i = 1; i < argc; i++) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+
+ r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "RemoveImage");
if (r < 0)
- return log_oom();
+ return bus_log_create_error(r);
- if (enable)
- return verb_start_machine(strv_length(new_args), new_args, data, userdata);
+ r = sd_bus_message_append(m, "s", argv[i]);
+ if (r < 0)
+ return bus_log_create_error(r);
- return verb_poweroff_machine(strv_length(new_args), new_args, data, userdata);
+ /* This is a slow operation, hence turn off any method call timeouts */
+ r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Could not remove image: %s", bus_error_message(&error, r));
}
return 0;
return 0;
}
+static int verb_bind_volume(int argc, char *argv[], uintptr_t _data, void *userdata) {
+ int r;
+
+ if (arg_transport != BUS_TRANSPORT_LOCAL)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "bind-volume is only supported on the local transport.");
+
+ _cleanup_(bind_volume_freep) BindVolume *bv = NULL;
+ r = bind_volume_parse(argv[2], &bv);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse bind-volume argument '%s': %m", argv[2]);
+
+ (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+ /* Locate and connect to the target machine before acquiring storage, so a missing
+ * machine doesn't trigger 'create=new' side effects on the StorageProvider. */
+ _cleanup_free_ char *address = NULL;
+ r = machine_get_control_address(argv[1], &address);
+ if (r == -EOPNOTSUPP)
+ return log_error_errno(r, "Machine '%s' does not expose a varlink control socket.", argv[1]);
+ if (r < 0)
+ return r;
+
+ _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
+ r = sd_varlink_connect_address(&vl, address);
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to machine control socket %s: %m", address);
+
+ r = sd_varlink_set_allow_fd_passing_output(vl, true);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enable fd passing on varlink connection: %m");
+
+ _cleanup_(storage_acquire_reply_done) StorageAcquireReply reply = STORAGE_ACQUIRE_REPLY_INIT;
+ _cleanup_free_ char *acquire_error_id = NULL;
+ r = storage_acquire_volume(arg_runtime_scope, bv, arg_ask_password, &acquire_error_id, &reply);
+ if (r < 0) {
+ if (acquire_error_id)
+ return log_error_errno(r, "Failed to acquire storage volume '%s:%s' from provider: %s",
+ bv->provider, bv->volume, acquire_error_id);
+ return log_error_errno(r, "Failed to acquire storage volume '%s:%s' from provider: %m",
+ bv->provider, bv->volume);
+ }
+
+ int fd_index = sd_varlink_push_fd(vl, reply.fd);
+ if (fd_index < 0)
+ return log_error_errno(fd_index, "Failed to push storage fd onto varlink connection: %m");
+ TAKE_FD(reply.fd);
+
+ _cleanup_free_ char *name = strjoin(bv->provider, ":", bv->volume);
+ if (!name)
+ return log_oom();
+
+ sd_json_variant *vl_reply = NULL;
+ const char *error_id = NULL;
+ r = sd_varlink_callbo(
+ vl,
+ "io.systemd.MachineInstance.AddStorage",
+ &vl_reply, &error_id,
+ SD_JSON_BUILD_PAIR_INTEGER("fileDescriptorIndex", fd_index),
+ SD_JSON_BUILD_PAIR_STRING("name", name),
+ JSON_BUILD_PAIR_STRING_NON_EMPTY("config", bv->config));
+ if (r < 0)
+ return log_error_errno(r, "Failed to call io.systemd.MachineInstance.AddStorage: %m");
+ if (error_id)
+ return log_error_errno(sd_varlink_error_to_errno(error_id, vl_reply),
+ "AddStorage failed for '%s': %s", name, error_id);
+
+ return 0;
+}
+
+static int verb_unbind_volume(int argc, char *argv[], uintptr_t _data, void *userdata) {
+ int r;
+
+ if (arg_transport != BUS_TRANSPORT_LOCAL)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "unbind-volume is only supported on the local transport.");
+
+ r = machine_storage_name_split(argv[2], /* ret_provider= */ NULL, /* ret_volume= */ NULL);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Invalid unbind-volume name '%s', expected '<provider>:<volume>'.", argv[2]);
+
+ (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+ _cleanup_free_ char *address = NULL;
+ r = machine_get_control_address(argv[1], &address);
+ if (r == -EOPNOTSUPP)
+ return log_error_errno(r, "Machine '%s' does not expose a varlink control socket.", argv[1]);
+ if (r < 0)
+ return r;
+
+ _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
+ r = sd_varlink_connect_address(&vl, address);
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to machine control socket %s: %m", address);
+
+ sd_json_variant *reply = NULL;
+ const char *error_id = NULL;
+ r = sd_varlink_callbo(
+ vl,
+ "io.systemd.MachineInstance.RemoveStorage",
+ &reply, &error_id,
+ SD_JSON_BUILD_PAIR_STRING("name", argv[2]));
+ if (r < 0)
+ return log_error_errno(r, "Failed to call io.systemd.MachineInstance.RemoveStorage: %m");
+ if (error_id)
+ return log_error_errno(sd_varlink_error_to_errno(error_id, reply),
+ "RemoveStorage failed for '%s': %s", argv[2], error_id);
+
+ return 0;
+}
+
static int chainload_importctl(int argc, char *argv[]) {
int r;