core: do BindMount/MountImage operations in async control process
These operations might require slow I/O, and thus might block PID1's main loop for an undeterminated amount of time. Instead of performing them inline, fork a worker process and stash away the D-Bus message, and reply once we get a SIGCHILD indicating they have completed. That way we don't break compatibility and callers can continue to rely on the fact that when they get the method reply the operation either succeeded or failed. To keep backward compatibility, unlike reload control processes, these are ran inside init.scope and not the target cgroup. Unlike ExecReload, this is under our control and is not defined by the unit. This is necessary because previously the operation also wasn't ran from the target cgroup, so suddenly forking a copy-on-write copy of pid1 into the target cgroup will make memory usage spike, and if there is a MemoryMax= or MemoryHigh= set and the cgroup is already close to the limit, it will cause an OOM kill, where previously it would have worked fine.
This commit is contained in:
committed by
Luca Boccassi
parent
5121f7c45b
commit
5162829ec8
+15
-45
@@ -19,7 +19,6 @@
|
||||
#include "fileio.h"
|
||||
#include "locale-util.h"
|
||||
#include "missing_fcntl.h"
|
||||
#include "mount-util.h"
|
||||
#include "open-file.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
@@ -130,6 +129,7 @@ static int property_get_exit_status_set(
|
||||
}
|
||||
|
||||
static int bus_service_method_mount(sd_bus_message *message, void *userdata, sd_bus_error *error, bool is_image) {
|
||||
MountInNamespaceFlags flags = 0;
|
||||
Unit *u = ASSERT_PTR(userdata);
|
||||
int r;
|
||||
|
||||
@@ -138,8 +138,9 @@ static int bus_service_method_mount(sd_bus_message *message, void *userdata, sd_
|
||||
if (!MANAGER_IS_SYSTEM(u->manager))
|
||||
return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Adding bind mounts at runtime is only supported by system manager");
|
||||
|
||||
if (u->type != UNIT_SERVICE)
|
||||
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Unit is not of type .service");
|
||||
r = unit_can_live_mount(u, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = mac_selinux_unit_access_check(u, message, "start", error);
|
||||
if (r < 0)
|
||||
@@ -178,50 +179,18 @@ static int bus_service_method_mount(sd_bus_message *message, void *userdata, sd_
|
||||
if (r == 0)
|
||||
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
|
||||
|
||||
const PidRef *unit_pid = unit_main_pid(u);
|
||||
if (!pidref_is_set(unit_pid) || !UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u)))
|
||||
return sd_bus_error_set(error, BUS_ERROR_UNIT_INACTIVE, "Unit is not running");
|
||||
|
||||
/* The context should always be available, but there's an assert in exec_needs_mount_namespace,
|
||||
* so double-check just in case. */
|
||||
ExecContext *c = unit_get_exec_context(u);
|
||||
if (!c)
|
||||
return -ENXIO;
|
||||
|
||||
/* Ensure that the unit was started in a private mount namespace */
|
||||
if (!exec_needs_mount_namespace(c, NULL, unit_get_exec_runtime(u)))
|
||||
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Unit not running in private mount namespace, cannot activate bind mount");
|
||||
|
||||
if (mount_point_is_credentials(u->manager->prefix[EXEC_DIRECTORY_RUNTIME], dest))
|
||||
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Refusing to bind mount over credential mounts");
|
||||
|
||||
/* If it would be dropped at startup time, return an error. */
|
||||
if (path_startswith_strv(dest, c->inaccessible_paths))
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "%s is not accessible to this unit", dest);
|
||||
|
||||
const char *propagate_directory = strjoina("/run/systemd/propagate/", u->id);
|
||||
if (is_image)
|
||||
r = mount_image_in_namespace(
|
||||
unit_pid,
|
||||
propagate_directory,
|
||||
"/run/systemd/incoming/",
|
||||
src, dest,
|
||||
read_only,
|
||||
make_file_or_directory,
|
||||
options,
|
||||
c->mount_image_policy ?: &image_policy_service);
|
||||
else
|
||||
r = bind_mount_in_namespace(
|
||||
unit_pid,
|
||||
propagate_directory,
|
||||
"/run/systemd/incoming/",
|
||||
src, dest,
|
||||
read_only,
|
||||
make_file_or_directory);
|
||||
if (r < 0)
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to mount '%s' on '%s' in unit's namespace: %m", src, dest);
|
||||
flags |= MOUNT_IN_NAMESPACE_IS_IMAGE;
|
||||
if (read_only)
|
||||
flags |= MOUNT_IN_NAMESPACE_READ_ONLY;
|
||||
if (make_file_or_directory)
|
||||
flags |= MOUNT_IN_NAMESPACE_MAKE_FILE_OR_DIRECTORY;
|
||||
|
||||
return sd_bus_reply_method_return(message, NULL);
|
||||
r = unit_live_mount(u, src, dest, message, flags, options, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int bus_service_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
@@ -362,6 +331,7 @@ const sd_bus_vtable bus_service_vtable[] = {
|
||||
SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Service, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
|
||||
SD_BUS_PROPERTY("ReloadResult", "s", property_get_result, offsetof(Service, reload_result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
|
||||
SD_BUS_PROPERTY("CleanResult", "s", property_get_result, offsetof(Service, clean_result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
|
||||
SD_BUS_PROPERTY("LiveMountResult", "s", property_get_result, offsetof(Service, live_mount_result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
|
||||
SD_BUS_PROPERTY("USBFunctionDescriptors", "s", NULL, offsetof(Service, usb_function_descriptors), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("USBFunctionStrings", "s", NULL, offsetof(Service, usb_function_strings), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("UID", "u", bus_property_get_uid, offsetof(Unit, ref_uid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
|
||||
|
||||
@@ -89,6 +89,23 @@ static int property_get_can_clean(
|
||||
return sd_bus_message_close_container(reply);
|
||||
}
|
||||
|
||||
static int property_get_can_live_mount(
|
||||
sd_bus *bus,
|
||||
const char *path,
|
||||
const char *interface,
|
||||
const char *property,
|
||||
sd_bus_message *reply,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
Unit *u = ASSERT_PTR(userdata);
|
||||
|
||||
assert(bus);
|
||||
assert(reply);
|
||||
|
||||
return sd_bus_message_append(reply, "b", unit_can_live_mount(u, /* error= */ NULL) >= 0);
|
||||
}
|
||||
|
||||
static int property_get_names(
|
||||
sd_bus *bus,
|
||||
const char *path,
|
||||
@@ -882,6 +899,7 @@ const sd_bus_vtable bus_unit_vtable[] = {
|
||||
SD_BUS_PROPERTY("CanIsolate", "b", property_get_can_isolate, 0, SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("CanClean", "as", property_get_can_clean, 0, SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("CanFreeze", "b", property_get_can_freeze, 0, SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("CanLiveMount", "b", property_get_can_live_mount, 0, SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("Job", "(uo)", property_get_job, offsetof(Unit, job), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
|
||||
SD_BUS_PROPERTY("StopWhenUnneeded", "b", bus_property_get_bool, offsetof(Unit, stop_when_unneeded), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("RefuseManualStart", "b", bus_property_get_bool, offsetof(Unit, refuse_manual_start), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
|
||||
+2
-2
@@ -425,13 +425,13 @@ bool job_type_is_redundant(JobType a, UnitActiveState b) {
|
||||
switch (a) {
|
||||
|
||||
case JOB_START:
|
||||
return IN_SET(b, UNIT_ACTIVE, UNIT_RELOADING);
|
||||
return IN_SET(b, UNIT_ACTIVE, UNIT_RELOADING, UNIT_REFRESHING);
|
||||
|
||||
case JOB_STOP:
|
||||
return IN_SET(b, UNIT_INACTIVE, UNIT_FAILED);
|
||||
|
||||
case JOB_VERIFY_ACTIVE:
|
||||
return IN_SET(b, UNIT_ACTIVE, UNIT_RELOADING);
|
||||
return IN_SET(b, UNIT_ACTIVE, UNIT_RELOADING, UNIT_REFRESHING);
|
||||
|
||||
case JOB_RELOAD:
|
||||
return
|
||||
|
||||
@@ -1890,6 +1890,7 @@ static bool manager_dbus_is_running(Manager *m, bool deserialized) {
|
||||
return false;
|
||||
if (!IN_SET((deserialized ? SERVICE(u)->deserialized_state : SERVICE(u)->state),
|
||||
SERVICE_RUNNING,
|
||||
SERVICE_MOUNTING,
|
||||
SERVICE_RELOAD,
|
||||
SERVICE_RELOAD_NOTIFY,
|
||||
SERVICE_RELOAD_SIGNAL))
|
||||
|
||||
+1
-1
@@ -356,7 +356,7 @@ static int scope_enter_start_chown(Scope *s) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = unit_fork_helper_process(u, "(sd-chown-cgroup)", &pidref);
|
||||
r = unit_fork_helper_process(u, "(sd-chown-cgroup)", /* into_cgroup= */ true, &pidref);
|
||||
if (r < 0)
|
||||
goto fail;
|
||||
|
||||
|
||||
+236
-5
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "async.h"
|
||||
#include "bus-common-errors.h"
|
||||
#include "bus-error.h"
|
||||
#include "bus-kernel.h"
|
||||
#include "bus-util.h"
|
||||
@@ -31,6 +32,7 @@
|
||||
#include "log.h"
|
||||
#include "manager.h"
|
||||
#include "missing_audit.h"
|
||||
#include "mount-util.h"
|
||||
#include "open-file.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
@@ -77,6 +79,7 @@ static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = {
|
||||
[SERVICE_AUTO_RESTART] = UNIT_ACTIVATING,
|
||||
[SERVICE_AUTO_RESTART_QUEUED] = UNIT_ACTIVATING,
|
||||
[SERVICE_CLEANING] = UNIT_MAINTENANCE,
|
||||
[SERVICE_MOUNTING] = UNIT_REFRESHING,
|
||||
};
|
||||
|
||||
/* For Type=idle we never want to delay any other jobs, hence we
|
||||
@@ -107,6 +110,7 @@ static const UnitActiveState state_translation_table_idle[_SERVICE_STATE_MAX] =
|
||||
[SERVICE_AUTO_RESTART] = UNIT_ACTIVATING,
|
||||
[SERVICE_AUTO_RESTART_QUEUED] = UNIT_ACTIVATING,
|
||||
[SERVICE_CLEANING] = UNIT_MAINTENANCE,
|
||||
[SERVICE_MOUNTING] = UNIT_REFRESHING,
|
||||
};
|
||||
|
||||
static int service_dispatch_inotify_io(sd_event_source *source, int fd, uint32_t events, void *userdata);
|
||||
@@ -122,6 +126,7 @@ static bool SERVICE_STATE_WITH_MAIN_PROCESS(ServiceState state) {
|
||||
SERVICE_START, SERVICE_START_POST,
|
||||
SERVICE_RUNNING,
|
||||
SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY,
|
||||
SERVICE_MOUNTING,
|
||||
SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
|
||||
SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL);
|
||||
}
|
||||
@@ -133,7 +138,7 @@ static bool SERVICE_STATE_WITH_CONTROL_PROCESS(ServiceState state) {
|
||||
SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY,
|
||||
SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
|
||||
SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL,
|
||||
SERVICE_CLEANING);
|
||||
SERVICE_CLEANING, SERVICE_MOUNTING);
|
||||
}
|
||||
|
||||
static void service_init(Unit *u) {
|
||||
@@ -505,6 +510,8 @@ static void service_done(Unit *u) {
|
||||
service_release_socket_fd(s);
|
||||
service_release_stdio_fd(s);
|
||||
service_release_fd_store(s);
|
||||
|
||||
s->mount_request = sd_bus_message_unref(s->mount_request);
|
||||
}
|
||||
|
||||
static int on_fd_store_io(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
|
||||
@@ -944,6 +951,7 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) {
|
||||
"%sResult: %s\n"
|
||||
"%sReload Result: %s\n"
|
||||
"%sClean Result: %s\n"
|
||||
"%sMount Result: %s\n"
|
||||
"%sPermissionsStartOnly: %s\n"
|
||||
"%sRootDirectoryStartOnly: %s\n"
|
||||
"%sRemainAfterExit: %s\n"
|
||||
@@ -958,6 +966,7 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) {
|
||||
prefix, service_result_to_string(s->result),
|
||||
prefix, service_result_to_string(s->reload_result),
|
||||
prefix, service_result_to_string(s->clean_result),
|
||||
prefix, service_result_to_string(s->live_mount_result),
|
||||
prefix, yes_no(s->permissions_start_only),
|
||||
prefix, yes_no(s->root_directory_start_only),
|
||||
prefix, yes_no(s->remain_after_exit),
|
||||
@@ -1252,6 +1261,7 @@ static void service_set_state(Service *s, ServiceState state) {
|
||||
SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
|
||||
SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL,
|
||||
SERVICE_AUTO_RESTART,
|
||||
SERVICE_MOUNTING,
|
||||
SERVICE_CLEANING))
|
||||
s->timer_event_source = sd_event_source_disable_unref(s->timer_event_source);
|
||||
|
||||
@@ -1277,7 +1287,7 @@ static void service_set_state(Service *s, ServiceState state) {
|
||||
if (state != SERVICE_START)
|
||||
s->exec_fd_event_source = sd_event_source_disable_unref(s->exec_fd_event_source);
|
||||
|
||||
if (!IN_SET(state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY))
|
||||
if (!IN_SET(state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_MOUNTING))
|
||||
service_stop_watchdog(s);
|
||||
|
||||
if (state == SERVICE_EXITED && !MANAGER_IS_RELOADING(u->manager)) {
|
||||
@@ -1301,6 +1311,9 @@ static void service_set_state(Service *s, ServiceState state) {
|
||||
unit_destroy_runtime_data(u, &s->exec_context);
|
||||
}
|
||||
|
||||
if (state != SERVICE_MOUNTING) /* Just in case */
|
||||
s->mount_request = sd_bus_message_unref(s->mount_request);
|
||||
|
||||
if (old_state != state)
|
||||
log_unit_debug(u, "Changed %s -> %s", service_state_to_string(old_state), service_state_to_string(state));
|
||||
|
||||
@@ -1342,6 +1355,9 @@ static usec_t service_coldplug_timeout(Service *s) {
|
||||
case SERVICE_CLEANING:
|
||||
return usec_add(UNIT(s)->state_change_timestamp.monotonic, s->exec_context.timeout_clean_usec);
|
||||
|
||||
case SERVICE_MOUNTING:
|
||||
return usec_add(UNIT(s)->state_change_timestamp.monotonic, s->timeout_start_usec);
|
||||
|
||||
default:
|
||||
return USEC_INFINITY;
|
||||
}
|
||||
@@ -1386,7 +1402,7 @@ static int service_coldplug(Unit *u) {
|
||||
(void) unit_setup_exec_runtime(u);
|
||||
}
|
||||
|
||||
if (IN_SET(s->deserialized_state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY))
|
||||
if (IN_SET(s->deserialized_state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_MOUNTING))
|
||||
service_start_watchdog(s);
|
||||
|
||||
if (UNIT_ISSET(s->accept_socket)) {
|
||||
@@ -2853,6 +2869,32 @@ static int service_start(Unit *u) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void service_mount_request_reply(Service *s, bool success, const char *error) {
|
||||
assert(s);
|
||||
assert(error);
|
||||
|
||||
if (!s->mount_request)
|
||||
return;
|
||||
|
||||
if (success) {
|
||||
(void) sd_bus_reply_method_return(s->mount_request, NULL);
|
||||
log_unit_debug(UNIT(s),
|
||||
"'%s' method succeeded",
|
||||
strna(sd_bus_message_get_member(s->mount_request)));
|
||||
} else {
|
||||
(void) sd_bus_reply_method_errorf(s->mount_request, error,
|
||||
"method '%s' for unit '%s' failed",
|
||||
strna(sd_bus_message_get_member(s->mount_request)),
|
||||
UNIT(s)->id);
|
||||
log_unit_debug(UNIT(s),
|
||||
"'%s' method failed: %s",
|
||||
strna(sd_bus_message_get_member(s->mount_request)),
|
||||
error);
|
||||
}
|
||||
|
||||
s->mount_request = sd_bus_message_unref(s->mount_request);
|
||||
}
|
||||
|
||||
static int service_stop(Unit *u) {
|
||||
Service *s = ASSERT_PTR(SERVICE(u));
|
||||
|
||||
@@ -2877,6 +2919,10 @@ static int service_stop(Unit *u) {
|
||||
service_set_state(s, service_determine_dead_state(s));
|
||||
return 0;
|
||||
|
||||
case SERVICE_MOUNTING:
|
||||
service_kill_control_process(s);
|
||||
service_mount_request_reply(s, /* success= */ false, BUS_ERROR_UNIT_INACTIVE);
|
||||
_fallthrough_;
|
||||
case SERVICE_CONDITION:
|
||||
case SERVICE_START_PRE:
|
||||
case SERVICE_START:
|
||||
@@ -3860,6 +3906,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
|
||||
case SERVICE_RELOAD:
|
||||
case SERVICE_RELOAD_SIGNAL:
|
||||
case SERVICE_RELOAD_NOTIFY:
|
||||
case SERVICE_MOUNTING:
|
||||
/* If neither main nor control processes are running then the current
|
||||
* state can never exit cleanly, hence immediately terminate the
|
||||
* service. */
|
||||
@@ -3974,7 +4021,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
|
||||
success,
|
||||
code, status);
|
||||
|
||||
if (s->state != SERVICE_RELOAD && s->result == SERVICE_SUCCESS)
|
||||
if (!IN_SET(s->state, SERVICE_RELOAD, SERVICE_MOUNTING) && s->result == SERVICE_SUCCESS)
|
||||
s->result = f;
|
||||
|
||||
if (s->control_command &&
|
||||
@@ -4114,6 +4161,14 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
|
||||
service_enter_dead(s, SERVICE_SUCCESS, false);
|
||||
break;
|
||||
|
||||
case SERVICE_MOUNTING:
|
||||
s->live_mount_result = f;
|
||||
|
||||
service_mount_request_reply(s, f == SERVICE_SUCCESS, SD_BUS_ERROR_FAILED);
|
||||
|
||||
service_enter_running(s, SERVICE_SUCCESS);
|
||||
break;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
@@ -4187,6 +4242,14 @@ static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *us
|
||||
service_enter_running(s, SERVICE_SUCCESS);
|
||||
break;
|
||||
|
||||
case SERVICE_MOUNTING:
|
||||
log_unit_warning(UNIT(s), "Mount operation timed out. Killing mount process.");
|
||||
service_kill_control_process(s);
|
||||
s->live_mount_result = SERVICE_FAILURE_TIMEOUT;
|
||||
service_mount_request_reply(s, /* success= */ false, SD_BUS_ERROR_TIMEOUT);
|
||||
service_enter_running(s, SERVICE_SUCCESS);
|
||||
break;
|
||||
|
||||
case SERVICE_STOP:
|
||||
switch (s->timeout_stop_failure_mode) {
|
||||
|
||||
@@ -4743,7 +4806,8 @@ static bool pick_up_pid_from_bus_name(Service *s) {
|
||||
SERVICE_RUNNING,
|
||||
SERVICE_RELOAD,
|
||||
SERVICE_RELOAD_SIGNAL,
|
||||
SERVICE_RELOAD_NOTIFY);
|
||||
SERVICE_RELOAD_NOTIFY,
|
||||
SERVICE_MOUNTING);
|
||||
}
|
||||
|
||||
static int bus_name_pid_lookup_callback(sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) {
|
||||
@@ -4892,6 +4956,7 @@ static void service_reset_failed(Unit *u) {
|
||||
s->result = SERVICE_SUCCESS;
|
||||
s->reload_result = SERVICE_SUCCESS;
|
||||
s->clean_result = SERVICE_SUCCESS;
|
||||
s->live_mount_result = SERVICE_SUCCESS;
|
||||
s->n_restarts = 0;
|
||||
|
||||
service_set_debug_invocation(s, /* enable= */ false);
|
||||
@@ -4928,6 +4993,7 @@ static bool service_needs_console(Unit *u) {
|
||||
SERVICE_RELOAD,
|
||||
SERVICE_RELOAD_SIGNAL,
|
||||
SERVICE_RELOAD_NOTIFY,
|
||||
SERVICE_MOUNTING,
|
||||
SERVICE_STOP,
|
||||
SERVICE_STOP_WATCHDOG,
|
||||
SERVICE_STOP_SIGTERM,
|
||||
@@ -5035,6 +5101,168 @@ static int service_can_clean(Unit *u, ExecCleanMask *ret) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int service_live_mount(Unit *u,
|
||||
const char *src,
|
||||
const char *dst,
|
||||
sd_bus_message *message,
|
||||
MountInNamespaceFlags flags,
|
||||
const MountOptions *options,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_(pidref_done) PidRef worker = PIDREF_NULL;
|
||||
Service *s = ASSERT_PTR(SERVICE(u));
|
||||
const char *propagate_directory;
|
||||
int r;
|
||||
|
||||
assert(u);
|
||||
assert(u->manager);
|
||||
assert(src);
|
||||
assert(dst);
|
||||
assert(message);
|
||||
assert(!s->mount_request);
|
||||
|
||||
if (s->state != SERVICE_RUNNING || !pidref_is_set(&s->main_pid)) {
|
||||
log_unit_warning(u, "Service is not running, cannot live mount");
|
||||
return sd_bus_error_setf(
|
||||
error,
|
||||
BUS_ERROR_UNIT_INACTIVE,
|
||||
"Live mounting '%s' on '%s' for unit '%s' cannot be scheduled: service not running",
|
||||
src,
|
||||
dst,
|
||||
u->id);
|
||||
}
|
||||
|
||||
if (mount_point_is_credentials(u->manager->prefix[EXEC_DIRECTORY_RUNTIME], dst)) {
|
||||
log_unit_warning(u, "Refusing to live mount over credential mount '%s'", dst);
|
||||
return sd_bus_error_setf(
|
||||
error,
|
||||
SD_BUS_ERROR_INVALID_ARGS,
|
||||
"Live mounting '%s' on '%s' for unit '%s' cannot be scheduled: cannot mount over credential mount",
|
||||
src,
|
||||
dst,
|
||||
u->id);
|
||||
}
|
||||
|
||||
if (path_startswith_strv(dst, s->exec_context.inaccessible_paths)) {
|
||||
log_unit_warning(u, "%s is not accessible to this unit, cannot live mount", dst);
|
||||
return sd_bus_error_setf(
|
||||
error,
|
||||
SD_BUS_ERROR_INVALID_ARGS,
|
||||
"Live mounting '%s' on '%s' for unit '%s' cannot be scheduled: destination is not accessible to this unit",
|
||||
src,
|
||||
dst,
|
||||
u->id);
|
||||
}
|
||||
|
||||
service_unwatch_control_pid(s);
|
||||
s->live_mount_result = SERVICE_SUCCESS;
|
||||
s->control_command = NULL;
|
||||
s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID;
|
||||
|
||||
r = service_arm_timer(s, /* relative= */ true, s->timeout_start_usec);
|
||||
if (r < 0) {
|
||||
log_unit_warning_errno(u, r, "Failed to install timer: %m");
|
||||
sd_bus_error_set_errnof(
|
||||
error,
|
||||
r,
|
||||
"Live mounting '%s' on '%s' for unit '%s' cannot be scheduled: failed to install timer",
|
||||
src,
|
||||
dst,
|
||||
u->id);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
propagate_directory = strjoina("/run/systemd/propagate/", u->id);
|
||||
|
||||
/* Given we are running from PID1, avoid doing potentially heavy I/O operations like opening images
|
||||
* directly, and instead fork a worker process. We record the D-Bus message, so that we can reply
|
||||
* after the operation has finished. This way callers can wait on the message and know that the new
|
||||
* resource is available (or the operation failed) once they receive the response. */
|
||||
r = unit_fork_helper_process(u, "(sd-mount-in-ns)", /* into_cgroup= */ false, &worker);
|
||||
if (r < 0) {
|
||||
log_unit_warning_errno(
|
||||
u,
|
||||
r,
|
||||
"Failed to fork process to mount '%s' on '%s' in unit's namespace: %m",
|
||||
src,
|
||||
dst);
|
||||
sd_bus_error_set_errnof(
|
||||
error,
|
||||
r,
|
||||
"Live mounting '%s' on '%s' for unit '%s' cannot be scheduled: failed to fork process",
|
||||
src,
|
||||
dst,
|
||||
u->id);
|
||||
goto fail;
|
||||
}
|
||||
if (r == 0) {
|
||||
if (flags & MOUNT_IN_NAMESPACE_IS_IMAGE)
|
||||
r = mount_image_in_namespace(
|
||||
&s->main_pid,
|
||||
propagate_directory,
|
||||
"/run/systemd/incoming/",
|
||||
src, dst,
|
||||
flags,
|
||||
options,
|
||||
s->exec_context.mount_image_policy ?: &image_policy_service);
|
||||
else
|
||||
r = bind_mount_in_namespace(
|
||||
&s->main_pid,
|
||||
propagate_directory,
|
||||
"/run/systemd/incoming/",
|
||||
src, dst,
|
||||
flags);
|
||||
if (r < 0)
|
||||
log_unit_warning_errno(
|
||||
u,
|
||||
r,
|
||||
"Failed to mount '%s' on '%s' in unit's namespace: %m",
|
||||
src,
|
||||
dst);
|
||||
else
|
||||
log_unit_debug(u, "Mounted '%s' on '%s' in unit's namespace", src, dst);
|
||||
_exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
r = unit_watch_pidref(u, &worker, /* exclusive= */ true);
|
||||
if (r < 0) {
|
||||
sd_bus_error_set_errnof(
|
||||
error,
|
||||
r,
|
||||
"Live mounting '%s' on '%s' for unit '%s' failed: failed to watch worker process",
|
||||
src,
|
||||
dst,
|
||||
u->id);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
s->mount_request = sd_bus_message_ref(message);
|
||||
s->control_pid = TAKE_PIDREF(worker);
|
||||
service_set_state(s, SERVICE_MOUNTING);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
s->live_mount_result = SERVICE_FAILURE_RESOURCES;
|
||||
s->timer_event_source = sd_event_source_disable_unref(s->timer_event_source);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int service_can_live_mount(const Unit *u, sd_bus_error *error) {
|
||||
assert(u);
|
||||
|
||||
/* Ensure that the unit runs in a private mount namespace */
|
||||
if (!exec_needs_mount_namespace(unit_get_exec_context(u), /* params= */ NULL, unit_get_exec_runtime(u))) {
|
||||
log_unit_debug(u, "Unit not running in private mount namespace, cannot live mount");
|
||||
return sd_bus_error_setf(
|
||||
error,
|
||||
SD_BUS_ERROR_INVALID_ARGS,
|
||||
"Live mounting for unit '%s' cannot be scheduled: unit not running in private mount namespace",
|
||||
u->id);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char* service_finished_job(Unit *u, JobType t, JobResult result) {
|
||||
Service *s = ASSERT_PTR(SERVICE(u));
|
||||
|
||||
@@ -5265,6 +5493,9 @@ const UnitVTable service_vtable = {
|
||||
.clean = service_clean,
|
||||
.can_clean = service_can_clean,
|
||||
|
||||
.live_mount = service_live_mount,
|
||||
.can_live_mount = service_can_live_mount,
|
||||
|
||||
.freezer_action = unit_cgroup_freezer_action,
|
||||
|
||||
.serialize = service_serialize,
|
||||
|
||||
@@ -188,6 +188,7 @@ struct Service {
|
||||
ServiceResult result;
|
||||
ServiceResult reload_result;
|
||||
ServiceResult clean_result;
|
||||
ServiceResult live_mount_result;
|
||||
|
||||
bool main_pid_known:1;
|
||||
bool main_pid_alien:1;
|
||||
@@ -232,6 +233,9 @@ struct Service {
|
||||
|
||||
int reload_signal;
|
||||
usec_t reload_begin_usec;
|
||||
|
||||
/* The D-Bus request, we will reply once the operation is finished, so that callers can block */
|
||||
sd_bus_message *mount_request;
|
||||
};
|
||||
|
||||
static inline usec_t service_timeout_abort_usec(Service *s) {
|
||||
|
||||
+3
-3
@@ -1568,7 +1568,7 @@ static int socket_address_listen_in_cgroup(
|
||||
if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, pair) < 0)
|
||||
return log_unit_error_errno(UNIT(s), errno, "Failed to create communication channel: %m");
|
||||
|
||||
r = unit_fork_helper_process(UNIT(s), "(sd-listen)", &pid);
|
||||
r = unit_fork_helper_process(UNIT(s), "(sd-listen)", /* into_cgroup= */ true, &pid);
|
||||
if (r < 0)
|
||||
return log_unit_error_errno(UNIT(s), r, "Failed to fork off listener stub process: %m");
|
||||
if (r == 0) {
|
||||
@@ -1989,7 +1989,7 @@ static int socket_chown(Socket *s, PidRef *ret_pid) {
|
||||
/* We have to resolve the user names out-of-process, hence
|
||||
* let's fork here. It's messy, but well, what can we do? */
|
||||
|
||||
r = unit_fork_helper_process(UNIT(s), "(sd-chown)", &pid);
|
||||
r = unit_fork_helper_process(UNIT(s), "(sd-chown)", /* into_cgroup= */ true, &pid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0) {
|
||||
@@ -3013,7 +3013,7 @@ static int socket_accept_in_cgroup(Socket *s, SocketPort *p, int fd) {
|
||||
if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, pair) < 0)
|
||||
return log_unit_error_errno(UNIT(s), errno, "Failed to create communication channel: %m");
|
||||
|
||||
r = unit_fork_helper_process(UNIT(s), "(sd-accept)", &pid);
|
||||
r = unit_fork_helper_process(UNIT(s), "(sd-accept)", /* into_cgroup= */ true, &pid);
|
||||
if (r < 0)
|
||||
return log_unit_error_errno(UNIT(s), r, "Failed to fork off accept stub process: %m");
|
||||
if (r == 0) {
|
||||
|
||||
+90
-10
@@ -2561,7 +2561,7 @@ static bool unit_process_job(Job *j, UnitActiveState ns, bool reload_success) {
|
||||
if (j->state == JOB_RUNNING) {
|
||||
if (ns == UNIT_ACTIVE)
|
||||
job_finish_and_invalidate(j, reload_success ? JOB_DONE : JOB_FAILED, true, false);
|
||||
else if (!IN_SET(ns, UNIT_ACTIVATING, UNIT_RELOADING)) {
|
||||
else if (!IN_SET(ns, UNIT_ACTIVATING, UNIT_RELOADING, UNIT_REFRESHING)) {
|
||||
unexpected = true;
|
||||
|
||||
if (UNIT_IS_INACTIVE_OR_FAILED(ns))
|
||||
@@ -5412,21 +5412,25 @@ int unit_set_exec_params(Unit *u, ExecParameters *p) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int unit_fork_helper_process(Unit *u, const char *name, PidRef *ret) {
|
||||
int unit_fork_helper_process(Unit *u, const char *name, bool into_cgroup, PidRef *ret) {
|
||||
CGroupRuntime *crt = NULL;
|
||||
pid_t pid;
|
||||
int r;
|
||||
|
||||
assert(u);
|
||||
assert(ret);
|
||||
|
||||
/* Forks off a helper process and makes sure it is a member of the unit's cgroup. Returns == 0 in the child,
|
||||
* and > 0 in the parent. The pid parameter is always filled in with the child's PID. */
|
||||
/* Forks off a helper process and makes sure it is a member of the unit's cgroup, if configured to
|
||||
* do so. Returns == 0 in the child, and > 0 in the parent. The pid parameter is always filled in
|
||||
* with the child's PID. */
|
||||
|
||||
(void) unit_realize_cgroup(u);
|
||||
if (into_cgroup) {
|
||||
(void) unit_realize_cgroup(u);
|
||||
|
||||
CGroupRuntime *crt = unit_setup_cgroup_runtime(u);
|
||||
if (!crt)
|
||||
return -ENOMEM;
|
||||
crt = unit_setup_cgroup_runtime(u);
|
||||
if (!crt)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
r = safe_fork(name, FORK_REOPEN_LOG|FORK_DEATHSIG_SIGTERM, &pid);
|
||||
if (r < 0)
|
||||
@@ -5450,7 +5454,7 @@ int unit_fork_helper_process(Unit *u, const char *name, PidRef *ret) {
|
||||
(void) default_signals(SIGNALS_CRASH_HANDLER, SIGNALS_IGNORE);
|
||||
(void) ignore_signals(SIGPIPE);
|
||||
|
||||
if (crt->cgroup_path) {
|
||||
if (crt && crt->cgroup_path) {
|
||||
r = cg_attach_everywhere(u->manager->cgroup_supported, crt->cgroup_path, 0);
|
||||
if (r < 0) {
|
||||
log_unit_error_errno(u, r, "Failed to join unit cgroup %s: %m", empty_to_root(crt->cgroup_path));
|
||||
@@ -5468,7 +5472,7 @@ int unit_fork_and_watch_rm_rf(Unit *u, char **paths, PidRef *ret_pid) {
|
||||
assert(u);
|
||||
assert(ret_pid);
|
||||
|
||||
r = unit_fork_helper_process(u, "(sd-rmrf)", &pid);
|
||||
r = unit_fork_helper_process(u, "(sd-rmrf)", /* into_cgroup= */ true, &pid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0) {
|
||||
@@ -6375,6 +6379,82 @@ Condition *unit_find_failed_condition(Unit *u) {
|
||||
return failed_trigger && !has_succeeded_trigger ? failed_trigger : NULL;
|
||||
}
|
||||
|
||||
int unit_can_live_mount(const Unit *u, sd_bus_error *error) {
|
||||
assert(u);
|
||||
|
||||
if (!UNIT_VTABLE(u)->live_mount) {
|
||||
log_unit_debug(u, "Live mounting not supported for unit type '%s'", unit_type_to_string(u->type));
|
||||
return sd_bus_error_setf(
|
||||
error,
|
||||
SD_BUS_ERROR_INVALID_ARGS,
|
||||
"Live mounting for unit '%s' cannot be scheduled: live mounting not supported for unit type '%s'",
|
||||
u->id,
|
||||
unit_type_to_string(u->type));
|
||||
}
|
||||
|
||||
if (u->load_state != UNIT_LOADED) {
|
||||
log_unit_debug(u, "Unit not loaded");
|
||||
return sd_bus_error_setf(
|
||||
error,
|
||||
BUS_ERROR_NO_SUCH_UNIT,
|
||||
"Live mounting for unit '%s' cannot be scheduled: unit not loaded",
|
||||
u->id);
|
||||
}
|
||||
|
||||
if (!UNIT_VTABLE(u)->can_live_mount)
|
||||
return 0;
|
||||
|
||||
return UNIT_VTABLE(u)->can_live_mount(u, error);
|
||||
}
|
||||
|
||||
int unit_live_mount(
|
||||
Unit *u,
|
||||
const char *src,
|
||||
const char *dst,
|
||||
sd_bus_message *message,
|
||||
MountInNamespaceFlags flags,
|
||||
const MountOptions *options,
|
||||
sd_bus_error *error) {
|
||||
|
||||
assert(u);
|
||||
assert(UNIT_VTABLE(u)->live_mount);
|
||||
|
||||
if (!UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u))) {
|
||||
log_unit_debug(u, "Unit not active");
|
||||
return sd_bus_error_setf(
|
||||
error,
|
||||
BUS_ERROR_UNIT_INACTIVE,
|
||||
"Live mounting '%s' on '%s' for unit '%s' cannot be scheduled: unit not active",
|
||||
src,
|
||||
dst,
|
||||
u->id);
|
||||
}
|
||||
|
||||
if (unit_active_state(u) == UNIT_REFRESHING) {
|
||||
log_unit_debug(u, "Unit already live mounting");
|
||||
return sd_bus_error_setf(
|
||||
error,
|
||||
BUS_ERROR_UNIT_BUSY,
|
||||
"Live mounting '%s' on '%s' for unit '%s' cannot be scheduled: another live mount in progress",
|
||||
src,
|
||||
dst,
|
||||
u->id);
|
||||
}
|
||||
|
||||
if (u->job) {
|
||||
log_unit_debug(u, "Unit already has a job in progress, cannot live mount");
|
||||
return sd_bus_error_setf(
|
||||
error,
|
||||
BUS_ERROR_UNIT_BUSY,
|
||||
"Live mounting '%s' on '%s' for unit '%s' cannot be scheduled: another operation in progress",
|
||||
src,
|
||||
dst,
|
||||
u->id);
|
||||
}
|
||||
|
||||
return UNIT_VTABLE(u)->live_mount(u, src, dst, message, flags, options, error);
|
||||
}
|
||||
|
||||
static const char* const collect_mode_table[_COLLECT_MODE_MAX] = {
|
||||
[COLLECT_INACTIVE] = "inactive",
|
||||
[COLLECT_INACTIVE_OR_FAILED] = "inactive-or-failed",
|
||||
|
||||
+11
-3
@@ -22,6 +22,7 @@ typedef enum UnitMountDependencyType {
|
||||
#include "emergency-action.h"
|
||||
#include "install.h"
|
||||
#include "list.h"
|
||||
#include "mount-util.h"
|
||||
#include "pidref.h"
|
||||
#include "unit-file.h"
|
||||
|
||||
@@ -45,11 +46,11 @@ typedef enum CollectMode {
|
||||
} CollectMode;
|
||||
|
||||
static inline bool UNIT_IS_ACTIVE_OR_RELOADING(UnitActiveState t) {
|
||||
return IN_SET(t, UNIT_ACTIVE, UNIT_RELOADING);
|
||||
return IN_SET(t, UNIT_ACTIVE, UNIT_RELOADING, UNIT_REFRESHING);
|
||||
}
|
||||
|
||||
static inline bool UNIT_IS_ACTIVE_OR_ACTIVATING(UnitActiveState t) {
|
||||
return IN_SET(t, UNIT_ACTIVE, UNIT_ACTIVATING, UNIT_RELOADING);
|
||||
return IN_SET(t, UNIT_ACTIVE, UNIT_ACTIVATING, UNIT_RELOADING, UNIT_REFRESHING);
|
||||
}
|
||||
|
||||
static inline bool UNIT_IS_INACTIVE_OR_DEACTIVATING(UnitActiveState t) {
|
||||
@@ -584,6 +585,10 @@ typedef struct UnitVTable {
|
||||
|
||||
bool (*can_reload)(Unit *u);
|
||||
|
||||
/* Add a bind/image mount into the unit namespace while it is running. */
|
||||
int (*live_mount)(Unit *u, const char *src, const char *dst, sd_bus_message *message, MountInNamespaceFlags flags, const MountOptions *options, sd_bus_error *error);
|
||||
int (*can_live_mount)(const Unit *u, sd_bus_error *error);
|
||||
|
||||
/* Serialize state and file descriptors that should be carried over into the new
|
||||
* instance after reexecution. */
|
||||
int (*serialize)(Unit *u, FILE *f, FDSet *fds);
|
||||
@@ -980,7 +985,7 @@ int unit_acquire_invocation_id(Unit *u);
|
||||
|
||||
int unit_set_exec_params(Unit *s, ExecParameters *p);
|
||||
|
||||
int unit_fork_helper_process(Unit *u, const char *name, PidRef *ret);
|
||||
int unit_fork_helper_process(Unit *u, const char *name, bool into_cgroup, PidRef *ret);
|
||||
int unit_fork_and_watch_rm_rf(Unit *u, char **paths, PidRef *ret);
|
||||
|
||||
void unit_remove_dependencies(Unit *u, UnitDependencyMask mask);
|
||||
@@ -1041,6 +1046,9 @@ void unit_next_freezer_state(Unit *u, FreezerAction action, FreezerState *ret_ne
|
||||
void unit_set_freezer_state(Unit *u, FreezerState state);
|
||||
void unit_freezer_complete(Unit *u, FreezerState kernel_state);
|
||||
|
||||
int unit_can_live_mount(const Unit *u, sd_bus_error *error);
|
||||
int unit_live_mount(Unit *u, const char *src, const char *dst, sd_bus_message *message, MountInNamespaceFlags flags, const MountOptions *options, sd_bus_error *error);
|
||||
|
||||
Condition *unit_find_failed_condition(Unit *u);
|
||||
|
||||
int unit_arm_timer(Unit *u, sd_event_source **source, bool relative, usec_t usec, sd_event_time_handler_t handler);
|
||||
|
||||
Reference in New Issue
Block a user