#include "signal-util.h"
#include "stat-util.h"
#include "string-util.h"
+#include "strv.h"
#include "unit.h"
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, service_type, ServiceType);
return sd_bus_message_close_container(reply);
}
+static int property_get_refresh_on_reload(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *reterr_error) {
+
+ Service *s = ASSERT_PTR(userdata);
+ _cleanup_strv_free_ char **l = NULL;
+ int r;
+
+ assert(bus);
+ assert(reply);
+
+ r = service_refresh_on_reload_to_strv(s->refresh_on_reload_flags, &l);
+ if (r < 0)
+ return r;
+
+ return sd_bus_message_append_strv(reply, l);
+}
+
static int property_get_exit_status_set(
sd_bus *bus,
const char *path,
SD_BUS_PROPERTY("OpenFile", "a(sst)", property_get_open_files, offsetof(Service, open_files), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ExtraFileDescriptorNames", "as", property_get_extra_file_descriptors, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ReloadSignal", "i", bus_property_get_int, offsetof(Service, reload_signal), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("RefreshOnReload", "as", property_get_refresh_on_reload, 0, SD_BUS_VTABLE_PROPERTY_CONST),
BUS_EXEC_STATUS_VTABLE("ExecMain", offsetof(Service, main_exec_status), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
BUS_EXEC_COMMAND_LIST_VTABLE("ExecCondition", offsetof(Service, exec_command[SERVICE_EXEC_CONDITION]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
return 1;
}
+ if (streq(name, "RefreshOnReload")) {
+ const char *t;
+ int invert;
+
+ r = sd_bus_message_enter_container(message, 'a', "(bs)");
+ if (r < 0)
+ return r;
+
+ while ((r = sd_bus_message_read(message, "(bs)", &invert, &t)) > 0) {
+ ServiceRefreshOnReload f;
+
+ f = service_refresh_on_reload_flag_from_string(t);
+ if (f < 0)
+ return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Invalid RefreshOnReload= value: %s", t);
+
+ if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
+ if (!s->refresh_on_reload_set)
+ s->refresh_on_reload_flags = invert ? (SERVICE_REFRESH_ON_RELOAD_DEFAULT & ~f) : f;
+ else
+ SET_FLAG(s->refresh_on_reload_flags, f, !invert);
+
+ s->refresh_on_reload_set = true;
+ unit_write_settingf(u, flags, name, "%s=%s%s", name, invert ? "~" : "", t);
+ }
+ }
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_exit_container(message);
+ if (r < 0)
+ return r;
+
+ if (!UNIT_WRITE_FLAGS_NOOP(flags) && !s->refresh_on_reload_set) { /* empty array? */
+ s->refresh_on_reload_flags = 0;
+ s->refresh_on_reload_set = true;
+ unit_write_settingf(u, flags, name, "%s=no", name);
+ }
+
+ return 1;
+ }
+
return 0;
}
Service.OOMPolicy, config_parse_oom_policy, 0, offsetof(Service, oom_policy)
Service.OpenFile, config_parse_open_file, 0, offsetof(Service, open_files)
Service.ReloadSignal, config_parse_signal, 0, offsetof(Service, reload_signal)
+Service.RefreshOnReload, config_parse_service_refresh_on_reload, 0, 0
{{ EXEC_CONTEXT_CONFIG_ITEMS('Service') }}
{{ CGROUP_CONTEXT_CONFIG_ITEMS('Service') }}
{{ KILL_CONTEXT_CONFIG_ITEMS('Service') }}
return 0;
}
+int config_parse_service_refresh_on_reload(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Service *s = ASSERT_PTR(userdata);
+ int r;
+
+ if (isempty(rvalue)) {
+ s->refresh_on_reload_set = false;
+ return 0;
+ }
+
+ r = parse_boolean(rvalue);
+ if (r >= 0) {
+ s->refresh_on_reload_flags = r > 0 ? _SERVICE_REFRESH_ON_RELOAD_ALL : 0;
+ s->refresh_on_reload_set = true;
+ return 0;
+ }
+
+ ServiceRefreshOnReload f;
+ bool invert = false;
+
+ if (rvalue[0] == '~') {
+ invert = true;
+ rvalue++;
+ }
+
+ r = service_refresh_on_reload_from_string_many(rvalue, &f);
+ if (r < 0)
+ return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue);
+
+ /* If the first entry is negated, mask off from default; otherwise assign "positive" values directly */
+ if (!s->refresh_on_reload_set)
+ s->refresh_on_reload_flags = invert ? (SERVICE_REFRESH_ON_RELOAD_DEFAULT & ~f) : f;
+ else
+ SET_FLAG(s->refresh_on_reload_flags, f, !invert);
+
+ s->refresh_on_reload_set = true;
+ return 0;
+}
+
int config_parse_set_status(
const char *unit,
const char *filename,
CONFIG_PARSER_PROTOTYPE(config_parse_set_credential);
CONFIG_PARSER_PROTOTYPE(config_parse_load_credential);
CONFIG_PARSER_PROTOTYPE(config_parse_import_credential);
+CONFIG_PARSER_PROTOTYPE(config_parse_service_refresh_on_reload);
CONFIG_PARSER_PROTOTYPE(config_parse_set_status);
CONFIG_PARSER_PROTOTYPE(config_parse_namespace_path_strv);
CONFIG_PARSER_PROTOTYPE(config_parse_temporary_filesystems);
static void service_reload_finish(Service *s, ServiceResult f);
static void service_enter_reload_by_notify(Service *s);
+static bool service_can_reload_extensions(Service *s, bool warn);
+
static bool SERVICE_STATE_WITH_MAIN_PROCESS(ServiceState state) {
return IN_SET(state,
SERVICE_START, SERVICE_START_POST,
s->restart_usec = s->restart_max_delay_usec;
}
+ if (s->refresh_on_reload_set && s->refresh_on_reload_flags != _SERVICE_REFRESH_ON_RELOAD_ALL) {
+ if (FLAGS_SET(s->refresh_on_reload_flags, SERVICE_RELOAD_EXTENSIONS))
+ service_can_reload_extensions(s, /* warn = */ true);
+ }
+
return 0;
}
(IN_SET(s->type, SERVICE_NOTIFY, SERVICE_NOTIFY_RELOAD) || s->watchdog_usec > 0 || s->n_fd_store_max > 0))
s->notify_access = NOTIFY_MAIN;
+ if (!s->refresh_on_reload_set) {
+ assert_cc(SERVICE_REFRESH_ON_RELOAD_DEFAULT == SERVICE_RELOAD_EXTENSIONS);
+ s->refresh_on_reload_flags = service_can_reload_extensions(s, /* warn = */ false) ? SERVICE_RELOAD_EXTENSIONS : 0;
+ }
+
/* If no OOM policy was explicitly set, then default to the configure default OOM policy. Except when
* delegation is on, in that case it we assume the payload knows better what to do and can process
* things in a more focused way. */
service_enter_reload_signal(s);
}
-static bool service_should_reload_extensions(Service *s) {
- int r;
-
+static bool service_can_reload_extensions(Service *s, bool warn) {
assert(s);
- if (!pidref_is_set(&s->main_pid)) {
- log_unit_debug(UNIT(s), "Not reloading extensions for service without main PID.");
+ // TODO: Add support for user services, which can use ExtensionDirectories= + notify-reload.
+ // For now, skip for user services.
+
+ if (exec_context_has_vpicked_extensions(&s->exec_context) <= 0) {
+ if (warn)
+ log_unit_warning(UNIT(s), "Service uses RefreshOnReload=extensions, but has no extensions using vpick. Ignoring.");
return false;
}
- r = exec_context_has_vpicked_extensions(&s->exec_context);
- if (r < 0)
- log_unit_warning_errno(UNIT(s), r, "Failed to determine if service should reload extensions, assuming false: %m");
- if (r == 0)
- log_unit_debug(UNIT(s), "Service has no extensions to reload.");
- if (r <= 0)
+ if (!s->exec_command[SERVICE_EXEC_START]) {
+ if (warn)
+ log_unit_warning(UNIT(s), "Service uses RefreshOnReload=extensions, but has no main process (ExecStart=). Ignoring.");
return false;
+ }
- // TODO: Add support for user services, which can use ExtensionDirectories= + notify-reload.
- // For now, skip for user services.
if (!MANAGER_IS_SYSTEM(UNIT(s)->manager)) {
- log_once(LOG_WARNING, "Not reloading extensions for user services.");
+ if (warn)
+ log_unit_warning(UNIT(s), "Service uses RefreshOnReload=extensions, which is not supported in user mode. Ignoring.");
+ return false;
+ }
+
+ return true;
+}
+
+static bool service_get_effective_reload_extensions(Service *s) {
+ assert(s);
+
+ if (!FLAGS_SET(s->refresh_on_reload_flags, SERVICE_RELOAD_EXTENSIONS))
+ return false;
+
+ if (!service_can_reload_extensions(s, /* warn = */ false))
+ return false;
+
+ return true;
+}
+
+static bool service_should_reload_extensions(Service *s) {
+ assert(s);
+
+ if (!service_get_effective_reload_extensions(s))
+ return false;
+
+ if (!pidref_is_set(&s->main_pid)) {
+ log_unit_debug(UNIT(s), "Not reloading extensions for service without main PID.");
return false;
}
DEFINE_STRING_TABLE_LOOKUP(service_timeout_failure_mode, ServiceTimeoutFailureMode);
+static const struct {
+ ServiceRefreshOnReload flag;
+ const char *name;
+} service_refresh_on_reload_table[] = {
+ { SERVICE_RELOAD_EXTENSIONS, "extensions" },
+};
+
+ServiceRefreshOnReload service_refresh_on_reload_flag_from_string(const char *s) {
+ assert(s);
+
+ FOREACH_ELEMENT(i, service_refresh_on_reload_table)
+ if (streq(s, i->name))
+ return i->flag;
+
+ return _SERVICE_REFRESH_ON_RELOAD_INVALID;
+}
+
+int service_refresh_on_reload_from_string_many(const char *s, ServiceRefreshOnReload *ret) {
+ ServiceRefreshOnReload flags = 0;
+ int r;
+
+ assert(s);
+ assert(ret);
+
+ for (;;) {
+ _cleanup_free_ char *v = NULL;
+ ServiceRefreshOnReload f;
+
+ r = extract_first_word(&s, &v, NULL, 0);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ f = service_refresh_on_reload_flag_from_string(v);
+ if (f < 0)
+ return f;
+ assert(f > 0);
+
+ flags |= f;
+ }
+
+ *ret = flags;
+ return 0;
+}
+
+int service_refresh_on_reload_to_strv(ServiceRefreshOnReload flags, char ***ret) {
+ _cleanup_strv_free_ char **l = NULL;
+ int r;
+
+ assert(flags >= 0);
+ assert(ret);
+
+ FOREACH_ELEMENT(i, service_refresh_on_reload_table) {
+ if (!FLAGS_SET(flags, i->flag))
+ continue;
+
+ r = strv_extend(&l, i->name);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = TAKE_PTR(l);
+ return 0;
+}
+
const UnitVTable service_vtable = {
.object_size = sizeof(Service),
.exec_context_offset = offsetof(Service, exec_context),
_SERVICE_RESTART_MODE_INVALID = -EINVAL,
} ServiceRestartMode;
+typedef enum ServiceRefreshOnReload {
+ SERVICE_RELOAD_EXTENSIONS = 1 << 0,
+ _SERVICE_REFRESH_ON_RELOAD_ALL = (1 << 1) - 1,
+ _SERVICE_REFRESH_ON_RELOAD_INVALID = -EINVAL,
+} ServiceRefreshOnReload;
+
+#define SERVICE_REFRESH_ON_RELOAD_DEFAULT SERVICE_RELOAD_EXTENSIONS
+
typedef struct ServiceFDStore {
Service *service;
int reload_signal;
usec_t reload_begin_usec;
+ bool refresh_on_reload_set;
+ ServiceRefreshOnReload refresh_on_reload_flags;
+
OOMPolicy oom_policy;
char *usb_function_descriptors;
DECLARE_STRING_TABLE_LOOKUP(service_timeout_failure_mode, ServiceTimeoutFailureMode);
+ServiceRefreshOnReload service_refresh_on_reload_flag_from_string(const char *s) _pure_;
+int service_refresh_on_reload_from_string_many(const char *s, ServiceRefreshOnReload *ret);
+int service_refresh_on_reload_to_strv(ServiceRefreshOnReload flags, char ***ret);
+
DEFINE_CAST(SERVICE, Service);
/* Only exported for unit tests */
return 1;
}
+static int bus_append_refresh_on_reload(sd_bus_message *m, const char *field, const char *eq) {
+ int r;
+
+ r = sd_bus_message_open_container(m, 'r', "sv");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append_basic(m, 's', field);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_open_container(m, 'v', "a(bs)");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_open_container(m, 'a', "(bs)");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ bool invert = *eq == '~';
+
+ for (const char *p = eq + invert;;) {
+ _cleanup_free_ char *word = NULL;
+
+ r = extract_first_word(&p, &word, NULL, 0);
+ if (r < 0)
+ return parse_log_error(r, field, eq);
+ if (r == 0)
+ break;
+
+ r = sd_bus_message_append(m, "(bs)", invert, word);
+ 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);
+
+ return 1;
+}
+
static int bus_append_log_extra_fields(sd_bus_message *m, const char *field, const char *eq) {
int r;
{ "SuccessExitStatus", bus_append_exit_status },
{ "OpenFile", bus_append_open_file },
{ "ReloadSignal", bus_append_signal_from_string },
+ { "RefreshOnReload", bus_append_refresh_on_reload },
{}
};