From 0eb4b7f2e7ed1dda0511b440ec002c668de99fb9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 30 Apr 2015 20:21:00 +0200 Subject: [PATCH] core: rework unit name validation and manipulation logic A variety of changes: - Make sure all our calls distuingish OOM from other errors if OOM is not the only error possible. - Be much stricter when parsing escaped paths, do not accept trailing or leading escaped slashes. - Change unit validation to take a bit mask for allowing plain names, instance names or template names or an combination thereof. - Refuse manipulating invalid unit name --- src/login/logind-user.c | 178 ++++++++++- src/shared/cgroup-util.c | 15 +- src/shared/dropin.c | 15 +- src/shared/generator.c | 18 +- src/shared/install-printf.c | 16 +- src/shared/install.c | 59 ++-- src/shared/unit-name.c | 600 ++++++++++++++++++++++-------------- src/shared/unit-name.h | 78 ++--- 8 files changed, 644 insertions(+), 335 deletions(-) diff --git a/src/login/logind-user.c b/src/login/logind-user.c index 3e1c75ef8..71bff9672 100644 --- a/src/login/logind-user.c +++ b/src/login/logind-user.c @@ -83,6 +83,19 @@ void user_free(User *u) { while (u->sessions) session_free(u->sessions); + if (u->slice) { + hashmap_remove(u->manager->user_units, u->slice); + free(u->slice); + } + + if (u->service) { + hashmap_remove(u->manager->user_units, u->service); + free(u->service); + } + + free(u->slice_job); + free(u->service_job); + free(u->runtime_path); hashmap_remove(u->manager->users, UID_TO_PTR(u->uid)); @@ -123,6 +136,16 @@ int user_save(User *u) { if (u->runtime_path) fprintf(f, "RUNTIME=%s\n", u->runtime_path); + if (u->service) + fprintf(f, "SERVICE=%s\n", u->service); + if (u->service_job) + fprintf(f, "SERVICE_JOB=%s\n", u->service_job); + + if (u->slice) + fprintf(f, "SLICE=%s\n", u->slice); + if (u->slice_job) + fprintf(f, "SLICE_JOB=%s\n", u->slice_job); + if (u->display) fprintf(f, "DISPLAY=%s\n", u->display->id); @@ -244,6 +267,10 @@ int user_load(User *u) { r = parse_env_file(u->state_file, NEWLINE, "RUNTIME", &u->runtime_path, + "SERVICE", &u->service, + "SERVICE_JOB", &u->service_job, + "SLICE", &u->slice, + "SLICE_JOB", &u->slice_job, "DISPLAY", &display, "REALTIME", &realtime, "MONOTONIC", &monotonic, @@ -340,6 +367,72 @@ fail: return r; } +static int user_start_slice(User *u) { + char *job; + int r; + + assert(u); + + if (!u->slice) { + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + char lu[DECIMAL_STR_MAX(uid_t) + 1], *slice; + sprintf(lu, UID_FMT, u->uid); + + r = slice_build_subslice(SPECIAL_USER_SLICE, lu, &slice); + if (r < 0) + return r; + + r = manager_start_unit(u->manager, slice, &error, &job); + if (r < 0) { + log_error("Failed to start user slice: %s", bus_error_message(&error, r)); + free(slice); + } else { + u->slice = slice; + + free(u->slice_job); + u->slice_job = job; + } + } + + if (u->slice) + hashmap_put(u->manager->user_units, u->slice, u); + + return 0; +} + +static int user_start_service(User *u) { + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + char *job; + int r; + + assert(u); + + if (!u->service) { + char lu[DECIMAL_STR_MAX(uid_t) + 1], *service; + sprintf(lu, UID_FMT, u->uid); + + r = unit_name_build("user", lu, ".service", &service); + if (r < 0) + return log_error_errno(r, "Failed to build service name: %m"); + + r = manager_start_unit(u->manager, service, &error, &job); + if (r < 0) { + log_error("Failed to start user service: %s", bus_error_message(&error, r)); + free(service); + } else { + u->service = service; + + free(u->service_job); + u->service_job = job; + } + } + + if (u->service) + hashmap_put(u->manager->user_units, u->service, u); + + return 0; +} + int user_start(User *u) { int r; @@ -355,6 +448,16 @@ int user_start(User *u) { if (r < 0) return r; + /* Create cgroup */ + r = user_start_slice(u); + if (r < 0) + return r; + + /* Spawn user systemd */ + r = user_start_service(u); + if (r < 0) + return r; + if (!dual_timestamp_is_set(&u->timestamp)) dual_timestamp_get(&u->timestamp); @@ -368,6 +471,50 @@ int user_start(User *u) { return 0; } +static int user_stop_slice(User *u) { + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + char *job; + int r; + + assert(u); + + if (!u->slice) + return 0; + + r = manager_stop_unit(u->manager, u->slice, &error, &job); + if (r < 0) { + log_error("Failed to stop user slice: %s", bus_error_message(&error, r)); + return r; + } + + free(u->slice_job); + u->slice_job = job; + + return r; +} + +static int user_stop_service(User *u) { + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + char *job; + int r; + + assert(u); + + if (!u->service) + return 0; + + r = manager_stop_unit(u->manager, u->service, &error, &job); + if (r < 0) { + log_error("Failed to stop user service: %s", bus_error_message(&error, r)); + return r; + } + + free(u->service_job); + u->service_job = job; + + return r; +} + static int user_remove_runtime_path(User *u) { int r; @@ -414,6 +561,16 @@ int user_stop(User *u, bool force) { r = k; } + /* Kill systemd */ + k = user_stop_service(u); + if (k < 0) + r = k; + + /* Kill cgroup */ + k = user_stop_slice(u); + if (k < 0) + r = k; + u->stopping = true; user_save(u); @@ -520,6 +677,12 @@ bool user_check_gc(User *u, bool drop_not_started) { if (user_check_linger_file(u) > 0) return true; + if (u->slice_job && manager_job_is_active(u->manager, u->slice_job)) + return true; + + if (u->service_job && manager_job_is_active(u->manager, u->service_job)) + return true; + return false; } @@ -541,6 +704,9 @@ UserState user_get_state(User *u) { if (u->stopping) return USER_CLOSING; + if (u->slice_job || u->service_job) + return USER_OPENING; + if (u->sessions) { bool all_closing = true; @@ -564,18 +730,12 @@ UserState user_get_state(User *u) { } int user_kill(User *u, int signo) { - Session *s; - int res = 0; - assert(u); - LIST_FOREACH(sessions_by_user, s, u->sessions) { - int r = session_kill(s, KILL_ALL, signo); - if (res == 0 && r < 0) - res = r; - } + if (!u->slice) + return -ESRCH; - return res; + return manager_kill_unit(u->manager, u->slice, KILL_ALL, signo, NULL); } void user_elect_display(User *u) { diff --git a/src/shared/cgroup-util.c b/src/shared/cgroup-util.c index ce8ebb2ca..78270b3fa 100644 --- a/src/shared/cgroup-util.c +++ b/src/shared/cgroup-util.c @@ -1153,7 +1153,7 @@ int cg_path_decode_unit(const char *cgroup, char **unit){ c = strndupa(cgroup, n); c = cg_unescape(c); - if (!unit_name_is_valid(c, TEMPLATE_INVALID)) + if (!unit_name_is_valid(c, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) return -ENXIO; s = strdup(c); @@ -1180,7 +1180,7 @@ static bool valid_slice_name(const char *p, size_t n) { c = cg_unescape(buf); - return unit_name_is_valid(c, TEMPLATE_INVALID); + return unit_name_is_valid(c, UNIT_NAME_PLAIN); } return false; @@ -1637,6 +1637,7 @@ bool cg_controller_is_valid(const char *p, bool allow_named) { int cg_slice_to_path(const char *unit, char **ret) { _cleanup_free_ char *p = NULL, *s = NULL, *e = NULL; const char *dash; + int r; assert(unit); assert(ret); @@ -1651,15 +1652,15 @@ int cg_slice_to_path(const char *unit, char **ret) { return 0; } - if (!unit_name_is_valid(unit, TEMPLATE_INVALID)) + if (!unit_name_is_valid(unit, UNIT_NAME_PLAIN)) return -EINVAL; if (!endswith(unit, ".slice")) return -EINVAL; - p = unit_name_to_prefix(unit); - if (!p) - return -ENOMEM; + r = unit_name_to_prefix(unit, &p); + if (r < 0) + return r; dash = strchr(p, '-'); while (dash) { @@ -1670,7 +1671,7 @@ int cg_slice_to_path(const char *unit, char **ret) { return -EINVAL; strcpy(stpncpy(n, p, dash - p), ".slice"); - if (!unit_name_is_valid(n, TEMPLATE_INVALID)) + if (!unit_name_is_valid(n, UNIT_NAME_PLAIN)) return -EINVAL; escaped = cg_escape(n); diff --git a/src/shared/dropin.c b/src/shared/dropin.c index d1baad619..963d05d32 100644 --- a/src/shared/dropin.c +++ b/src/shared/dropin.c @@ -164,7 +164,7 @@ static int iterate_dir( } int unit_file_process_dir( - Set * unit_path_cache, + Set *unit_path_cache, const char *unit_path, const char *name, const char *suffix, @@ -174,6 +174,7 @@ int unit_file_process_dir( char ***strv) { _cleanup_free_ char *path = NULL; + int r; assert(unit_path); assert(name); @@ -184,22 +185,22 @@ int unit_file_process_dir( return log_oom(); if (!unit_path_cache || set_get(unit_path_cache, path)) - iterate_dir(path, dependency, consumer, arg, strv); + (void) iterate_dir(path, dependency, consumer, arg, strv); - if (unit_name_is_instance(name)) { + if (unit_name_is_valid(name, UNIT_NAME_INSTANCE)) { _cleanup_free_ char *template = NULL, *p = NULL; /* Also try the template dir */ - template = unit_name_template(name); - if (!template) - return log_oom(); + r = unit_name_template(name, &template); + if (r < 0) + return log_error_errno(r, "Failed to generate template from unit name: %m"); p = strjoin(unit_path, "/", template, suffix, NULL); if (!p) return log_oom(); if (!unit_path_cache || set_get(unit_path_cache, p)) - iterate_dir(p, dependency, consumer, arg, strv); + (void) iterate_dir(p, dependency, consumer, arg, strv); } return 0; diff --git a/src/shared/generator.c b/src/shared/generator.c index 569b25bb7..8f48ac4fe 100644 --- a/src/shared/generator.c +++ b/src/shared/generator.c @@ -37,6 +37,8 @@ int generator_write_fsck_deps( const char *where, const char *fstype) { + int r; + assert(f); assert(dest); assert(what); @@ -48,7 +50,6 @@ int generator_write_fsck_deps( } if (!isempty(fstype) && !streq(fstype, "auto")) { - int r; r = fsck_exists(fstype); if (r == -ENOENT) { /* treat missing check as essentially OK */ @@ -70,9 +71,9 @@ int generator_write_fsck_deps( } else { _cleanup_free_ char *fsck = NULL; - fsck = unit_name_from_path_instance("systemd-fsck", what, ".service"); - if (!fsck) - return log_oom(); + r = unit_name_from_path_instance("systemd-fsck", what, ".service", &fsck); + if (r < 0) + return log_error_errno(r, "Failed to create fsck service name: %m"); fprintf(f, "RequiresOverridable=%s\n" @@ -103,8 +104,7 @@ int generator_write_timeouts(const char *dir, const char *what, const char *wher r = parse_sec(timeout, &u); if (r < 0) { - log_warning("Failed to parse timeout for %s, ignoring: %s", - where, timeout); + log_warning("Failed to parse timeout for %s, ignoring: %s", where, timeout); return 0; } @@ -112,9 +112,9 @@ int generator_write_timeouts(const char *dir, const char *what, const char *wher if (!node) return log_oom(); - unit = unit_name_from_path(node, ".device"); - if (!unit) - return log_oom(); + r = unit_name_from_path(node, ".device", &unit); + if (r < 0) + return log_error_errno(r, "Failed to make unit name from path: %m"); return write_drop_in_format(dir, unit, 50, "device-timeout", "# Automatically generated by %s\n\n" diff --git a/src/shared/install-printf.c b/src/shared/install-printf.c index 999664942..7c25d3693 100644 --- a/src/shared/install-printf.c +++ b/src/shared/install-printf.c @@ -29,30 +29,18 @@ static int specifier_prefix_and_instance(char specifier, void *data, void *userdata, char **ret) { InstallInfo *i = userdata; - char *n; assert(i); - n = unit_name_to_prefix_and_instance(i->name); - if (!n) - return -ENOMEM; - - *ret = n; - return 0; + return unit_name_to_prefix_and_instance(i->name, ret); } static int specifier_prefix(char specifier, void *data, void *userdata, char **ret) { InstallInfo *i = userdata; - char *n; assert(i); - n = unit_name_to_prefix(i->name); - if (!n) - return -ENOMEM; - - *ret = n; - return 0; + return unit_name_to_prefix(i->name, ret); } static int specifier_instance(char specifier, void *data, void *userdata, char **ret) { diff --git a/src/shared/install.c b/src/shared/install.c index b121018e9..9648c641d 100644 --- a/src/shared/install.c +++ b/src/shared/install.c @@ -258,10 +258,10 @@ static int remove_marked_symlinks_fd( int q; bool found; - if (!unit_name_is_valid(de->d_name, TEMPLATE_VALID)) + if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY)) continue; - if (unit_name_is_instance(de->d_name) && + if (unit_name_is_valid(de->d_name, UNIT_NAME_INSTANCE) && instance_whitelist && !strv_contains(instance_whitelist, de->d_name)) { @@ -272,9 +272,9 @@ static int remove_marked_symlinks_fd( * the template of it might be * listed. */ - w = unit_name_template(de->d_name); - if (!w) - return -ENOMEM; + r = unit_name_template(de->d_name, &w); + if (r < 0) + return r; if (!strv_contains(instance_whitelist, w)) continue; @@ -583,7 +583,7 @@ int unit_file_mask( STRV_FOREACH(i, files) { _cleanup_free_ char *path = NULL; - if (!unit_name_is_valid(*i, TEMPLATE_VALID)) { + if (!unit_name_is_valid(*i, UNIT_NAME_ANY)) { if (r == 0) r = -EINVAL; continue; @@ -646,7 +646,7 @@ int unit_file_unmask( STRV_FOREACH(i, files) { _cleanup_free_ char *path = NULL; - if (!unit_name_is_valid(*i, TEMPLATE_VALID)) { + if (!unit_name_is_valid(*i, UNIT_NAME_ANY)) { if (r == 0) r = -EINVAL; continue; @@ -717,7 +717,7 @@ int unit_file_link( fn = basename(*i); if (!path_is_absolute(*i) || - !unit_name_is_valid(fn, TEMPLATE_VALID)) { + !unit_name_is_valid(fn, UNIT_NAME_ANY)) { if (r == 0) r = -EINVAL; continue; @@ -856,7 +856,7 @@ static int install_info_add( if (!name) name = basename(path); - if (!unit_name_is_valid(name, TEMPLATE_VALID)) + if (!unit_name_is_valid(name, UNIT_NAME_ANY)) return -EINVAL; if (ordered_hashmap_get(c->have_installed, name) || @@ -1118,7 +1118,7 @@ static int unit_file_search( return r; } - if (unit_name_is_instance(info->name)) { + if (unit_name_is_valid(info->name, UNIT_NAME_INSTANCE)) { /* Unit file doesn't exist, however instance * enablement was requested. We will check if it is @@ -1126,9 +1126,9 @@ static int unit_file_search( _cleanup_free_ char *template = NULL; - template = unit_name_template(info->name); - if (!template) - return -ENOMEM; + r = unit_name_template(info->name, &template); + if (r < 0) + return r; STRV_FOREACH(p, paths->unit_path) { _cleanup_free_ char *path = NULL; @@ -1274,7 +1274,7 @@ static int install_info_symlink_wants( assert(i); assert(config_path); - if (unit_name_is_template(i->name)) { + if (unit_name_is_valid(i->name, UNIT_NAME_TEMPLATE)) { /* Don't install any symlink if there's no default * instance configured */ @@ -1282,9 +1282,9 @@ static int install_info_symlink_wants( if (!i->default_instance) return 0; - buf = unit_name_replace_instance(i->name, i->default_instance); - if (!buf) - return -ENOMEM; + r = unit_name_replace_instance(i->name, i->default_instance, &buf); + if (r < 0) + return r; n = buf; } else @@ -1297,7 +1297,7 @@ static int install_info_symlink_wants( if (q < 0) return q; - if (!unit_name_is_valid(dst, TEMPLATE_VALID)) { + if (!unit_name_is_valid(dst, UNIT_NAME_ANY)) { r = -EINVAL; continue; } @@ -1462,13 +1462,13 @@ static int install_context_mark_for_removal( } else if (r >= 0) r += q; - if (unit_name_is_instance(i->name)) { + if (unit_name_is_valid(i->name, UNIT_NAME_INSTANCE)) { char *unit_file; if (i->path) { unit_file = basename(i->path); - if (unit_name_is_instance(unit_file)) + if (unit_name_is_valid(unit_file, UNIT_NAME_INSTANCE)) /* unit file named as instance exists, thus all symlinks * pointing to it will be removed */ q = mark_symlink_for_removal(remove_symlinks_to, i->name); @@ -1480,9 +1480,9 @@ static int install_context_mark_for_removal( /* If i->path is not set, it means that we didn't actually find * the unit file. But we can still remove symlinks to the * nonexistent template. */ - unit_file = unit_name_template(i->name); - if (!unit_file) - return log_oom(); + r = unit_name_template(i->name, &unit_file); + if (r < 0) + return r; q = mark_symlink_for_removal(remove_symlinks_to, unit_file); free(unit_file); @@ -1797,7 +1797,7 @@ UnitFileState unit_file_lookup_state( assert(paths); - if (!unit_name_is_valid(name, TEMPLATE_VALID)) + if (!unit_name_is_valid(name, UNIT_NAME_ANY)) return -EINVAL; STRV_FOREACH(i, paths->unit_path) { @@ -1824,7 +1824,7 @@ UnitFileState unit_file_lookup_state( if (errno != ENOENT) return r; - if (!unit_name_is_instance(name)) + if (!unit_name_is_valid(name, UNIT_NAME_INSTANCE)) continue; } else { if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) @@ -1834,8 +1834,7 @@ UnitFileState unit_file_lookup_state( if (r < 0 && r != -ENOENT) return r; else if (r > 0) { - state = path_startswith(*i, "/run") ? - UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED; + state = path_startswith(*i, "/run") ? UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED; return state; } } @@ -1995,7 +1994,7 @@ int unit_file_preset( STRV_FOREACH(i, files) { - if (!unit_name_is_valid(*i, TEMPLATE_VALID)) + if (!unit_name_is_valid(*i, UNIT_NAME_ANY)) return -EINVAL; r = unit_file_query_preset(scope, root_dir, *i); @@ -2091,7 +2090,7 @@ int unit_file_preset_all( if (hidden_file(de->d_name)) continue; - if (!unit_name_is_valid(de->d_name, TEMPLATE_VALID)) + if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY)) continue; dirent_ensure_type(d, de); @@ -2203,7 +2202,7 @@ int unit_file_get_list( if (hidden_file(de->d_name)) continue; - if (!unit_name_is_valid(de->d_name, TEMPLATE_VALID)) + if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY)) continue; if (hashmap_get(h, de->d_name)) diff --git a/src/shared/unit-name.c b/src/shared/unit-name.c index 86d6ce3df..c41d7d86a 100644 --- a/src/shared/unit-name.c +++ b/src/shared/unit-name.c @@ -33,45 +33,13 @@ DIGITS LETTERS \ ":-_.\\" -static const char* const unit_type_table[_UNIT_TYPE_MAX] = { - [UNIT_SERVICE] = "service", - [UNIT_SOCKET] = "socket", - [UNIT_BUSNAME] = "busname", - [UNIT_TARGET] = "target", - [UNIT_SNAPSHOT] = "snapshot", - [UNIT_DEVICE] = "device", - [UNIT_MOUNT] = "mount", - [UNIT_AUTOMOUNT] = "automount", - [UNIT_SWAP] = "swap", - [UNIT_TIMER] = "timer", - [UNIT_PATH] = "path", - [UNIT_SLICE] = "slice", - [UNIT_SCOPE] = "scope" -}; - -DEFINE_STRING_TABLE_LOOKUP(unit_type, UnitType); - -static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = { - [UNIT_STUB] = "stub", - [UNIT_LOADED] = "loaded", - [UNIT_NOT_FOUND] = "not-found", - [UNIT_ERROR] = "error", - [UNIT_MERGED] = "merged", - [UNIT_MASKED] = "masked" -}; - -DEFINE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState); - -bool unit_name_is_valid(const char *n, enum template_valid template_ok) { +bool unit_name_is_valid(const char *n, UnitNameFlags flags) { const char *e, *i, *at; - /* Valid formats: - * - * string@instance.suffix - * string.suffix - */ + assert((flags & ~(UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) == 0); - assert(IN_SET(template_ok, TEMPLATE_VALID, TEMPLATE_INVALID)); + if (_unlikely_(flags == 0)) + return false; if (isempty(n)) return false; @@ -95,15 +63,32 @@ bool unit_name_is_valid(const char *n, enum template_valid template_ok) { return false; } - if (at) { - if (at == n) - return false; + if (at == n) + return false; - if (template_ok != TEMPLATE_VALID && at+1 == e) - return false; - } + if (flags & UNIT_NAME_PLAIN) + if (!at) + return true; - return true; + if (flags & UNIT_NAME_INSTANCE) + if (at && e > at + 1) + return true; + + if (flags & UNIT_NAME_TEMPLATE) + if (at && e == at + 1) + return true; + + return false; +} + +bool unit_prefix_is_valid(const char *p) { + + /* We don't allow additional @ in the prefix string */ + + if (isempty(p)) + return false; + + return in_charset(p, VALID_CHARS); } bool unit_instance_is_valid(const char *i) { @@ -120,14 +105,41 @@ bool unit_instance_is_valid(const char *i) { return in_charset(i, "@" VALID_CHARS); } -bool unit_prefix_is_valid(const char *p) { +bool unit_suffix_is_valid(const char *s) { + if (isempty(s)) + return false; - /* We don't allow additional @ in the instance string */ + if (s[0] != '.') + return false; - if (isempty(p)) + if (unit_type_from_string(s + 1) < 0) return false; - return in_charset(p, VALID_CHARS); + return true; +} + +int unit_name_to_prefix(const char *n, char **ret) { + const char *p; + char *s; + + assert(n); + assert(ret); + + if (!unit_name_is_valid(n, UNIT_NAME_ANY)) + return -EINVAL; + + p = strchr(n, '@'); + if (!p) + p = strrchr(n, '.'); + + assert_se(p); + + s = strndup(n, p - n); + if (!s) + return -ENOMEM; + + *ret = s; + return 0; } int unit_name_to_instance(const char *n, char **instance) { @@ -137,6 +149,9 @@ int unit_name_to_instance(const char *n, char **instance) { assert(n); assert(instance); + if (!unit_name_is_valid(n, UNIT_NAME_ANY)) + return -EINVAL; + /* Everything past the first @ and before the last . is the instance */ p = strchr(n, '@'); if (!p) { @@ -144,13 +159,13 @@ int unit_name_to_instance(const char *n, char **instance) { return 0; } - d = strrchr(n, '.'); + p++; + + d = strrchr(p, '.'); if (!d) return -EINVAL; - if (d < p) - return -EINVAL; - i = strndup(p+1, d-p-1); + i = strndup(p, d-p); if (!i) return -ENOMEM; @@ -158,55 +173,95 @@ int unit_name_to_instance(const char *n, char **instance) { return 1; } -char *unit_name_to_prefix_and_instance(const char *n) { +int unit_name_to_prefix_and_instance(const char *n, char **ret) { const char *d; + char *s; assert(n); + assert(ret); + + if (!unit_name_is_valid(n, UNIT_NAME_ANY)) + return -EINVAL; - assert_se(d = strrchr(n, '.')); - return strndup(n, d - n); + d = strrchr(n, '.'); + if (!d) + return -EINVAL; + + s = strndup(n, d - n); + if (!s) + return -ENOMEM; + + *ret = s; + return 0; } -char *unit_name_to_prefix(const char *n) { - const char *p; +UnitType unit_name_to_type(const char *n) { + const char *e; assert(n); - p = strchr(n, '@'); - if (p) - return strndup(n, p - n); + if (!unit_name_is_valid(n, UNIT_NAME_ANY)) + return _UNIT_TYPE_INVALID; - return unit_name_to_prefix_and_instance(n); + assert_se(e = strrchr(n, '.')); + + return unit_type_from_string(e + 1); } -char *unit_name_change_suffix(const char *n, const char *suffix) { - char *e, *r; +int unit_name_change_suffix(const char *n, const char *suffix, char **ret) { + char *e, *s; size_t a, b; assert(n); assert(suffix); - assert(suffix[0] == '.'); + assert(ret); + + if (!unit_name_is_valid(n, UNIT_NAME_ANY)) + return -EINVAL; + + if (!unit_suffix_is_valid(suffix)) + return -EINVAL; assert_se(e = strrchr(n, '.')); + a = e - n; b = strlen(suffix); - r = new(char, a + b + 1); - if (!r) - return NULL; + s = new(char, a + b + 1); + if (!s) + return -ENOMEM; - strcpy(mempcpy(r, n, a), suffix); - return r; + strcpy(mempcpy(s, n, a), suffix); + *ret = s; + + return 0; } -char *unit_name_build(const char *prefix, const char *instance, const char *suffix) { +int unit_name_build(const char *prefix, const char *instance, const char *suffix, char **ret) { + char *s; + assert(prefix); assert(suffix); + assert(ret); + + if (!unit_prefix_is_valid(prefix)) + return -EINVAL; + + if (instance && !unit_instance_is_valid(instance)) + return -EINVAL; + + if (!unit_suffix_is_valid(suffix)) + return -EINVAL; if (!instance) - return strappend(prefix, suffix); + s = strappend(prefix, suffix); + else + s = strjoin(prefix, "@", instance, suffix, NULL); + if (!s) + return -ENOMEM; - return strjoin(prefix, "@", instance, suffix, NULL); + *ret = s; + return 0; } static char *do_escape_char(char c, char *t) { @@ -242,30 +297,6 @@ static char *do_escape(const char *f, char *t) { return t; } -static char *do_escape_mangle(const char *f, enum unit_name_mangle allow_globs, char *t) { - const char *valid_chars; - - assert(f); - assert(IN_SET(allow_globs, MANGLE_GLOB, MANGLE_NOGLOB)); - assert(t); - - /* We'll only escape the obvious characters here, to play - * safe. */ - - valid_chars = allow_globs == MANGLE_GLOB ? "@" VALID_CHARS "[]!-*?" : "@" VALID_CHARS; - - for (; *f; f++) { - if (*f == '/') - *(t++) = '-'; - else if (!strchr(valid_chars, *f)) - t = do_escape_char(*f, t); - else - *(t++) = *f; - } - - return t; -} - char *unit_name_escape(const char *f) { char *r, *t; @@ -281,14 +312,15 @@ char *unit_name_escape(const char *f) { return r; } -char *unit_name_unescape(const char *f) { - char *r, *t; +int unit_name_unescape(const char *f, char **ret) { + _cleanup_free_ char *r = NULL; + char *t; assert(f); r = strdup(f); if (!r) - return NULL; + return -ENOMEM; for (t = r; *f; f++) { if (*f == '-') @@ -296,180 +328,228 @@ char *unit_name_unescape(const char *f) { else if (*f == '\\') { int a, b; - if (f[1] != 'x' || - (a = unhexchar(f[2])) < 0 || - (b = unhexchar(f[3])) < 0) { - /* Invalid escape code, let's take it literal then */ - *(t++) = '\\'; - } else { - *(t++) = (char) ((a << 4) | b); - f += 3; - } + if (f[1] != 'x') + return -EINVAL; + + a = unhexchar(f[2]); + if (a < 0) + return -EINVAL; + + b = unhexchar(f[3]); + if (b < 0) + return -EINVAL; + + *(t++) = (char) ((a << 4) | b); + f += 3; } else *(t++) = *f; } *t = 0; - return r; + *ret = r; + r = NULL; + + return 0; } -char *unit_name_path_escape(const char *f) { - _cleanup_free_ char *p = NULL; +int unit_name_path_escape(const char *f, char **ret) { + char *p, *s; assert(f); + assert(ret); - p = strdup(f); + p = strdupa(f); if (!p) - return NULL; + return -ENOMEM; path_kill_slashes(p); if (STR_IN_SET(p, "/", "")) - return strdup("-"); - - return unit_name_escape(p[0] == '/' ? p + 1 : p); -} + s = strdup("-"); + else { + char *e; -char *unit_name_path_unescape(const char *f) { - char *e, *w; + if (!path_is_safe(p)) + return -EINVAL; - assert(f); + /* Truncate trailing slashes */ + e = endswith(p, "/"); + if (e) + *e = 0; - e = unit_name_unescape(f); - if (!e) - return NULL; + /* Truncate leading slashes */ + if (p[0] == '/') + p++; - if (e[0] != '/') { - w = strappend("/", e); - free(e); - return w; + s = unit_name_escape(p); } + if (!s) + return -ENOMEM; - return e; + *ret = s; + return 0; } -bool unit_name_is_template(const char *n) { - const char *p, *e; - - assert(n); +int unit_name_path_unescape(const char *f, char **ret) { + char *s, *w; + int r; - p = strchr(n, '@'); - if (!p) - return false; + assert(f); - e = strrchr(p+1, '.'); - if (!e) - return false; + if (streq(f, "-")) { + s = strdup("/"); + if (!s) + return -ENOMEM; - return e == p + 1; -} + *ret = s; + return 0; + } -bool unit_name_is_instance(const char *n) { - const char *p, *e; + r = unit_name_unescape(f, &s); + if (r < 0) + return r; - assert(n); + /* Don't accept trailing or leading slashes */ + if (startswith(s, "/") || endswith(s, "/")) { + free(s); + return -EINVAL; + } - p = strchr(n, '@'); - if (!p) - return false; + /* Prefix a slash again */ + w = strappend("/", s); + free(s); + if (!w) + return -ENOMEM; - e = strrchr(p+1, '.'); - if (!e) - return false; + if (!path_is_safe(w)) { + free(w); + return -EINVAL; + } - return e > p + 1; + *ret = w; + return 0; } -char *unit_name_replace_instance(const char *f, const char *i) { +int unit_name_replace_instance(const char *f, const char *i, char **ret) { const char *p, *e; - char *r; + char *s; size_t a, b; assert(f); assert(i); + assert(ret); - p = strchr(f, '@'); - if (!p) - return strdup(f); + if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) + return -EINVAL; + if (!unit_instance_is_valid(i)) + return -EINVAL; - e = strrchr(f, '.'); - if (!e) - e = strchr(f, 0); + assert_se(p = strchr(f, '@')); + assert_se(e = strrchr(f, '.')); a = p - f; b = strlen(i); - r = new(char, a + 1 + b + strlen(e) + 1); - if (!r) - return NULL; + s = new(char, a + 1 + b + strlen(e) + 1); + if (!s) + return -ENOMEM; - strcpy(mempcpy(mempcpy(r, f, a + 1), i, b), e); - return r; + strcpy(mempcpy(mempcpy(s, f, a + 1), i, b), e); + + *ret = s; + return 0; } -char *unit_name_template(const char *f) { +int unit_name_template(const char *f, char **ret) { const char *p, *e; - char *r; + char *s; size_t a; assert(f); + assert(ret); - p = strchr(f, '@'); - if (!p) - return strdup(f); + if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) + return -EINVAL; - e = strrchr(f, '.'); - if (!e) - e = strchr(f, 0); + assert_se(p = strchr(f, '@')); + assert_se(e = strrchr(f, '.')); a = p - f; - r = new(char, a + 1 + strlen(e) + 1); - if (!r) - return NULL; + s = new(char, a + 1 + strlen(e) + 1); + if (!s) + return -ENOMEM; - strcpy(mempcpy(r, f, a + 1), e); - return r; + strcpy(mempcpy(s, f, a + 1), e); + + *ret = s; + return 0; } -char *unit_name_from_path(const char *path, const char *suffix) { +int unit_name_from_path(const char *path, const char *suffix, char **ret) { _cleanup_free_ char *p = NULL; + char *s = NULL; + int r; assert(path); assert(suffix); + assert(ret); - p = unit_name_path_escape(path); - if (!p) - return NULL; + if (!unit_suffix_is_valid(suffix)) + return -EINVAL; - return strappend(p, suffix); + r = unit_name_path_escape(path, &p); + if (r < 0) + return r; + + s = strappend(p, suffix); + if (!s) + return -ENOMEM; + + *ret = s; + return 0; } -char *unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix) { +int unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix, char **ret) { _cleanup_free_ char *p = NULL; + char *s; + int r; assert(prefix); assert(path); assert(suffix); + assert(ret); - p = unit_name_path_escape(path); - if (!p) - return NULL; + if (!unit_prefix_is_valid(prefix)) + return -EINVAL; + + if (!unit_suffix_is_valid(suffix)) + return -EINVAL; + + r = unit_name_path_escape(path, &p); + if (r < 0) + return r; + + s = strjoin(prefix, "@", p, suffix, NULL); + if (!s) + return -ENOMEM; - return strjoin(prefix, "@", p, suffix, NULL); + *ret = s; + return 0; } -char *unit_name_to_path(const char *name) { - _cleanup_free_ char *w = NULL; +int unit_name_to_path(const char *name, char **ret) { + _cleanup_free_ char *prefix = NULL; + int r; assert(name); - w = unit_name_to_prefix(name); - if (!w) - return NULL; + r = unit_name_to_prefix(name, &prefix); + if (r < 0) + return r; - return unit_name_path_unescape(w); + return unit_name_path_unescape(prefix, ret); } char *unit_dbus_path_from_name(const char *name) { @@ -500,6 +580,30 @@ int unit_name_from_dbus_path(const char *path, char **name) { return 0; } +static char *do_escape_mangle(const char *f, UnitNameMangle allow_globs, char *t) { + const char *valid_chars; + + assert(f); + assert(IN_SET(allow_globs, UNIT_NAME_GLOB, UNIT_NAME_NOGLOB)); + assert(t); + + /* We'll only escape the obvious characters here, to play + * safe. */ + + valid_chars = allow_globs == UNIT_NAME_GLOB ? "@" VALID_CHARS "[]!-*?" : "@" VALID_CHARS; + + for (; *f; f++) { + if (*f == '/') + *(t++) = '-'; + else if (!strchr(valid_chars, *f)) + t = do_escape_char(*f, t); + else + *(t++) = *f; + } + + return t; +} + /** * Convert a string to a unit name. /dev/blah is converted to dev-blah.device, * /blah/blah is converted to blah-blah.mount, anything else is left alone, @@ -507,54 +611,75 @@ int unit_name_from_dbus_path(const char *path, char **name) { * * If @allow_globs, globs characters are preserved. Otherwise they are escaped. */ -char *unit_name_mangle_with_suffix(const char *name, enum unit_name_mangle allow_globs, const char *suffix) { - char *r, *t; +int unit_name_mangle_with_suffix(const char *name, UnitNameMangle allow_globs, const char *suffix, char **ret) { + char *s, *t; + int r; assert(name); assert(suffix); - assert(suffix[0] == '.'); + assert(ret); - if (is_device_path(name)) - return unit_name_from_path(name, ".device"); + if (isempty(name)) /* We cannot mangle empty unit names to become valid, sorry. */ + return -EINVAL; - if (path_is_absolute(name)) - return unit_name_from_path(name, ".mount"); + if (!unit_suffix_is_valid(suffix)) + return -EINVAL; - r = new(char, strlen(name) * 4 + strlen(suffix) + 1); - if (!r) - return NULL; + if (unit_name_is_valid(name, UNIT_NAME_ANY)) { + /* No mangling necessary... */ + s = strdup(name); + if (!s) + return -ENOMEM; - t = do_escape_mangle(name, allow_globs, r); + *ret = s; + return 0; + } - if (unit_name_to_type(name) < 0) - strcpy(t, suffix); - else - *t = 0; + if (is_device_path(name)) { + r = unit_name_from_path(name, ".device", ret); + if (r >= 0) + return 1; + if (r != -EINVAL) + return r; + } - return r; -} + if (path_is_absolute(name)) { + r = unit_name_from_path(name, ".mount", ret); + if (r >= 0) + return 1; + if (r != -EINVAL) + return r; + } -UnitType unit_name_to_type(const char *n) { - const char *e; + s = new(char, strlen(name) * 4 + strlen(suffix) + 1); + if (!s) + return -ENOMEM; - assert(n); + t = do_escape_mangle(name, allow_globs, s); + *t = 0; - e = strrchr(n, '.'); - if (!e) - return _UNIT_TYPE_INVALID; + if (unit_name_to_type(s) < 0) + strcpy(t, suffix); - return unit_type_from_string(e + 1); + *ret = s; + return 1; } -int build_subslice(const char *slice, const char*name, char **subslice) { - char *ret; +int slice_build_subslice(const char *slice, const char*name, char **ret) { + char *subslice; assert(slice); assert(name); - assert(subslice); + assert(ret); + + if (!unit_name_is_valid(slice, UNIT_NAME_PLAIN)) + return -EINVAL; + + if (!unit_prefix_is_valid(name)) + return -EINVAL; if (streq(slice, "-.slice")) - ret = strappend(name, ".slice"); + subslice = strappend(name, ".slice"); else { char *e; @@ -562,17 +687,46 @@ int build_subslice(const char *slice, const char*name, char **subslice) { if (!e) return -EINVAL; - ret = new(char, (e - slice) + 1 + strlen(name) + 6 + 1); - if (!ret) + subslice = new(char, (e - slice) + 1 + strlen(name) + 6 + 1); + if (!subslice) return -ENOMEM; - stpcpy(stpcpy(stpcpy(mempcpy(ret, slice, e - slice), "-"), name), ".slice"); + stpcpy(stpcpy(stpcpy(mempcpy(subslice, slice, e - slice), "-"), name), ".slice"); } - *subslice = ret; + *ret = subslice; return 0; } +static const char* const unit_type_table[_UNIT_TYPE_MAX] = { + [UNIT_SERVICE] = "service", + [UNIT_SOCKET] = "socket", + [UNIT_BUSNAME] = "busname", + [UNIT_TARGET] = "target", + [UNIT_SNAPSHOT] = "snapshot", + [UNIT_DEVICE] = "device", + [UNIT_MOUNT] = "mount", + [UNIT_AUTOMOUNT] = "automount", + [UNIT_SWAP] = "swap", + [UNIT_TIMER] = "timer", + [UNIT_PATH] = "path", + [UNIT_SLICE] = "slice", + [UNIT_SCOPE] = "scope" +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_type, UnitType); + +static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = { + [UNIT_STUB] = "stub", + [UNIT_LOADED] = "loaded", + [UNIT_NOT_FOUND] = "not-found", + [UNIT_ERROR] = "error", + [UNIT_MERGED] = "merged", + [UNIT_MASKED] = "masked" +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState); + static const char* const unit_dependency_table[_UNIT_DEPENDENCY_MAX] = { [UNIT_REQUIRES] = "Requires", [UNIT_REQUIRES_OVERRIDABLE] = "RequiresOverridable", diff --git a/src/shared/unit-name.h b/src/shared/unit-name.h index 6f139cc4c..057512cbd 100644 --- a/src/shared/unit-name.h +++ b/src/shared/unit-name.h @@ -107,61 +107,67 @@ enum UnitDependency { _UNIT_DEPENDENCY_INVALID = -1 }; -const char *unit_type_to_string(UnitType i) _const_; -UnitType unit_type_from_string(const char *s) _pure_; +typedef enum UnitNameFlags { + UNIT_NAME_PLAIN = 1, /* Allow foo.service */ + UNIT_NAME_INSTANCE = 2, /* Allow foo@bar.service */ + UNIT_NAME_TEMPLATE = 4, /* Allow foo@.service */ + UNIT_NAME_ANY = UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE, +} UnitNameFlags; + +bool unit_name_is_valid(const char *n, UnitNameFlags flags) _pure_; +bool unit_prefix_is_valid(const char *p) _pure_; +bool unit_instance_is_valid(const char *i) _pure_; +bool unit_suffix_is_valid(const char *s) _pure_; -const char *unit_load_state_to_string(UnitLoadState i) _const_; -UnitLoadState unit_load_state_from_string(const char *s) _pure_; +static inline int unit_prefix_and_instance_is_valid(const char *p) { + /* For prefix+instance and instance the same rules apply */ + return unit_instance_is_valid(p); +} +int unit_name_to_prefix(const char *n, char **prefix); int unit_name_to_instance(const char *n, char **instance); -char* unit_name_to_prefix(const char *n); -char* unit_name_to_prefix_and_instance(const char *n); - -enum template_valid { - TEMPLATE_INVALID, - TEMPLATE_VALID, -}; - -bool unit_name_is_valid(const char *n, enum template_valid template_ok) _pure_; -bool unit_prefix_is_valid(const char *p) _pure_; -bool unit_instance_is_valid(const char *i) _pure_; +int unit_name_to_prefix_and_instance(const char *n, char **ret); UnitType unit_name_to_type(const char *n) _pure_; -char *unit_name_change_suffix(const char *n, const char *suffix); +int unit_name_change_suffix(const char *n, const char *suffix, char **ret); -char *unit_name_build(const char *prefix, const char *instance, const char *suffix); +int unit_name_build(const char *prefix, const char *instance, const char *suffix, char **ret); char *unit_name_escape(const char *f); -char *unit_name_unescape(const char *f); -char *unit_name_path_escape(const char *f); -char *unit_name_path_unescape(const char *f); - -bool unit_name_is_template(const char *n) _pure_; -bool unit_name_is_instance(const char *n) _pure_; +int unit_name_unescape(const char *f, char **ret); +int unit_name_path_escape(const char *f, char **ret); +int unit_name_path_unescape(const char *f, char **ret); -char *unit_name_replace_instance(const char *f, const char *i); +int unit_name_replace_instance(const char *f, const char *i, char **ret); -char *unit_name_template(const char *f); +int unit_name_template(const char *f, char **ret); -char *unit_name_from_path(const char *path, const char *suffix); -char *unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix); -char *unit_name_to_path(const char *name); +int unit_name_from_path(const char *path, const char *suffix, char **ret); +int unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix, char **ret); +int unit_name_to_path(const char *name, char **ret); char *unit_dbus_path_from_name(const char *name); int unit_name_from_dbus_path(const char *path, char **name); -enum unit_name_mangle { - MANGLE_NOGLOB, - MANGLE_GLOB, -}; +typedef enum UnitNameMangle { + UNIT_NAME_NOGLOB, + UNIT_NAME_GLOB, +} UnitNameMangle; -char *unit_name_mangle_with_suffix(const char *name, enum unit_name_mangle allow_globs, const char *suffix); -static inline char *unit_name_mangle(const char *name, enum unit_name_mangle allow_globs) { - return unit_name_mangle_with_suffix(name, allow_globs, ".service"); +int unit_name_mangle_with_suffix(const char *name, UnitNameMangle allow_globs, const char *suffix, char **ret); + +static inline int unit_name_mangle(const char *name, UnitNameMangle allow_globs, char **ret) { + return unit_name_mangle_with_suffix(name, allow_globs, ".service", ret); } -int build_subslice(const char *slice, const char*name, char **subslice); +int slice_build_subslice(const char *slice, const char*name, char **subslice); + +const char *unit_type_to_string(UnitType i) _const_; +UnitType unit_type_from_string(const char *s) _pure_; + +const char *unit_load_state_to_string(UnitLoadState i) _const_; +UnitLoadState unit_load_state_from_string(const char *s) _pure_; const char *unit_dependency_to_string(UnitDependency i) _const_; UnitDependency unit_dependency_from_string(const char *s) _pure_; -- 2.30.2