/* SPDX-License-Identifier: LGPL-2.1+ */
-/***
- This file is part of systemd.
-
- Copyright 2013 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 <http://www.gnu.org/licenses/>.
-***/
#include <getopt.h>
#include <stdio.h>
#include "format-util.h"
#include "parse-util.h"
#include "path-util.h"
+#include "pretty-print.h"
#include "process-util.h"
#include "ptyfwd.h"
#include "signal-util.h"
#include "spawn-polkit-agent.h"
#include "strv.h"
#include "terminal-util.h"
+#include "unit-def.h"
#include "unit-name.h"
#include "user-util.h"
ARG_STDIO_DIRECT, /* Directly pass our stdin/stdout/stderr to the activated service, useful for usage in shell pipelines, requested by --pipe */
ARG_STDIO_AUTO, /* If --pipe and --pty are used together we use --pty when invoked on a TTY, and --pipe otherwise */
} arg_stdio = ARG_STDIO_NONE;
+static char **arg_path_property = NULL;
+static char **arg_socket_property = NULL;
static char **arg_timer_property = NULL;
static bool with_timer = false;
static bool arg_quiet = false;
static bool arg_aggressive_gc = false;
-static void help(void) {
+static int help(void) {
+ _cleanup_free_ char *link = NULL;
+ int r;
+
+ r = terminal_urlify_man("systemd-run", "1", &link);
+ if (r < 0)
+ return log_oom();
+
printf("%s [OPTIONS...] {COMMAND} [ARGS...]\n\n"
"Run the specified command in a transient scope or service.\n\n"
" -h --help Show this help\n"
" -P --pipe Pass STDIN/STDOUT/STDERR directly to service\n"
" -q --quiet Suppress information messages during runtime\n"
" -G --collect Unload unit after it ran, even when failed\n\n"
+ "Path options:\n"
+ " --path-property=NAME=VALUE Set path unit property\n\n"
+ "Socket options:\n"
+ " --socket-property=NAME=VALUE Set socket unit property\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-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);
+ "\nSee the %s for details.\n"
+ , program_invocation_short_name
+ , link
+ );
+
+ return 0;
}
static int add_timer_property(const char *name, const char *val) {
- _cleanup_free_ char *p = NULL;
+ char *p;
assert(name);
assert(val);
if (strv_consume(&arg_timer_property, p) < 0)
return log_oom();
- p = NULL;
-
return 0;
}
ARG_ON_UNIT_INACTIVE,
ARG_ON_CALENDAR,
ARG_TIMER_PROPERTY,
+ ARG_PATH_PROPERTY,
+ ARG_SOCKET_PROPERTY,
ARG_NO_BLOCK,
ARG_NO_ASK_PASSWORD,
ARG_WAIT,
{ "on-unit-inactive", required_argument, NULL, ARG_ON_UNIT_INACTIVE },
{ "on-calendar", required_argument, NULL, ARG_ON_CALENDAR },
{ "timer-property", required_argument, NULL, ARG_TIMER_PROPERTY },
+ { "path-property", required_argument, NULL, ARG_PATH_PROPERTY },
+ { "socket-property", required_argument, NULL, ARG_SOCKET_PROPERTY },
{ "no-block", no_argument, NULL, ARG_NO_BLOCK },
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
{ "collect", no_argument, NULL, 'G' },
{},
};
+ bool with_trigger = false;
int r, c;
assert(argc >= 0);
switch (c) {
case 'h':
- help();
- return 0;
+ return help();
case ARG_VERSION:
return version();
!!startswith(optarg, "OnCalendar=");
break;
+ case ARG_PATH_PROPERTY:
+
+ if (strv_extend(&arg_path_property, optarg) < 0)
+ return log_oom();
+
+ break;
+
+ case ARG_SOCKET_PROPERTY:
+
+ if (strv_extend(&arg_socket_property, optarg) < 0)
+ return log_oom();
+
+ break;
+
case ARG_NO_BLOCK:
arg_no_block = true;
break;
assert_not_reached("Unhandled option");
}
+ with_trigger = !!arg_path_property || !!arg_socket_property || with_timer;
+
+ /* currently, only single trigger (path, socket, timer) unit can be created simultaneously */
+ if ((int) !!arg_path_property + (int) !!arg_socket_property + (int) with_timer > 1)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Only single trigger (path, socket, timer) unit can be created.");
if (arg_stdio == ARG_STDIO_AUTO) {
/* If we both --pty and --pipe are specified we'll automatically pick --pty if we are connected fully
ARG_STDIO_DIRECT;
}
- if ((optind >= argc) && (!arg_unit || !with_timer)) {
- log_error("Command line to execute required.");
- return -EINVAL;
- }
+ if ((optind >= argc) && (!arg_unit || !with_trigger))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Command line to execute required.");
- 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_user && arg_transport != BUS_TRANSPORT_LOCAL)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Execution in user context is not supported on non-local systems.");
- if (arg_scope && arg_transport != BUS_TRANSPORT_LOCAL) {
- log_error("Scope execution is not supported on non-local systems.");
- return -EINVAL;
- }
+ if (arg_scope && arg_transport != BUS_TRANSPORT_LOCAL)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Scope execution is not supported on non-local systems.");
- if (arg_scope && (arg_remain_after_exit || arg_service_type)) {
- log_error("--remain-after-exit and --service-type= are not supported in --scope mode.");
- return -EINVAL;
- }
+ if (arg_scope && (arg_remain_after_exit || arg_service_type))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--remain-after-exit and --service-type= are not supported in --scope mode.");
- if (arg_stdio != ARG_STDIO_NONE && (with_timer || arg_scope)) {
- log_error("--pty/--pipe is not compatible in timer or --scope mode.");
- return -EINVAL;
- }
+ if (arg_stdio != ARG_STDIO_NONE && (with_trigger || arg_scope))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--pty/--pipe is not compatible in timer or --scope mode.");
- if (arg_stdio != ARG_STDIO_NONE && arg_transport == BUS_TRANSPORT_REMOTE) {
- log_error("--pty/--pipe is only supported when connecting to the local system or containers.");
- return -EINVAL;
- }
+ if (arg_stdio != ARG_STDIO_NONE && arg_transport == BUS_TRANSPORT_REMOTE)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--pty/--pipe is only supported when connecting to the local system or containers.");
- if (arg_stdio != ARG_STDIO_NONE && arg_no_block) {
- log_error("--pty/--pipe is not compatible with --no-block.");
- return -EINVAL;
- }
+ if (arg_stdio != ARG_STDIO_NONE && arg_no_block)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--pty/--pipe is not compatible with --no-block.");
- if (arg_scope && with_timer) {
- log_error("Timer options are not supported in --scope mode.");
- return -EINVAL;
- }
+ if (arg_scope && with_trigger)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Path, socket or timer options are not supported in --scope mode.");
- if (arg_timer_property && !with_timer) {
- log_error("--timer-property= has no effect without any other timer options.");
- return -EINVAL;
- }
+ if (arg_timer_property && !with_timer)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--timer-property= has no effect without any other timer options.");
if (arg_wait) {
- if (arg_no_block) {
- log_error("--wait may not be combined with --no-block.");
- return -EINVAL;
- }
+ if (arg_no_block)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--wait may not be combined with --no-block.");
- if (with_timer) {
- log_error("--wait may not be combined with timer operations.");
- return -EINVAL;
- }
+ if (with_trigger)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--wait may not be combined with path, socket or timer operations.");
- if (arg_scope) {
- log_error("--wait may not be combined with --scope.");
- return -EINVAL;
- }
+ if (arg_scope)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--wait may not be combined with --scope.");
}
return 1;
}
-static int transient_unit_set_properties(sd_bus_message *m, char **properties) {
+static int transient_unit_set_properties(sd_bus_message *m, UnitType t, char **properties) {
int r;
r = sd_bus_message_append(m, "(sv)", "Description", "s", arg_description);
return bus_log_create_error(r);
}
- r = bus_append_unit_property_assignment_many(m, properties);
+ r = bus_append_unit_property_assignment_many(m, t, properties);
if (r < 0)
return r;
if (!isempty(arg_slice)) {
_cleanup_free_ char *slice = NULL;
- r = unit_name_mangle_with_suffix(arg_slice, UNIT_NAME_NOGLOB, ".slice", &slice);
+ r = unit_name_mangle_with_suffix(arg_slice, arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN, ".slice", &slice);
if (r < 0)
return log_error_errno(r, "Failed to mangle name '%s': %m", arg_slice);
assert(m);
- r = transient_unit_set_properties(m, arg_property);
+ r = transient_unit_set_properties(m, UNIT_SERVICE, arg_property);
if (r < 0)
return r;
assert(m);
- r = transient_unit_set_properties(m, arg_property);
+ r = transient_unit_set_properties(m, UNIT_SCOPE, arg_property);
if (r < 0)
return r;
assert(m);
- r = transient_unit_set_properties(m, arg_timer_property);
+ r = transient_unit_set_properties(m, UNIT_TIMER, arg_timer_property);
if (r < 0)
return r;
* name our transient units. */
id = startswith(unique, ":1.");
- if (!id) {
- log_error("Unique name %s has unexpected format.", unique);
- return -EINVAL;
- }
+ if (!id)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Unique name %s has unexpected format.",
+ unique);
p = strjoin("run-u", id, ".", unit_type_to_string(t));
if (!p)
"org.freedesktop.systemd1",
path,
map,
+ BUS_MAP_STRDUP,
&error,
+ NULL,
c);
if (r < 0) {
sd_event_exit(c->event, EXIT_FAILURE);
if (arg_stdio == ARG_STDIO_PTY) {
if (arg_transport == BUS_TRANSPORT_LOCAL) {
- master = posix_openpt(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NDELAY);
+ master = posix_openpt(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
if (master < 0)
return log_error_errno(errno, "Failed to acquire pseudo tty: %m");
&error,
&pty_reply,
"s", arg_host);
- if (r < 0) {
- log_error("Failed to get machine PTY: %s", bus_error_message(&error, -r));
- return r;
- }
+ if (r < 0)
+ return log_error_errno(r, "Failed to get machine PTY: %s", bus_error_message(&error, -r));
r = sd_bus_message_read(pty_reply, "hs", &master, &s);
if (r < 0)
}
if (arg_unit) {
- r = unit_name_mangle_with_suffix(arg_unit, UNIT_NAME_NOGLOB, ".service", &service);
+ r = unit_name_mangle_with_suffix(arg_unit, arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN, ".service", &service);
if (r < 0)
return log_error_errno(r, "Failed to mangle unit name: %m");
} else {
.inactive_enter_usec = USEC_INFINITY,
};
_cleanup_free_ char *path = NULL;
- const char *mt;
c.bus = sd_bus_ref(bus);
if (!path)
return log_oom();
- mt = strjoina("type='signal',"
- "sender='org.freedesktop.systemd1',"
- "path='", path, "',"
- "interface='org.freedesktop.DBus.Properties',"
- "member='PropertiesChanged'");
- r = sd_bus_add_match(bus, &c.match, mt, on_properties_changed, &c);
+ r = sd_bus_match_signal_async(
+ bus,
+ &c.match,
+ "org.freedesktop.systemd1",
+ path,
+ "org.freedesktop.DBus.Properties",
+ "PropertiesChanged",
+ on_properties_changed, NULL, &c);
if (r < 0)
- return log_error_errno(r, "Failed to add properties changed signal.");
+ return log_error_errno(r, "Failed to request properties changed signal match: %m");
r = sd_bus_attach_event(bus, c.event, SD_EVENT_PRIORITY_NORMAL);
if (r < 0)
- return log_error_errno(r, "Failed to attach bus to event loop.");
+ return log_error_errno(r, "Failed to attach bus to event loop: %m");
r = run_context_update(&c, path);
if (r < 0)
return log_oom();
if (arg_unit) {
- r = unit_name_mangle_with_suffix(arg_unit, UNIT_NAME_NOGLOB, ".scope", &scope);
+ r = unit_name_mangle_with_suffix(arg_unit, arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN, ".scope", &scope);
if (r < 0)
return log_error_errno(r, "Failed to mangle scope name: %m");
} else {
polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
r = sd_bus_call(bus, m, 0, &error, &reply);
- if (r < 0) {
- log_error("Failed to start transient scope unit: %s", bus_error_message(&error, -r));
- return r;
- }
+ if (r < 0)
+ return log_error_errno(r, "Failed to start transient scope unit: %s", bus_error_message(&error, -r));
if (arg_nice_set) {
if (setpriority(PRIO_PROCESS, 0, arg_nice) < 0)
if (arg_exec_group) {
gid_t gid;
- r = get_group_creds(&arg_exec_group, &gid);
+ r = get_group_creds(&arg_exec_group, &gid, 0);
if (r < 0)
return log_error_errno(r, "Failed to resolve group %s: %m", arg_exec_group);
uid_t uid;
gid_t gid;
- r = get_user_creds_clean(&arg_exec_user, &uid, &gid, &home, &shell);
+ r = get_user_creds(&arg_exec_user, &uid, &gid, &home, &shell, USER_CREDS_CLEAN|USER_CREDS_PREFER_NSS);
if (r < 0)
return log_error_errno(r, "Failed to resolve user %s: %m", arg_exec_user);
return log_error_errno(errno, "Failed to execute: %m");
}
-static int start_transient_timer(
+static int start_transient_trigger(
sd_bus *bus,
- char **argv) {
+ char **argv,
+ const char *suffix) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
_cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
- _cleanup_free_ char *timer = NULL, *service = NULL;
+ _cleanup_free_ char *trigger = NULL, *service = NULL;
const char *object = NULL;
int r;
if (!service)
return log_oom();
- r = unit_name_change_suffix(service, ".timer", &timer);
+ r = unit_name_change_suffix(service, suffix, &trigger);
if (r < 0)
return log_error_errno(r, "Failed to change unit suffix: %m");
break;
case UNIT_TIMER:
- timer = strdup(arg_unit);
- if (!timer)
+ trigger = strdup(arg_unit);
+ if (!trigger)
return log_oom();
- r = unit_name_change_suffix(timer, ".service", &service);
+ r = unit_name_change_suffix(trigger, ".service", &service);
if (r < 0)
return log_error_errno(r, "Failed to change unit suffix: %m");
break;
default:
- r = unit_name_mangle_with_suffix(arg_unit, UNIT_NAME_NOGLOB, ".service", &service);
+ r = unit_name_mangle_with_suffix(arg_unit, arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN, ".service", &service);
if (r < 0)
return log_error_errno(r, "Failed to mangle unit name: %m");
- r = unit_name_mangle_with_suffix(arg_unit, UNIT_NAME_NOGLOB, ".timer", &timer);
+ r = unit_name_mangle_with_suffix(arg_unit, arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN, suffix, &trigger);
if (r < 0)
return log_error_errno(r, "Failed to mangle unit name: %m");
if (r < 0)
return r;
- r = unit_name_change_suffix(service, ".timer", &timer);
+ r = unit_name_change_suffix(service, suffix, &trigger);
if (r < 0)
return log_error_errno(r, "Failed to change unit suffix: %m");
}
return bus_log_create_error(r);
/* Name and Mode */
- r = sd_bus_message_append(m, "ss", timer, "fail");
+ r = sd_bus_message_append(m, "ss", trigger, "fail");
if (r < 0)
return bus_log_create_error(r);
if (r < 0)
return bus_log_create_error(r);
- r = transient_timer_set_properties(m);
+ if (streq(suffix, ".path"))
+ r = transient_unit_set_properties(m, UNIT_PATH, arg_path_property);
+ else if (streq(suffix, ".socket"))
+ r = transient_unit_set_properties(m, UNIT_SOCKET, arg_socket_property);
+ else if (streq(suffix, ".timer"))
+ r = transient_timer_set_properties(m);
+ else
+ assert_not_reached("Invalid suffix");
if (r < 0)
return r;
polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
r = sd_bus_call(bus, m, 0, &error, &reply);
- if (r < 0) {
- log_error("Failed to start transient timer unit: %s", bus_error_message(&error, -r));
- return r;
- }
+ if (r < 0)
+ return log_error_errno(r, "Failed to start transient %s unit: %s", suffix + 1, bus_error_message(&error, -r));
r = sd_bus_message_read(reply, "o", &object);
if (r < 0)
return r;
if (!arg_quiet) {
- log_info("Running timer as unit: %s", timer);
+ log_info("Running %s as unit: %s", suffix + 1, trigger);
if (argv[0])
log_info("Will run service as unit: %s", service);
}
if (arg_scope)
r = start_transient_scope(bus, argv + optind);
+ else if (arg_path_property)
+ r = start_transient_trigger(bus, argv + optind, ".path");
+ else if (arg_socket_property)
+ r = start_transient_trigger(bus, argv + optind, ".socket");
else if (with_timer)
- r = start_transient_timer(bus, argv + optind);
+ r = start_transient_trigger(bus, argv + optind, ".timer");
else
r = start_transient_service(bus, argv + optind, &retval);
finish:
strv_free(arg_environment);
strv_free(arg_property);
+ strv_free(arg_path_property);
+ strv_free(arg_socket_property);
strv_free(arg_timer_property);
return r < 0 ? EXIT_FAILURE : retval;