diff --git a/.gitignore b/.gitignore index f7db68b4a6..6caa8218bd 100644 --- a/.gitignore +++ b/.gitignore @@ -89,6 +89,7 @@ /systemd-machine-id-setup /systemd-machined /systemd-modules-load +/systemd-mount /systemd-networkd /systemd-networkd-wait-online /systemd-notify diff --git a/Makefile-man.am b/Makefile-man.am index 3ac1906a4a..bf8de1e58f 100644 --- a/Makefile-man.am +++ b/Makefile-man.am @@ -121,6 +121,7 @@ MANPAGES += \ man/systemd-journald.service.8 \ man/systemd-machine-id-commit.service.8 \ man/systemd-machine-id-setup.1 \ + man/systemd-mount.1 \ man/systemd-notify.1 \ man/systemd-nspawn.1 \ man/systemd-path.1 \ @@ -2651,6 +2652,7 @@ EXTRA_DIST += \ man/systemd-machine-id-setup.xml \ man/systemd-machined.service.xml \ man/systemd-modules-load.service.xml \ + man/systemd-mount.xml \ man/systemd-networkd-wait-online.service.xml \ man/systemd-networkd.service.xml \ man/systemd-notify.xml \ diff --git a/Makefile.am b/Makefile.am index a1bb21e4fc..431975de8b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -384,6 +384,7 @@ bin_PROGRAMS = \ systemd-delta \ systemd-analyze \ systemd-run \ + systemd-mount \ systemd-stdio-bridge \ systemd-path @@ -3145,6 +3146,13 @@ systemd_run_SOURCES = \ systemd_run_LDADD = \ libsystemd-shared.la +# ------------------------------------------------------------------------------ +systemd_mount_SOURCES = \ + src/mount/mount-tool.c + +systemd_mount_LDADD = \ + libsystemd-shared.la + # ------------------------------------------------------------------------------ systemd_stdio_bridge_SOURCES = \ src/stdio-bridge/stdio-bridge.c diff --git a/man/standard-options.xml b/man/standard-options.xml index f214463392..f718451a1b 100644 --- a/man/standard-options.xml +++ b/man/standard-options.xml @@ -28,6 +28,12 @@ + + + + Do not query the user for authentication for privileged operations. + + diff --git a/man/systemd-mount.xml b/man/systemd-mount.xml new file mode 100644 index 0000000000..e6c07863c0 --- /dev/null +++ b/man/systemd-mount.xml @@ -0,0 +1,295 @@ + + + + + + + + + systemd-mount + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd-mount + 1 + + + + systemd-mount + Establish a mount or auto-mount point transiently + + + + + systemd-mount + OPTIONS + WHAT + WHERE + + + systemd-mount + OPTIONS + + + + + + Description + + systemd-mount may be used to create and start a transient .mount or + .automount unit of the file system WHAT on the mount point + WHERE. + + In many ways, systemd-mount is similar to the lower-level + mount8 command, however instead + of executing the mount operation directly and immediately, systemd-mount schedules it through + the service manager job queue, so that it may pull in further dependencies (such as parent mounts, or a file system + checker to execute a priori), and may make use of the auto-mounting logic. + + The command takes either one or two arguments. If only one argument is specified it should refer to a block + device containing a file system (e.g. /dev/sdb1), which is then probed for a label and other + metadata, and is mounted to a directory whose name is generated from the label. In this mode the block device must + exist at the time of invocation of the command, so that it may be probed. If the device is found to be a removable + block device (e.g. a USB stick) an automount point instead of a regular mount point is created (i.e. the + option is implied, see below). + + If two arguments are specified the first indicates the mount source (the WHAT) and + the second indicates the path to mount it on (the WHERE). In this mode no probing of the + source is attempted, and a backing device node doesn't have to exist yet. However, if this mode is combined with + , device node probing for additional metadata is enabled, and – much like in the + single-argument case discussed above – the specified device has to exist at the time of invocation of the + command. + + Use the command to show a terse table of all local, known block devices with file + systems that may be mounted with this command. + + + + Options + + The following options are understood: + + + + + + + + Do not synchronously wait for the requested operation to finish. If this is not specified, the job will + be verified, enqueued and systemd-mount will wait until the mount or automount unit's + start-up is completed. By passing this argument, it is only verified and enqueued. + + + + + + + + + + + Suppresses additional informational output while running. + + + + + + Enable probing of the mount source. This switch is implied if a single argument is specified on + the command line. If passed, additional metadata is read from the device to enhance the unit to create. For + example, a descriptive string for the transient units is generated from the file system label and device + model. Moreover if a removable block device (e.g. USB stick) is detected an automount unit instead of a regular + mount unit is created, with a short idle time-out, in order to ensure the file-system is placed in a clean + state quickly after each access. + + + + + + + Specifies the file system type to mount (e.g. vfat, ext4, + …). If omitted (or set to auto) the file system is determined automatically. + + + + + + + Additional mount options for the mount point. + + + + + + Takes a boolean argument, defaults to on. Controls whether to run a file system check + immediately before the mount operation. In the automount case (see below) the + check will be run the moment the first access to the device is made, which might slightly delay the + access. + + + + + + Provide a description for the mount or automount unit. See Description= in + systemd.unit5. + + + + + + + + Sets a unit property for the mount unit that is created. This takes an assignment in the same + format as systemctl1's + set-property command. + + + + + + + Takes a boolean argument. Controls whether to create an automount point or a regular mount + point. If true an automount point is created that is backed by the actual file system at the time of first + access. If false a plain mount point is created that is backed by the actual file system immediately. Automount + points have the benefit that the file system stays unmounted and hence in clean state until it is first + accessed. In automount mode the switch (see below) may be used to ensure + the mount point is unmounted automatically after the last access and an idle period passed. + + If this switch is not specified it defaults to false. If not specified and is + used (or only a single argument passed, which implies , see above), and the file + system block device is detected to be removable, it is set to true, in order to increase the chance that the + file system is in a fully clean state if the device is unplugged abruptly. + + + + + + Equivalent to . + + + + + + Takes a time value that controls the idle timeout in automount mode. If set to + infinity (the default) no automatic unmounts are done. Otherwise the file system backing the + automount point is detached after the last access and the idle timeout passed. See + systemd.time7 for details on + the time syntax supported. This option has no effect if only a regular mount is established, and automounting + is not used. + + Note that if is used (or only a single argument passed, which implies + , see above), and the file system block device is detected to be removable, + is implied. + + + + + + Similar to , but applies additional properties to the automount + unit created, instead of the mount unit. + + + + + + Takes a boolean argument, defaults to off. This option only has an effect in automount mode, + and controls whether the automount unit shall be bound to the backing device's lifetime. If enabled, the + automount point will be removed automatically when the backing device vanishes. If disabled the automount point + stays around, and subsequent accesses will block until backing device is replugged. This option has no effect + in case of non-device mounts, such as network or virtual file system mounts. + + Note that if is used (or only a single argument passed, which implies + , see above), and the file system block device is detected to be removable, this + option is implied. + + + + + + Instead of establishing a mount or automount point, print a terse list of block devices + containing file systems that may be mounted with systemd-mount, along with useful metadata + such as labels, etc. + + + + + + + + + + + + + + + Exit status + + On success, 0 is returned, a non-zero failure + code otherwise. + + + + The udev Database + + If is used, systemd-mount honours a couple of additional udev + properties of block devices: + + + + SYSTEMD_MOUNT_OPTIONS= + + The mount options to use, if is not used. + + + + SYSTEMD_MOUNT_WHERE= + + The file system path to place the mount point at, instead of the automatically generated + one. + + + + + + See Also + + systemd1, + mount8, + systemctl1, + systemd.unit5, + systemd.mount5, + systemd.automount5, + systemd-run1 + + + + diff --git a/man/systemd-run.xml b/man/systemd-run.xml index 9c1a29218e..56d585cdf9 100644 --- a/man/systemd-run.xml +++ b/man/systemd-run.xml @@ -45,7 +45,7 @@ systemd-run - Run programs in transient scope or service or timer units + Run programs in transient scope units, service units, or timer-scheduled service units @@ -452,6 +452,7 @@ There is a screen on: systemd.exec5, systemd.resource-control5, systemd.timer5, + systemd-mount1, machinectl1 diff --git a/src/basic/mount-util.c b/src/basic/mount-util.c index 28dc778969..bfa04394fe 100644 --- a/src/basic/mount-util.c +++ b/src/basic/mount-util.c @@ -75,7 +75,6 @@ static int fd_fdinfo_mnt_id(int fd, const char *filename, int flags, int *mnt_id return safe_atoi(p, mnt_id); } - int fd_is_mount_point(int fd, const char *filename, int flags) { union file_handle_union h = FILE_HANDLE_INIT, h_parent = FILE_HANDLE_INIT; int mount_id = -1, mount_id_parent = -1; diff --git a/src/mount/Makefile b/src/mount/Makefile new file mode 120000 index 0000000000..d0b0e8e008 --- /dev/null +++ b/src/mount/Makefile @@ -0,0 +1 @@ +../Makefile \ No newline at end of file diff --git a/src/mount/mount-tool.c b/src/mount/mount-tool.c new file mode 100644 index 0000000000..9076ba3b0b --- /dev/null +++ b/src/mount/mount-tool.c @@ -0,0 +1,1112 @@ +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include + +#include "libudev.h" +#include "sd-bus.h" + +#include "bus-error.h" +#include "bus-unit-util.h" +#include "bus-util.h" +#include "escape.h" +#include "fstab-util.h" +#include "pager.h" +#include "parse-util.h" +#include "path-util.h" +#include "spawn-polkit-agent.h" +#include "strv.h" +#include "udev-util.h" +#include "unit-name.h" +#include "terminal-util.h" + +enum { + ACTION_DEFAULT, + ACTION_MOUNT, + ACTION_AUTOMOUNT, + ACTION_LIST, +} arg_action = ACTION_DEFAULT; + +static bool arg_no_block = false; +static bool arg_no_pager = false; +static bool arg_ask_password = true; +static bool arg_quiet = false; +static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; +static bool arg_user = false; +static const char *arg_host = NULL; +static bool arg_discover = false; +static char *arg_mount_what = NULL; +static char *arg_mount_where = NULL; +static char *arg_mount_type = NULL; +static char *arg_mount_options = NULL; +static char *arg_description = NULL; +static char **arg_property = NULL; +static usec_t arg_timeout_idle = USEC_INFINITY; +static bool arg_timeout_idle_set = false; +static char **arg_automount_property = NULL; +static int arg_bind_device = -1; +static bool arg_fsck = true; + +static void polkit_agent_open_if_enabled(void) { + + /* Open the polkit agent as a child process if necessary */ + if (!arg_ask_password) + return; + + if (arg_transport != BUS_TRANSPORT_LOCAL) + return; + + polkit_agent_open(); +} + +static void help(void) { + printf("%s [OPTIONS...] WHAT [WHERE]\n\n" + "Establish a mount or auto-mount point transiently.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-block Do not wait until operation finished\n" + " --no-pager Do not pipe output into a pager\n" + " --no-ask-password Do not prompt for password\n" + " -q --quiet Suppress information messages during runtime\n" + " --user Run as user unit\n" + " -H --host=[USER@]HOST Operate on remote host\n" + " -M --machine=CONTAINER Operate on local container\n" + " --discover Discover mount device metadata\n" + " -t --type=TYPE File system type\n" + " -o --options=OPTIONS Mount options\n" + " --fsck=no Don't run file system check before mount\n" + " --description=TEXT Description for unit\n" + " -p --property=NAME=VALUE Set mount unit property\n" + " -A --automount=BOOL Create an auto-mount point\n" + " --timeout-idle-sec=SEC Specify automount idle timeout\n" + " --automount-property=NAME=VALUE\n" + " Set automount unit property\n" + " --bind-device Bind automount unit to device\n" + " --list List mountable block devices\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_NO_BLOCK, + ARG_NO_PAGER, + ARG_NO_ASK_PASSWORD, + ARG_USER, + ARG_SYSTEM, + ARG_DISCOVER, + ARG_MOUNT_TYPE, + ARG_MOUNT_OPTIONS, + ARG_FSCK, + ARG_DESCRIPTION, + ARG_TIMEOUT_IDLE, + ARG_AUTOMOUNT, + ARG_AUTOMOUNT_PROPERTY, + ARG_BIND_DEVICE, + ARG_LIST, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "no-block", no_argument, NULL, ARG_NO_BLOCK }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, + { "quiet", no_argument, NULL, 'q' }, + { "user", no_argument, NULL, ARG_USER }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "host", required_argument, NULL, 'H' }, + { "machine", required_argument, NULL, 'M' }, + { "discover", no_argument, NULL, ARG_DISCOVER }, + { "type", required_argument, NULL, 't' }, + { "options", required_argument, NULL, 'o' }, + { "description", required_argument, NULL, ARG_DESCRIPTION }, + { "property", required_argument, NULL, 'p' }, + { "automount", required_argument, NULL, ARG_AUTOMOUNT }, + { "timeout-idle-sec", required_argument, NULL, ARG_TIMEOUT_IDLE }, + { "automount-property", required_argument, NULL, ARG_AUTOMOUNT_PROPERTY }, + { "bind-device", no_argument, NULL, ARG_BIND_DEVICE }, + { "list", no_argument, NULL, ARG_LIST }, + {}, + }; + + int r, c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hqH:M:t:o:p:A", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_NO_BLOCK: + arg_no_block = true; + break; + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case ARG_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + case 'q': + arg_quiet = true; + break; + + case ARG_USER: + arg_user = true; + break; + + case ARG_SYSTEM: + arg_user = false; + break; + + case 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + arg_transport = BUS_TRANSPORT_MACHINE; + arg_host = optarg; + break; + + case ARG_DISCOVER: + arg_discover = true; + break; + + case 't': + if (free_and_strdup(&arg_mount_type, optarg) < 0) + return log_oom(); + break; + + case 'o': + if (free_and_strdup(&arg_mount_options, optarg) < 0) + return log_oom(); + break; + + case ARG_FSCK: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --fsck= argument: %s", optarg); + + arg_fsck = r; + break; + + case ARG_DESCRIPTION: + if (free_and_strdup(&arg_description, optarg) < 0) + return log_oom(); + break; + + case 'p': + if (strv_extend(&arg_property, optarg) < 0) + return log_oom(); + + break; + + case 'A': + arg_action = ACTION_AUTOMOUNT; + break; + + case ARG_AUTOMOUNT: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "--automount= expects a valid boolean parameter: %s", optarg); + + arg_action = r ? ACTION_AUTOMOUNT : ACTION_MOUNT; + break; + + case ARG_TIMEOUT_IDLE: + r = parse_sec(optarg, &arg_timeout_idle); + if (r < 0) + return log_error_errno(r, "Failed to parse timeout: %s", optarg); + + break; + + case ARG_AUTOMOUNT_PROPERTY: + if (strv_extend(&arg_automount_property, optarg) < 0) + return log_oom(); + + break; + + case ARG_BIND_DEVICE: + arg_bind_device = true; + break; + + case ARG_LIST: + arg_action = ACTION_LIST; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (arg_user && arg_transport != BUS_TRANSPORT_LOCAL) { + log_error("Execution in user context is not supported on non-local systems."); + return -EINVAL; + } + + if (arg_action == ACTION_LIST) { + if (optind < argc) { + log_error("Too many arguments."); + return -EINVAL; + } + + if (arg_transport != BUS_TRANSPORT_LOCAL) { + log_error("Listing devices only supported locally."); + return -EOPNOTSUPP; + } + } else { + if (optind >= argc) { + log_error("At least one argument required."); + return -EINVAL; + } + + if (argc > optind+2) { + log_error("At most two arguments required."); + return -EINVAL; + } + + arg_mount_what = fstab_node_to_udev_node(argv[optind]); + if (!arg_mount_what) + return log_oom(); + + if (argc > optind+1) { + r = path_make_absolute_cwd(argv[optind+1], &arg_mount_where); + if (r < 0) + return log_error_errno(r, "Failed to make path absolute: %m"); + } else + arg_discover = true; + + if (arg_discover && arg_transport != BUS_TRANSPORT_LOCAL) { + log_error("Automatic mount location discovery is only supported locally."); + return -EOPNOTSUPP; + } + } + + return 1; +} + +static int transient_unit_set_properties(sd_bus_message *m, char **properties) { + int r; + + r = sd_bus_message_append(m, "(sv)", "Description", "s", arg_description); + if (r < 0) + return r; + + if (arg_bind_device && is_device_path(arg_mount_what)) { + _cleanup_free_ char *device_unit = NULL; + + r = unit_name_from_path(arg_mount_what, ".device", &device_unit); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "(sv)(sv)", + "After", "as", 1, device_unit, + "BindsTo", "as", 1, device_unit); + if (r < 0) + return r; + } + + r = bus_append_unit_property_assignment_many(m, properties); + if (r < 0) + return r; + + return 0; +} + +static int transient_mount_set_properties(sd_bus_message *m) { + int r; + + assert(m); + + r = transient_unit_set_properties(m, arg_property); + if (r < 0) + return r; + + if (arg_mount_what) { + r = sd_bus_message_append(m, "(sv)", "What", "s", arg_mount_what); + if (r < 0) + return r; + } + + if (arg_mount_type) { + r = sd_bus_message_append(m, "(sv)", "Type", "s", arg_mount_type); + if (r < 0) + return r; + } + + if (arg_mount_options) { + r = sd_bus_message_append(m, "(sv)", "Options", "s", arg_mount_options); + if (r < 0) + return r; + } + + if (arg_fsck) { + _cleanup_free_ char *fsck = NULL; + + r = unit_name_from_path_instance("systemd-fsck", arg_mount_what, ".service", &fsck); + if (r < 0) + return r; + + r = sd_bus_message_append(m, + "(sv)(sv)", + "Requires", "as", 1, fsck, + "After", "as", 1, fsck); + if (r < 0) + return r; + } + + return 0; +} + +static int transient_automount_set_properties(sd_bus_message *m) { + int r; + + assert(m); + + r = transient_unit_set_properties(m, arg_automount_property); + if (r < 0) + return r; + + if (arg_timeout_idle != USEC_INFINITY) { + r = sd_bus_message_append(m, "(sv)", "TimeoutIdleUSec", "t", arg_timeout_idle); + if (r < 0) + return r; + } + + return 0; +} + +static int start_transient_mount( + sd_bus *bus, + char **argv) { + + _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; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + _cleanup_free_ char *mount_unit = NULL; + int r; + + if (!arg_no_block) { + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_error_errno(r, "Could not watch jobs: %m"); + } + + r = unit_name_from_path(arg_mount_where, ".mount", &mount_unit); + if (r < 0) + return log_error_errno(r, "Failed to make mount unit name: %m"); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartTransientUnit"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); + if (r < 0) + return bus_log_create_error(r); + + /* Name and mode */ + r = sd_bus_message_append(m, "ss", mount_unit, "fail"); + if (r < 0) + return bus_log_create_error(r); + + /* Properties */ + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = transient_mount_set_properties(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + /* Auxiliary units */ + r = sd_bus_message_append(m, "a(sa(sv))", 0); + if (r < 0) + return bus_log_create_error(r); + + polkit_agent_open_if_enabled(); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "Failed to start transient mount unit: %s", bus_error_message(&error, r)); + + if (w) { + const char *object; + + r = sd_bus_message_read(reply, "o", &object); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_wait_for_jobs_one(w, object, arg_quiet); + if (r < 0) + return r; + } + + if (!arg_quiet) + log_info("Started unit %s%s%s for mount point: %s%s%s", + ansi_highlight(), mount_unit, ansi_normal(), + ansi_highlight(), arg_mount_where, ansi_normal()); + + return 0; +} + +static int start_transient_automount( + sd_bus *bus, + char **argv) { + + _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; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + _cleanup_free_ char *automount_unit = NULL, *mount_unit = NULL; + int r; + + if (!arg_no_block) { + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_error_errno(r, "Could not watch jobs: %m"); + } + + r = unit_name_from_path(arg_mount_where, ".automount", &automount_unit); + if (r < 0) + return log_error_errno(r, "Failed to make automount unit name: %m"); + + r = unit_name_from_path(arg_mount_where, ".mount", &mount_unit); + if (r < 0) + return log_error_errno(r, "Failed to make mount unit name: %m"); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartTransientUnit"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); + if (r < 0) + return bus_log_create_error(r); + + /* Name and mode */ + r = sd_bus_message_append(m, "ss", automount_unit, "fail"); + if (r < 0) + return bus_log_create_error(r); + + /* Properties */ + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = transient_automount_set_properties(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + /* Auxiliary units */ + r = sd_bus_message_open_container(m, 'a', "(sa(sv))"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'r', "sa(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "s", mount_unit); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = transient_mount_set_properties(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + polkit_agent_open_if_enabled(); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "Failed to start transient automount unit: %s", bus_error_message(&error, r)); + + if (w) { + const char *object; + + r = sd_bus_message_read(reply, "o", &object); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_wait_for_jobs_one(w, object, arg_quiet); + if (r < 0) + return r; + } + + if (!arg_quiet) + log_info("Started unit %s%s%s for mount point: %s%s%s", + ansi_highlight(), automount_unit, ansi_normal(), + ansi_highlight(), arg_mount_where, ansi_normal()); + + return 0; +} + +static int acquire_mount_type(struct udev_device *d) { + const char *v; + + assert(d); + + if (arg_mount_type) + return 0; + + v = udev_device_get_property_value(d, "ID_FS_TYPE"); + if (isempty(v)) + return 0; + + arg_mount_type = strdup(v); + if (!arg_mount_type) + return log_oom(); + + log_debug("Discovered type=%s", arg_mount_type); + return 1; +} + +static int acquire_mount_options(struct udev_device *d) { + const char *v; + + if (arg_mount_options) + return 0; + + v = udev_device_get_property_value(d, "SYSTEMD_MOUNT_OPTIONS"); + if (isempty(v)) + return 0; + + arg_mount_options = strdup(v); + if (!arg_mount_options) + return log_oom(); + + log_debug("Discovered options=%s", arg_mount_options); + return 1; +} + +static const char *get_model(struct udev_device *d) { + const char *model; + + assert(d); + + model = udev_device_get_property_value(d, "ID_MODEL_FROM_DATABASE"); + if (model) + return model; + + return udev_device_get_property_value(d, "ID_MODEL"); +} + +static const char* get_label(struct udev_device *d) { + const char *label; + + assert(d); + + label = udev_device_get_property_value(d, "ID_FS_LABEL"); + if (label) + return label; + + return udev_device_get_property_value(d, "ID_PART_ENTRY_NAME"); +} + +static int acquire_mount_where(struct udev_device *d) { + const char *v; + + if (arg_mount_where) + return 0; + + v = udev_device_get_property_value(d, "SYSTEMD_MOUNT_WHERE"); + if (isempty(v)) { + _cleanup_free_ char *escaped = NULL; + const char *name; + + name = get_label(d); + if (!name) + name = get_model(d); + if (!name) { + const char *dn; + + dn = udev_device_get_devnode(d); + if (!dn) + return 0; + + name = basename(dn); + } + + escaped = xescape(name, "\\"); + if (!filename_is_valid(escaped)) + return 0; + + arg_mount_where = strjoin("/run/media/system/", escaped, NULL); + } else + arg_mount_where = strdup(v); + + if (!arg_mount_where) + return log_oom(); + + log_debug("Discovered where=%s", arg_mount_where); + return 1; +} + +static int acquire_description(struct udev_device *d) { + const char *model, *label; + + if (arg_description) + return 0; + + model = get_model(d); + + label = get_label(d); + if (!label) + label = udev_device_get_property_value(d, "ID_PART_ENTRY_NUMBER"); + + if (model && label) + arg_description = strjoin(model, " ", label, NULL); + else if (label) + arg_description = strdup(label); + else if (model) + arg_description = strdup(model); + else + return NULL; + + if (!arg_description) + return log_oom(); + + log_debug("Discovered description=%s", arg_description); + return 1; +} + +static int acquire_removable(struct udev_device *d) { + const char *v; + + /* Shortcut this if there's no reason to check it */ + if (arg_action != ACTION_DEFAULT && arg_timeout_idle_set && arg_bind_device >= 0) + return 0; + + for (;;) { + v = udev_device_get_sysattr_value(d, "removable"); + if (v) + break; + + d = udev_device_get_parent(d); + if (!d) + return 0; + + if (!streq_ptr(udev_device_get_subsystem(d), "block")) + return 0; + } + + if (parse_boolean(v) <= 0) + return 0; + + log_debug("Discovered removable device."); + + if (arg_action == ACTION_DEFAULT) { + log_debug("Automatically turning on automount."); + arg_action = ACTION_AUTOMOUNT; + } + + if (!arg_timeout_idle_set) { + log_debug("Setting idle timeout to 1s."); + arg_timeout_idle = USEC_PER_SEC; + } + + if (arg_bind_device < 0) { + log_debug("Binding automount unit to device."); + arg_bind_device = true; + } + + return 1; +} + +static int discover_device(void) { + _cleanup_udev_device_unref_ struct udev_device *d = NULL; + _cleanup_udev_unref_ struct udev *udev = NULL; + struct stat st; + const char *v; + int r; + + if (!arg_discover) + return 0; + + if (!is_device_path(arg_mount_what)) { + log_error("Discovery only supported for block devices, don't know what to do."); + return -EINVAL; + } + + if (stat(arg_mount_what, &st) < 0) + return log_error_errno(errno, "Can't stat %s: %m", arg_mount_what); + + if (!S_ISBLK(st.st_mode)) { + log_error("Path %s is not a block device, don't know what to do.", arg_mount_what); + return -ENOTBLK; + } + + udev = udev_new(); + if (!udev) + return log_oom(); + + d = udev_device_new_from_devnum(udev, 'b', st.st_rdev); + if (!d) + return log_oom(); + + v = udev_device_get_property_value(d, "ID_FS_USAGE"); + if (!streq_ptr(v, "filesystem")) { + log_error("%s does not contain a file system.", arg_mount_what); + return -EINVAL; + } + + r = acquire_mount_type(d); + if (r < 0) + return r; + + r = acquire_mount_options(d); + if (r < 0) + return r; + + r = acquire_mount_where(d); + if (r < 0) + return r; + + r = acquire_description(d); + if (r < 0) + return r; + + r = acquire_removable(d); + if (r < 0) + return r; + + return 0; +} + +enum { + COLUMN_NODE, + COLUMN_PATH, + COLUMN_MODEL, + COLUMN_WWN, + COLUMN_FSTYPE, + COLUMN_LABEL, + COLUMN_UUID, + _COLUMN_MAX, +}; + +struct item { + char* columns[_COLUMN_MAX]; +}; + +static int compare_item(const void *a, const void *b) { + const struct item *x = a, *y = b; + + if (x->columns[COLUMN_NODE] == y->columns[COLUMN_NODE]) + return 0; + if (!x->columns[COLUMN_NODE]) + return 1; + if (!y->columns[COLUMN_NODE]) + return -1; + + return path_compare(x->columns[COLUMN_NODE], y->columns[COLUMN_NODE]); +} + +static int list_devices(void) { + + static const char * const titles[_COLUMN_MAX] = { + [COLUMN_NODE] = "NODE", + [COLUMN_PATH] = "PATH", + [COLUMN_MODEL] = "MODEL", + [COLUMN_WWN] = "WWN", + [COLUMN_FSTYPE] = "TYPE", + [COLUMN_LABEL] = "LABEL", + [COLUMN_UUID] = "UUID" + }; + + _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL; + _cleanup_udev_unref_ struct udev *udev = NULL; + struct udev_list_entry *item = NULL, *first = NULL; + size_t n_allocated = 0, n = 0, i; + size_t column_width[_COLUMN_MAX]; + struct item *items = NULL; + unsigned c; + int r; + + for (c = 0; c < _COLUMN_MAX; c++) + column_width[c] = strlen(titles[c]); + + udev = udev_new(); + if (!udev) + return log_oom(); + + e = udev_enumerate_new(udev); + if (!e) + return log_oom(); + + r = udev_enumerate_add_match_subsystem(e, "block"); + if (r < 0) + return log_error_errno(r, "Failed to add block match: %m"); + + r = udev_enumerate_add_match_property(e, "ID_FS_USAGE", "filesystem"); + if (r < 0) + return log_error_errno(r, "Failed to add property match: %m"); + + r = udev_enumerate_scan_devices(e); + if (r < 0) + return log_error_errno(r, "Failed to scan devices: %m"); + + first = udev_enumerate_get_list_entry(e); + udev_list_entry_foreach(item, first) { + _cleanup_udev_device_unref_ struct udev_device *d; + struct item *j; + + d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); + if (!d) { + r = log_oom(); + goto finish; + } + + if (!GREEDY_REALLOC0(items, n_allocated, n+1)) { + r = log_oom(); + goto finish; + } + + j = items + n++; + + for (c = 0; c < _COLUMN_MAX; c++) { + const char *x; + size_t k; + + switch (c) { + + case COLUMN_NODE: + x = udev_device_get_devnode(d); + break; + + case COLUMN_PATH: + x = udev_device_get_property_value(d, "ID_PATH"); + break; + + case COLUMN_MODEL: + x = get_model(d); + break; + + case COLUMN_WWN: + x = udev_device_get_property_value(d, "ID_WWN"); + break; + + case COLUMN_FSTYPE: + x = udev_device_get_property_value(d, "ID_FS_TYPE"); + break; + + case COLUMN_LABEL: + x = get_label(d); + break; + + case COLUMN_UUID: + x = udev_device_get_property_value(d, "ID_FS_UUID"); + break; + } + + if (isempty(x)) + continue; + + j->columns[c] = strdup(x); + if (!j->columns[c]) { + r = log_oom(); + goto finish; + } + + k = strlen(x); + if (k > column_width[c]) + column_width[c] = k; + } + } + + if (n == 0) { + log_info("No devices found."); + goto finish; + } + + qsort_safe(items, n, sizeof(struct item), compare_item); + + pager_open(arg_no_pager, false); + + fputs(ansi_underline(), stdout); + for (c = 0; c < _COLUMN_MAX; c++) { + if (c > 0) + fputc(' ', stdout); + + printf("%-*s", (int) column_width[c], titles[c]); + } + fputs(ansi_normal(), stdout); + fputc('\n', stdout); + + for (i = 0; i < n; i++) { + for (c = 0; c < _COLUMN_MAX; c++) { + if (c > 0) + fputc(' ', stdout); + + printf("%-*s", (int) column_width[c], strna(items[i].columns[c])); + } + fputc('\n', stdout); + } + + r = 0; + +finish: + for (i = 0; i < n; i++) + for (c = 0; c < _COLUMN_MAX; c++) + free(items[i].columns[c]); + + free(items); + return r; +} + +int main(int argc, char* argv[]) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (arg_action == ACTION_LIST) { + r = list_devices(); + goto finish; + } + + r = discover_device(); + if (r < 0) + goto finish; + if (!arg_mount_where) { + log_error("Can't figure out where to mount %s.", arg_mount_what); + r = -EINVAL; + goto finish; + } + + path_kill_slashes(arg_mount_where); + + if (path_equal(arg_mount_where, "/")) { + log_error("Refusing to operate on root directory."); + r = -EINVAL; + goto finish; + } + + if (!path_is_safe(arg_mount_where)) { + log_error("Path is contains unsafe components."); + r = -EINVAL; + goto finish; + } + + if (streq_ptr(arg_mount_type, "auto")) + arg_mount_type = mfree(arg_mount_type); + if (streq_ptr(arg_mount_options, "defaults")) + arg_mount_options = mfree(arg_mount_options); + + if (!is_device_path(arg_mount_what)) + arg_fsck = false; + + if (arg_fsck && arg_mount_type && arg_transport == BUS_TRANSPORT_LOCAL) { + r = fsck_exists(arg_mount_type); + if (r < 0) + log_warning_errno(r, "Couldn't determine whether fsck for %s exists, proceeding anyway.", arg_mount_type); + else if (r == 0) { + log_debug("Disabling file system check as fsck for %s doesn't exist.", arg_mount_type); + arg_fsck = false; /* fsck doesn't exist, let's not attempt it */ + } + } + + r = bus_connect_transport_systemd(arg_transport, arg_host, arg_user, &bus); + if (r < 0) { + log_error_errno(r, "Failed to create bus connection: %m"); + goto finish; + } + + switch (arg_action) { + + case ACTION_MOUNT: + case ACTION_DEFAULT: + r = start_transient_mount(bus, argv + optind); + break; + + case ACTION_AUTOMOUNT: + r = start_transient_automount(bus, argv + optind); + break; + + default: + assert_not_reached("Unexpected action."); + } + +finish: + bus = sd_bus_flush_close_unref(bus); + + pager_close(); + + free(arg_mount_what); + free(arg_mount_where); + free(arg_mount_type); + free(arg_mount_options); + free(arg_description); + strv_free(arg_property); + strv_free(arg_automount_property); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/nspawn/nspawn-register.c b/src/nspawn/nspawn-register.c index e5b76a0c5d..06c56d9ec8 100644 --- a/src/nspawn/nspawn-register.c +++ b/src/nspawn/nspawn-register.c @@ -68,7 +68,6 @@ int register_machine( local_ifindex > 0 ? 1 : 0, local_ifindex); } else { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - char **i; unsigned j; r = sd_bus_message_new_method_call( @@ -157,11 +156,9 @@ int register_machine( return bus_log_create_error(r); } - STRV_FOREACH(i, properties) { - r = bus_append_unit_property_assignment(m, *i); - if (r < 0) - return r; - } + r = bus_append_unit_property_assignment_many(m, properties); + if (r < 0) + return r; r = sd_bus_message_close_container(m); if (r < 0) diff --git a/src/run/run.c b/src/run/run.c index 1917ffd857..0797547684 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -83,9 +83,7 @@ static void polkit_agent_open_if_enabled(void) { static void help(void) { printf("%s [OPTIONS...] {COMMAND} [ARGS...]\n\n" - "Run the specified command in a transient scope or service or timer\n" - "unit. If a timer option is specified and the unit specified with\n" - "the --unit option exists, the command can be omitted.\n\n" + "Run the specified command in a transient scope or service.\n\n" " -h --help Show this help\n" " --version Show package version\n" " --no-ask-password Do not prompt for password\n" @@ -94,7 +92,7 @@ static void help(void) { " -M --machine=CONTAINER Operate on local container\n" " --scope Run this as scope rather than service\n" " --unit=UNIT Run under the specified unit name\n" - " -p --property=NAME=VALUE Set unit property\n" + " -p --property=NAME=VALUE Set service or scope unit property\n" " --description=TEXT Description for unit\n" " --slice=SLICE Run in the specified slice\n" " --no-block Do not wait until operation finished\n" @@ -107,15 +105,15 @@ static void help(void) { " -E --setenv=NAME=VALUE Set environment\n" " -t --pty Run service on pseudo tty\n" " -q --quiet Suppress information messages during runtime\n\n" - "Timer options:\n\n" + "Timer options:\n" " --on-active=SECONDS Run after SECONDS delay\n" " --on-boot=SECONDS Run SECONDS after machine was booted up\n" " --on-startup=SECONDS Run SECONDS after systemd activation\n" " --on-unit-active=SECONDS Run SECONDS after the last activation\n" " --on-unit-inactive=SECONDS Run SECONDS after the last deactivation\n" " --on-calendar=SPEC Realtime timer\n" - " --timer-property=NAME=VALUE Set timer unit property\n", - program_invocation_short_name); + " --timer-property=NAME=VALUE Set timer unit property\n" + , program_invocation_short_name); } static bool with_timer(void) { @@ -178,7 +176,7 @@ static int parse_argv(int argc, char *argv[]) { { "on-calendar", required_argument, NULL, ARG_ON_CALENDAR }, { "timer-property", required_argument, NULL, ARG_TIMER_PROPERTY }, { "no-block", no_argument, NULL, ARG_NO_BLOCK }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, + { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, {}, }; @@ -195,13 +193,13 @@ static int parse_argv(int argc, char *argv[]) { help(); return 0; + case ARG_VERSION: + return version(); + case ARG_NO_ASK_PASSWORD: arg_ask_password = false; break; - case ARG_VERSION: - return version(); - case ARG_USER: arg_user = true; break; @@ -410,18 +408,15 @@ static int parse_argv(int argc, char *argv[]) { } static int transient_unit_set_properties(sd_bus_message *m, char **properties) { - char **i; int r; r = sd_bus_message_append(m, "(sv)", "Description", "s", arg_description); if (r < 0) return r; - STRV_FOREACH(i, properties) { - r = bus_append_unit_property_assignment(m, *i); - if (r < 0) - return r; - } + r = bus_append_unit_property_assignment_many(m, properties); + if (r < 0) + return r; return 0; } @@ -1144,7 +1139,7 @@ static int start_transient_timer( if (r < 0) return bus_log_create_error(r); - if (argv[0]) { + if (!strv_isempty(argv)) { r = sd_bus_message_open_container(m, 'r', "sa(sv)"); if (r < 0) return bus_log_create_error(r); diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index ab30afb527..feb4a06737 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -568,6 +568,21 @@ finish: return 0; } +int bus_append_unit_property_assignment_many(sd_bus_message *m, char **l) { + char **i; + int r; + + assert(m); + + STRV_FOREACH(i, l) { + r = bus_append_unit_property_assignment(m, *i); + if (r < 0) + return r; + } + + return 0; +} + typedef struct BusWaitForJobs { sd_bus *bus; Set *jobs; diff --git a/src/shared/bus-unit-util.h b/src/shared/bus-unit-util.h index c0c172f336..d102ea180e 100644 --- a/src/shared/bus-unit-util.h +++ b/src/shared/bus-unit-util.h @@ -41,6 +41,7 @@ typedef struct UnitInfo { int bus_parse_unit_info(sd_bus_message *message, UnitInfo *u); int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignment); +int bus_append_unit_property_assignment_many(sd_bus_message *m, char **l); typedef struct BusWaitForJobs BusWaitForJobs; diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index b4ce6fba5a..4444e3064d 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -5093,7 +5093,6 @@ static int set_property(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_free_ char *n = NULL; sd_bus *bus; - char **i; int r; r = acquire_bus(BUS_MANAGER, &bus); @@ -5124,11 +5123,9 @@ static int set_property(int argc, char *argv[], void *userdata) { if (r < 0) return bus_log_create_error(r); - STRV_FOREACH(i, strv_skip(argv, 2)) { - r = bus_append_unit_property_assignment(m, *i); - if (r < 0) - return r; - } + r = bus_append_unit_property_assignment_many(m, strv_skip(argv, 2)); + if (r < 0) + return r; r = sd_bus_message_close_container(m); if (r < 0)