diff options
Diffstat (limited to 'src')
383 files changed, 17054 insertions, 5295 deletions
diff --git a/src/analyze/analyze-verify.c b/src/analyze/analyze-verify.c index 5fd3ee49eb..0ce0276d92 100644 --- a/src/analyze/analyze-verify.c +++ b/src/analyze/analyze-verify.c @@ -71,6 +71,7 @@ static int prepare_filename(const char *filename, char **ret) { } static int generate_path(char **var, char **filenames) { + const char *old; char **filename; _cleanup_strv_free_ char **ans = NULL; @@ -90,9 +91,19 @@ static int generate_path(char **var, char **filenames) { assert_se(strv_uniq(ans)); - r = strv_extend(&ans, ""); - if (r < 0) - return r; + /* First, prepend our directories. Second, if some path was specified, use that, and + * otherwise use the defaults. Any duplicates will be filtered out in path-lookup.c. + * Treat explicit empty path to mean that nothing should be appended. + */ + old = getenv("SYSTEMD_UNIT_PATH"); + if (!streq_ptr(old, "")) { + if (!old) + old = ":"; + + r = strv_extend(&ans, old); + if (r < 0) + return r; + } *var = strv_join(ans, ":"); if (!*var) diff --git a/src/backlight/backlight.c b/src/backlight/backlight.c index 45be135a23..7c59f60d5f 100644 --- a/src/backlight/backlight.c +++ b/src/backlight/backlight.c @@ -167,7 +167,7 @@ static bool validate_device(struct udev *udev, struct udev_device *device) { continue; v = udev_device_get_sysattr_value(other, "type"); - if (!streq_ptr(v, "platform") && !streq_ptr(v, "firmware")) + if (!STRPTR_IN_SET(v, "platform", "firmware")) continue; /* OK, so there's another backlight device, and it's a diff --git a/src/basic/alloc-util.h b/src/basic/alloc-util.h index ceeee519b7..a44dd473c1 100644 --- a/src/basic/alloc-util.h +++ b/src/basic/alloc-util.h @@ -43,6 +43,14 @@ static inline void *mfree(void *memory) { return NULL; } +#define free_and_replace(a, b) \ + ({ \ + free(a); \ + (a) = (b); \ + (b) = NULL; \ + 0; \ + }) + void* memdup(const void *p, size_t l) _alloc_(2); static inline void freep(void *p) { diff --git a/src/basic/architecture.c b/src/basic/architecture.c index b1c8e91f50..b74dc0db78 100644 --- a/src/basic/architecture.c +++ b/src/basic/architecture.c @@ -123,6 +123,14 @@ int uname_architecture(void) { { "crisv32", ARCHITECTURE_CRIS }, #elif defined(__nios2__) { "nios2", ARCHITECTURE_NIOS2 }, +#elif defined(__riscv__) + { "riscv32", ARCHITECTURE_RISCV32 }, + { "riscv64", ARCHITECTURE_RISCV64 }, +# if __SIZEOF_POINTER__ == 4 + { "riscv", ARCHITECTURE_RISCV32 }, +# elif __SIZEOF_POINTER__ == 8 + { "riscv", ARCHITECTURE_RISCV64 }, +# endif #else #error "Please register your architecture here!" #endif @@ -174,6 +182,8 @@ static const char *const architecture_table[_ARCHITECTURE_MAX] = { [ARCHITECTURE_TILEGX] = "tilegx", [ARCHITECTURE_CRIS] = "cris", [ARCHITECTURE_NIOS2] = "nios2", + [ARCHITECTURE_RISCV32] = "riscv32", + [ARCHITECTURE_RISCV64] = "riscv64", }; DEFINE_STRING_TABLE_LOOKUP(architecture, int); diff --git a/src/basic/architecture.h b/src/basic/architecture.h index b3e4d85906..5a77c31932 100644 --- a/src/basic/architecture.h +++ b/src/basic/architecture.h @@ -58,6 +58,8 @@ enum { ARCHITECTURE_TILEGX, ARCHITECTURE_CRIS, ARCHITECTURE_NIOS2, + ARCHITECTURE_RISCV32, + ARCHITECTURE_RISCV64, _ARCHITECTURE_MAX, _ARCHITECTURE_INVALID = -1 }; @@ -191,6 +193,16 @@ int uname_architecture(void); #elif defined(__nios2__) # define native_architecture() ARCHITECTURE_NIOS2 # define LIB_ARCH_TUPLE "nios2-linux-gnu" +#elif defined(__riscv__) +# if __SIZEOF_POINTER__ == 4 +# define native_architecture() ARCHITECTURE_RISCV32 +# define LIB_ARCH_TUPLE "riscv32-linux-gnu" +# elif __SIZEOF_POINTER__ == 8 +# define native_architecture() ARCHITECTURE_RISCV64 +# define LIB_ARCH_TUPLE "riscv64-linux-gnu" +# else +# error "Unrecognized riscv architecture variant" +# endif #else # error "Please register your architecture here!" #endif diff --git a/src/basic/audit-util.c b/src/basic/audit-util.c index 5741fecdd6..d1c9695973 100644 --- a/src/basic/audit-util.c +++ b/src/basic/audit-util.c @@ -92,8 +92,11 @@ bool use_audit(void) { int fd; fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_AUDIT); - if (fd < 0) - cached_use = errno != EAFNOSUPPORT && errno != EPROTONOSUPPORT; + if (fd < 0) { + cached_use = !IN_SET(errno, EAFNOSUPPORT, EPROTONOSUPPORT, EPERM); + if (errno == EPERM) + log_debug_errno(errno, "Audit access prohibited, won't talk to audit"); + } else { cached_use = true; safe_close(fd); diff --git a/src/basic/bitmap.c b/src/basic/bitmap.c index f4b12fc261..f6212e6151 100644 --- a/src/basic/bitmap.c +++ b/src/basic/bitmap.c @@ -58,10 +58,8 @@ Bitmap *bitmap_copy(Bitmap *b) { return NULL; ret->bitmaps = newdup(uint64_t, b->bitmaps, b->n_bitmaps); - if (!ret->bitmaps) { - free(ret); - return NULL; - } + if (!ret->bitmaps) + return mfree(ret); ret->n_bitmaps = ret->bitmaps_allocated = b->n_bitmaps; return ret; diff --git a/src/basic/calendarspec.c b/src/basic/calendarspec.c index e4cfab364e..fda293fcb9 100644 --- a/src/basic/calendarspec.c +++ b/src/basic/calendarspec.c @@ -302,6 +302,17 @@ int calendar_spec_to_string(const CalendarSpec *c, char **p) { if (c->utc) fputs(" UTC", f); + else if (IN_SET(c->dst, 0, 1)) { + + /* If daylight saving is explicitly on or off, let's show the used timezone. */ + + tzset(); + + if (!isempty(tzname[c->dst])) { + fputc(' ', f); + fputs(tzname[c->dst], f); + } + } r = fflush_and_check(f); if (r < 0) { @@ -747,9 +758,9 @@ fail: } int calendar_spec_from_string(const char *p, CalendarSpec **spec) { + const char *utc; CalendarSpec *c; int r; - const char *utc; assert(p); assert(spec); @@ -760,11 +771,39 @@ int calendar_spec_from_string(const char *p, CalendarSpec **spec) { c = new0(CalendarSpec, 1); if (!c) return -ENOMEM; + c->dst = -1; utc = endswith_no_case(p, " UTC"); if (utc) { c->utc = true; p = strndupa(p, utc - p); + } else { + const char *e = NULL; + int j; + + tzset(); + + /* Check if the local timezone was specified? */ + for (j = 0; j <= 1; j++) { + if (isempty(tzname[j])) + continue; + + e = endswith_no_case(p, tzname[j]); + if(!e) + continue; + if (e == p) + continue; + if (e[-1] != ' ') + continue; + + break; + } + + /* Found one of the two timezones specified? */ + if (IN_SET(j, 0, 1)) { + p = strndupa(p, e - p - 1); + c->dst = j; + } } if (strcaseeq(p, "minutely")) { @@ -1017,7 +1056,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) { for (;;) { /* Normalize the current date */ (void) mktime_or_timegm(&c, spec->utc); - c.tm_isdst = -1; + c.tm_isdst = spec->dst; c.tm_year += 1900; r = find_matching_component(spec->year, &c.tm_year); diff --git a/src/basic/calendarspec.h b/src/basic/calendarspec.h index f6472c1244..c6087228fd 100644 --- a/src/basic/calendarspec.h +++ b/src/basic/calendarspec.h @@ -37,6 +37,7 @@ typedef struct CalendarComponent { typedef struct CalendarSpec { int weekdays_bits; bool utc; + int dst; CalendarComponent *year; CalendarComponent *month; diff --git a/src/basic/capability-util.c b/src/basic/capability-util.c index d4c5bd6937..c3de20a0e8 100644 --- a/src/basic/capability-util.c +++ b/src/basic/capability-util.c @@ -31,6 +31,7 @@ #include "log.h" #include "macro.h" #include "parse-util.h" +#include "user-util.h" #include "util.h" int have_effective_cap(int value) { @@ -295,8 +296,9 @@ int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities) { if (setresgid(gid, gid, gid) < 0) return log_error_errno(errno, "Failed to change group ID: %m"); - if (setgroups(0, NULL) < 0) - return log_error_errno(errno, "Failed to drop auxiliary groups list: %m"); + r = maybe_setgroups(0, NULL); + if (r < 0) + return log_error_errno(r, "Failed to drop auxiliary groups list: %m"); /* Ensure we keep the permitted caps across the setresuid() */ if (prctl(PR_SET_KEEPCAPS, 1) < 0) diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c index 472e24b7a3..134e6e3664 100644 --- a/src/basic/cgroup-util.c +++ b/src/basic/cgroup-util.c @@ -28,6 +28,7 @@ #include <sys/stat.h> #include <sys/statfs.h> #include <sys/types.h> +#include <sys/xattr.h> #include <unistd.h> #include "alloc-util.h" @@ -134,6 +135,20 @@ int cg_read_event(const char *controller, const char *path, const char *event, return -ENOENT; } +bool cg_ns_supported(void) { + static thread_local int enabled = -1; + + if (enabled >= 0) + return enabled; + + if (access("/proc/self/ns/cgroup", F_OK) == 0) + enabled = 1; + else + enabled = 0; + + return enabled; +} + int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d) { _cleanup_free_ char *fs = NULL; int r; @@ -609,7 +624,7 @@ int cg_get_path(const char *controller, const char *path, const char *suffix, ch if (!cg_controller_is_valid(controller)) return -EINVAL; - unified = cg_unified(); + unified = cg_all_unified(); if (unified < 0) return unified; @@ -637,7 +652,7 @@ static int controller_is_accessible(const char *controller) { if (!cg_controller_is_valid(controller)) return -EINVAL; - unified = cg_unified(); + unified = cg_all_unified(); if (unified < 0) return unified; if (unified > 0) { @@ -855,7 +870,7 @@ int cg_set_task_access( if (r < 0) return r; - unified = cg_unified(); + unified = cg_unified(controller); if (unified < 0) return unified; if (unified) @@ -869,6 +884,43 @@ int cg_set_task_access( return 0; } +int cg_set_xattr(const char *controller, const char *path, const char *name, const void *value, size_t size, int flags) { + _cleanup_free_ char *fs = NULL; + int r; + + assert(path); + assert(name); + assert(value || size <= 0); + + r = cg_get_path(controller, path, NULL, &fs); + if (r < 0) + return r; + + if (setxattr(fs, name, value, size, flags) < 0) + return -errno; + + return 0; +} + +int cg_get_xattr(const char *controller, const char *path, const char *name, void *value, size_t size) { + _cleanup_free_ char *fs = NULL; + ssize_t n; + int r; + + assert(path); + assert(name); + + r = cg_get_path(controller, path, NULL, &fs); + if (r < 0) + return r; + + n = getxattr(fs, name, value, size); + if (n < 0) + return -errno; + + return (int) n; +} + int cg_pid_get_path(const char *controller, pid_t pid, char **path) { _cleanup_fclose_ FILE *f = NULL; char line[LINE_MAX]; @@ -879,18 +931,17 @@ int cg_pid_get_path(const char *controller, pid_t pid, char **path) { assert(path); assert(pid >= 0); - unified = cg_unified(); + if (controller) { + if (!cg_controller_is_valid(controller)) + return -EINVAL; + } else + controller = SYSTEMD_CGROUP_CONTROLLER; + + unified = cg_unified(controller); if (unified < 0) return unified; - if (unified == 0) { - if (controller) { - if (!cg_controller_is_valid(controller)) - return -EINVAL; - } else - controller = SYSTEMD_CGROUP_CONTROLLER; - + if (unified == 0) cs = strlen(controller); - } fs = procfs_file_alloca(pid, "cgroup"); f = fopen(fs, "re"); @@ -955,7 +1006,7 @@ int cg_install_release_agent(const char *controller, const char *agent) { assert(agent); - unified = cg_unified(); + unified = cg_unified(controller); if (unified < 0) return unified; if (unified) /* doesn't apply to unified hierarchy */ @@ -1006,7 +1057,7 @@ int cg_uninstall_release_agent(const char *controller) { _cleanup_free_ char *fs = NULL; int r, unified; - unified = cg_unified(); + unified = cg_unified(controller); if (unified < 0) return unified; if (unified) /* Doesn't apply to unified hierarchy */ @@ -1062,7 +1113,7 @@ int cg_is_empty_recursive(const char *controller, const char *path) { if (controller && (isempty(path) || path_equal(path, "/"))) return false; - unified = cg_unified(); + unified = cg_unified(controller); if (unified < 0) return unified; @@ -1653,7 +1704,7 @@ int cg_path_get_slice(const char *p, char **slice) { if (!e) { char *s; - s = strdup("-.slice"); + s = strdup(SPECIAL_ROOT_SLICE); if (!s) return -ENOMEM; @@ -1808,7 +1859,7 @@ int cg_slice_to_path(const char *unit, char **ret) { assert(unit); assert(ret); - if (streq(unit, "-.slice")) { + if (streq(unit, SPECIAL_ROOT_SLICE)) { char *x; x = strdup(""); @@ -1891,6 +1942,49 @@ int cg_get_attribute(const char *controller, const char *path, const char *attri return read_one_line_file(p, ret); } +int cg_get_keyed_attribute(const char *controller, const char *path, const char *attribute, const char **keys, char **values) { + _cleanup_free_ char *filename = NULL, *content = NULL; + char *line, *p; + int i, r; + + for (i = 0; keys[i]; i++) + values[i] = NULL; + + r = cg_get_path(controller, path, attribute, &filename); + if (r < 0) + return r; + + r = read_full_file(filename, &content, NULL); + if (r < 0) + return r; + + p = content; + while ((line = strsep(&p, "\n"))) { + char *key; + + key = strsep(&line, " "); + + for (i = 0; keys[i]; i++) { + if (streq(key, keys[i])) { + values[i] = strdup(line); + break; + } + } + } + + for (i = 0; keys[i]; i++) { + if (!values[i]) { + for (i = 0; keys[i]; i++) { + free(values[i]); + values[i] = NULL; + } + return -ENOENT; + } + } + + return 0; +} + int cg_create_everywhere(CGroupMask supported, CGroupMask mask, const char *path) { CGroupController c; int r, unified; @@ -1905,7 +1999,7 @@ int cg_create_everywhere(CGroupMask supported, CGroupMask mask, const char *path return r; /* If we are in the unified hierarchy, we are done now */ - unified = cg_unified(); + unified = cg_all_unified(); if (unified < 0) return unified; if (unified > 0) @@ -1935,7 +2029,7 @@ int cg_attach_everywhere(CGroupMask supported, const char *path, pid_t pid, cg_m if (r < 0) return r; - unified = cg_unified(); + unified = cg_all_unified(); if (unified < 0) return unified; if (unified > 0) @@ -1987,7 +2081,7 @@ int cg_migrate_everywhere(CGroupMask supported, const char *from, const char *to return r; } - unified = cg_unified(); + unified = cg_all_unified(); if (unified < 0) return unified; if (unified > 0) @@ -2020,7 +2114,7 @@ int cg_trim_everywhere(CGroupMask supported, const char *path, bool delete_root) if (r < 0) return r; - unified = cg_unified(); + unified = cg_all_unified(); if (unified < 0) return unified; if (unified > 0) @@ -2046,7 +2140,7 @@ int cg_mask_supported(CGroupMask *ret) { * includes controllers we can make sense of and that are * actually accessible. */ - unified = cg_unified(); + unified = cg_all_unified(); if (unified < 0) return unified; if (unified > 0) { @@ -2087,10 +2181,10 @@ int cg_mask_supported(CGroupMask *ret) { mask |= CGROUP_CONTROLLER_TO_MASK(v); } - /* Currently, we only support the memory, io and pids + /* Currently, we support the cpu, memory, io and pids * controller in the unified hierarchy, mask * everything else off. */ - mask &= CGROUP_MASK_MEMORY | CGROUP_MASK_IO | CGROUP_MASK_PIDS; + mask &= CGROUP_MASK_CPU | CGROUP_MASK_MEMORY | CGROUP_MASK_IO | CGROUP_MASK_PIDS; } else { CGroupController c; @@ -2167,9 +2261,10 @@ int cg_kernel_controllers(Set *controllers) { return 0; } -static thread_local int unified_cache = -1; +static thread_local CGroupUnified unified_cache = CGROUP_UNIFIED_UNKNOWN; + +static int cg_update_unified(void) { -int cg_unified(void) { struct statfs fs; /* Checks if we support the unified hierarchy. Returns an @@ -2177,24 +2272,47 @@ int cg_unified(void) { * have any other trouble determining if the unified hierarchy * is supported. */ - if (unified_cache >= 0) - return unified_cache; + if (unified_cache >= CGROUP_UNIFIED_NONE) + return 0; if (statfs("/sys/fs/cgroup/", &fs) < 0) return -errno; if (F_TYPE_EQUAL(fs.f_type, CGROUP2_SUPER_MAGIC)) - unified_cache = true; - else if (F_TYPE_EQUAL(fs.f_type, TMPFS_MAGIC)) - unified_cache = false; - else + unified_cache = CGROUP_UNIFIED_ALL; + else if (F_TYPE_EQUAL(fs.f_type, TMPFS_MAGIC)) { + if (statfs("/sys/fs/cgroup/systemd/", &fs) < 0) + return -errno; + + unified_cache = F_TYPE_EQUAL(fs.f_type, CGROUP2_SUPER_MAGIC) ? + CGROUP_UNIFIED_SYSTEMD : CGROUP_UNIFIED_NONE; + } else return -ENOMEDIUM; - return unified_cache; + return 0; +} + +int cg_unified(const char *controller) { + + int r; + + r = cg_update_unified(); + if (r < 0) + return r; + + if (streq_ptr(controller, SYSTEMD_CGROUP_CONTROLLER)) + return unified_cache >= CGROUP_UNIFIED_SYSTEMD; + else + return unified_cache >= CGROUP_UNIFIED_ALL; +} + +int cg_all_unified(void) { + + return cg_unified(NULL); } void cg_unified_flush(void) { - unified_cache = -1; + unified_cache = CGROUP_UNIFIED_UNKNOWN; } int cg_enable_everywhere(CGroupMask supported, CGroupMask mask, const char *p) { @@ -2207,7 +2325,7 @@ int cg_enable_everywhere(CGroupMask supported, CGroupMask mask, const char *p) { if (supported == 0) return 0; - unified = cg_unified(); + unified = cg_all_unified(); if (unified < 0) return unified; if (!unified) /* on the legacy hiearchy there's no joining of controllers defined */ @@ -2246,7 +2364,7 @@ bool cg_is_unified_wanted(void) { /* If the hierarchy is already mounted, then follow whatever * was chosen for it. */ - unified = cg_unified(); + unified = cg_all_unified(); if (unified >= 0) return unified; @@ -2276,6 +2394,50 @@ bool cg_is_legacy_wanted(void) { return !cg_is_unified_wanted(); } +bool cg_is_unified_systemd_controller_wanted(void) { + static thread_local int wanted = -1; + int r, unified; + + /* If the unified hierarchy is requested in full, no need to + * bother with this. */ + if (cg_is_unified_wanted()) + return 0; + + /* If the hierarchy is already mounted, then follow whatever + * was chosen for it. */ + unified = cg_unified(SYSTEMD_CGROUP_CONTROLLER); + if (unified >= 0) + return unified; + + /* Otherwise, let's see what the kernel command line has to + * say. Since checking that is expensive, let's cache the + * result. */ + if (wanted >= 0) + return wanted; + + r = get_proc_cmdline_key("systemd.legacy_systemd_cgroup_controller", NULL); + if (r > 0) + wanted = false; + else { + _cleanup_free_ char *value = NULL; + + r = get_proc_cmdline_key("systemd.legacy_systemd_cgroup_controller=", &value); + if (r < 0) + return false; + + if (r == 0) + wanted = false; + else + wanted = parse_boolean(value) <= 0; + } + + return wanted; +} + +bool cg_is_legacy_systemd_controller_wanted(void) { + return cg_is_legacy_wanted() && !cg_is_unified_systemd_controller_wanted(); +} + int cg_weight_parse(const char *s, uint64_t *ret) { uint64_t u; int r; @@ -2352,6 +2514,20 @@ int cg_blkio_weight_parse(const char *s, uint64_t *ret) { return 0; } +bool is_cgroup_fs(const struct statfs *s) { + return is_fs_type(s, CGROUP_SUPER_MAGIC) || + is_fs_type(s, CGROUP2_SUPER_MAGIC); +} + +bool fd_is_cgroup_fs(int fd) { + struct statfs s; + + if (fstatfs(fd, &s) < 0) + return -errno; + + return is_cgroup_fs(&s); +} + static const char *cgroup_controller_table[_CGROUP_CONTROLLER_MAX] = { [CGROUP_CONTROLLER_CPU] = "cpu", [CGROUP_CONTROLLER_CPUACCT] = "cpuacct", diff --git a/src/basic/cgroup-util.h b/src/basic/cgroup-util.h index 14ebde5fc9..0aa27c4cd7 100644 --- a/src/basic/cgroup-util.h +++ b/src/basic/cgroup-util.h @@ -23,6 +23,7 @@ #include <stdbool.h> #include <stdint.h> #include <stdio.h> +#include <sys/statfs.h> #include <sys/types.h> #include "def.h" @@ -112,6 +113,17 @@ static inline bool CGROUP_BLKIO_WEIGHT_IS_OK(uint64_t x) { (x >= CGROUP_BLKIO_WEIGHT_MIN && x <= CGROUP_BLKIO_WEIGHT_MAX); } +/* Default resource limits */ +#define DEFAULT_TASKS_MAX_PERCENTAGE 15U /* 15% of PIDs, 4915 on default settings */ +#define DEFAULT_USER_TASKS_MAX_PERCENTAGE 33U /* 33% of PIDs, 10813 on default settings */ + +typedef enum CGroupUnified { + CGROUP_UNIFIED_UNKNOWN = -1, + CGROUP_UNIFIED_NONE = 0, /* Both systemd and controllers on legacy */ + CGROUP_UNIFIED_SYSTEMD = 1, /* Only systemd on unified */ + CGROUP_UNIFIED_ALL = 2, /* Both systemd and controllers on unified */ +} CGroupUnified; + /* * General rules: * @@ -169,10 +181,14 @@ int cg_create_and_attach(const char *controller, const char *path, pid_t pid); int cg_set_attribute(const char *controller, const char *path, const char *attribute, const char *value); int cg_get_attribute(const char *controller, const char *path, const char *attribute, char **ret); +int cg_get_keyed_attribute(const char *controller, const char *path, const char *attribute, const char **keys, char **values); int cg_set_group_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid); int cg_set_task_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid); +int cg_set_xattr(const char *controller, const char *path, const char *name, const void *value, size_t size, int flags); +int cg_get_xattr(const char *controller, const char *path, const char *name, void *value, size_t size); + int cg_install_release_agent(const char *controller, const char *agent); int cg_uninstall_release_agent(const char *controller); @@ -222,11 +238,16 @@ int cg_mask_supported(CGroupMask *ret); int cg_kernel_controllers(Set *controllers); -int cg_unified(void); +bool cg_ns_supported(void); + +int cg_all_unified(void); +int cg_unified(const char *controller); void cg_unified_flush(void); bool cg_is_unified_wanted(void); bool cg_is_legacy_wanted(void); +bool cg_is_unified_systemd_controller_wanted(void); +bool cg_is_legacy_systemd_controller_wanted(void); const char* cgroup_controller_to_string(CGroupController c) _const_; CGroupController cgroup_controller_from_string(const char *s) _pure_; @@ -234,3 +255,6 @@ CGroupController cgroup_controller_from_string(const char *s) _pure_; int cg_weight_parse(const char *s, uint64_t *ret); int cg_cpu_shares_parse(const char *s, uint64_t *ret); int cg_blkio_weight_parse(const char *s, uint64_t *ret); + +bool is_cgroup_fs(const struct statfs *s); +bool fd_is_cgroup_fs(int fd); diff --git a/src/basic/def.h b/src/basic/def.h index 1a7a0f4928..2266eff650 100644 --- a/src/basic/def.h +++ b/src/basic/def.h @@ -79,7 +79,7 @@ #endif /* Return a nulstr for a standard cascade of configuration paths, - * suitable to pass to conf_files_list_nulstr() or config_parse_many() + * suitable to pass to conf_files_list_nulstr() or config_parse_many_nulstr() * to implement drop-in directories for extending configuration * files. */ #define CONF_PATHS_NULSTR(n) \ diff --git a/src/basic/env-util.c b/src/basic/env-util.c index 7f5fddb700..b74290d6fd 100644 --- a/src/basic/env-util.c +++ b/src/basic/env-util.c @@ -544,8 +544,7 @@ char *replace_env(const char *format, char **env) { return k; fail: - free(r); - return NULL; + return mfree(r); } char **replace_env_argv(char **argv, char **env) { diff --git a/src/basic/escape.c b/src/basic/escape.c index 01daf11ce7..4a1ec4505e 100644 --- a/src/basic/escape.c +++ b/src/basic/escape.c @@ -333,7 +333,7 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi assert(remaining > 0); if (*f != '\\') { - /* A literal literal, copy verbatim */ + /* A literal, copy verbatim */ *(t++) = *f; continue; } diff --git a/src/basic/exit-status.c b/src/basic/exit-status.c index d488cfc59f..59557f8afe 100644 --- a/src/basic/exit-status.c +++ b/src/basic/exit-status.c @@ -24,12 +24,12 @@ #include "macro.h" #include "set.h" -const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) { +const char* exit_status_to_string(int status, ExitStatusLevel level) { /* We cast to int here, so that -Wenum doesn't complain that * EXIT_SUCCESS/EXIT_FAILURE aren't in the enum */ - switch ((int) status) { + switch (status) { case EXIT_SUCCESS: return "SUCCESS"; @@ -39,7 +39,7 @@ const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) { } if (IN_SET(level, EXIT_STATUS_SYSTEMD, EXIT_STATUS_LSB)) { - switch ((int) status) { + switch (status) { case EXIT_CHDIR: return "CHDIR"; @@ -140,19 +140,19 @@ const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) { case EXIT_RUNTIME_DIRECTORY: return "RUNTIME_DIRECTORY"; - case EXIT_CHOWN: - return "CHOWN"; - case EXIT_MAKE_STARTER: return "MAKE_STARTER"; + case EXIT_CHOWN: + return "CHOWN"; + case EXIT_SMACK_PROCESS_LABEL: return "SMACK_PROCESS_LABEL"; } } if (level == EXIT_STATUS_LSB) { - switch ((int) status) { + switch (status) { case EXIT_INVALIDARGUMENT: return "INVALIDARGUMENT"; @@ -177,34 +177,23 @@ const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) { return NULL; } - -bool is_clean_exit(int code, int status, ExitStatusSet *success_status) { +bool is_clean_exit(int code, int status, ExitClean clean, ExitStatusSet *success_status) { if (code == CLD_EXITED) return status == 0 || (success_status && set_contains(success_status->status, INT_TO_PTR(status))); - /* If a daemon does not implement handlers for some of the - * signals that's not considered an unclean shutdown */ + /* If a daemon does not implement handlers for some of the signals that's not considered an unclean shutdown */ if (code == CLD_KILLED) - return IN_SET(status, SIGHUP, SIGINT, SIGTERM, SIGPIPE) || + return + (clean == EXIT_CLEAN_DAEMON && IN_SET(status, SIGHUP, SIGINT, SIGTERM, SIGPIPE)) || (success_status && set_contains(success_status->signal, INT_TO_PTR(status))); return false; } -bool is_clean_exit_lsb(int code, int status, ExitStatusSet *success_status) { - - if (is_clean_exit(code, status, success_status)) - return true; - - return - code == CLD_EXITED && - IN_SET(status, EXIT_NOTINSTALLED, EXIT_NOTCONFIGURED); -} - void exit_status_set_free(ExitStatusSet *x) { assert(x); diff --git a/src/basic/exit-status.h b/src/basic/exit-status.h index 2309f68815..0cfdfd7891 100644 --- a/src/basic/exit-status.h +++ b/src/basic/exit-status.h @@ -31,7 +31,7 @@ * https://refspecs.linuxbase.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html */ -typedef enum ExitStatus { +enum { /* EXIT_SUCCESS defined by libc */ /* EXIT_FAILURE defined by libc */ EXIT_INVALIDARGUMENT = 2, @@ -82,7 +82,7 @@ typedef enum ExitStatus { EXIT_MAKE_STARTER, EXIT_CHOWN, EXIT_SMACK_PROCESS_LABEL, -} ExitStatus; +}; typedef enum ExitStatusLevel { EXIT_STATUS_MINIMAL, /* only cover libc EXIT_STATUS/EXIT_FAILURE */ @@ -96,10 +96,14 @@ typedef struct ExitStatusSet { Set *signal; } ExitStatusSet; -const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) _const_; +const char* exit_status_to_string(int status, ExitStatusLevel level) _const_; -bool is_clean_exit(int code, int status, ExitStatusSet *success_status); -bool is_clean_exit_lsb(int code, int status, ExitStatusSet *success_status); +typedef enum ExitClean { + EXIT_CLEAN_DAEMON, + EXIT_CLEAN_COMMAND, +} ExitClean; + +bool is_clean_exit(int code, int status, ExitClean clean, ExitStatusSet *success_status); void exit_status_set_free(ExitStatusSet *x); bool exit_status_set_is_empty(ExitStatusSet *x); diff --git a/src/basic/fileio.c b/src/basic/fileio.c index f183de4999..1cfb7a98f5 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -37,6 +37,7 @@ #include "hexdecoct.h" #include "log.h" #include "macro.h" +#include "missing.h" #include "parse-util.h" #include "path-util.h" #include "random-util.h" @@ -47,6 +48,8 @@ #include "umask-util.h" #include "utf8.h" +#define READ_FULL_BYTES_MAX (4U*1024U*1024U) + int write_string_stream(FILE *f, const char *line, bool enforce_newline) { assert(f); @@ -230,7 +233,7 @@ int read_full_stream(FILE *f, char **contents, size_t *size) { if (S_ISREG(st.st_mode)) { /* Safety check */ - if (st.st_size > 4*1024*1024) + if (st.st_size > READ_FULL_BYTES_MAX) return -E2BIG; /* Start with the right file size, but be prepared for @@ -245,26 +248,31 @@ int read_full_stream(FILE *f, char **contents, size_t *size) { char *t; size_t k; - t = realloc(buf, n+1); + t = realloc(buf, n + 1); if (!t) return -ENOMEM; buf = t; k = fread(buf + l, 1, n - l, f); + if (k > 0) + l += k; - if (k <= 0) { - if (ferror(f)) - return -errno; + if (ferror(f)) + return -errno; + if (feof(f)) break; - } - l += k; - n *= 2; + /* We aren't expecting fread() to return a short read outside + * of (error && eof), assert buffer is full and enlarge buffer. + */ + assert(l == n); /* Safety check */ - if (n > 4*1024*1024) + if (n >= READ_FULL_BYTES_MAX) return -E2BIG; + + n = MIN(n * 2, READ_FULL_BYTES_MAX); } buf[l] = 0; @@ -1035,7 +1043,7 @@ int fopen_temporary(const char *path, FILE **_f, char **_temp_path) { if (r < 0) return r; - fd = mkostemp_safe(t, O_WRONLY|O_CLOEXEC); + fd = mkostemp_safe(t); if (fd < 0) { free(t); return -errno; @@ -1068,7 +1076,7 @@ int fflush_and_check(FILE *f) { } /* This is much like mkostemp() but is subject to umask(). */ -int mkostemp_safe(char *pattern, int flags) { +int mkostemp_safe(char *pattern) { _cleanup_umask_ mode_t u = 0; int fd; @@ -1076,7 +1084,7 @@ int mkostemp_safe(char *pattern, int flags) { u = umask(077); - fd = mkostemp(pattern, flags); + fd = mkostemp(pattern, O_CLOEXEC); if (fd < 0) return -errno; @@ -1161,8 +1169,8 @@ int tempfn_random_child(const char *p, const char *extra, char **ret) { char *t, *x; uint64_t u; unsigned i; + int r; - assert(p); assert(ret); /* Turns this: @@ -1171,6 +1179,12 @@ int tempfn_random_child(const char *p, const char *extra, char **ret) { * /foo/bar/waldo/.#<extra>3c2b6219aa75d7d0 */ + if (!p) { + r = tmp_dir(&p); + if (r < 0) + return r; + } + if (!extra) extra = ""; @@ -1257,24 +1271,25 @@ int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space) int open_tmpfile_unlinkable(const char *directory, int flags) { char *p; - int fd; + int fd, r; - if (!directory) - directory = "/tmp"; + if (!directory) { + r = tmp_dir(&directory); + if (r < 0) + return r; + } /* Returns an unlinked temporary file that cannot be linked into the file system anymore */ -#ifdef O_TMPFILE /* Try O_TMPFILE first, if it is supported */ fd = open(directory, flags|O_TMPFILE|O_EXCL, S_IRUSR|S_IWUSR); if (fd >= 0) return fd; -#endif /* Fall back to unguessable name + unlinking */ p = strjoina(directory, "/systemd-tmp-XXXXXX"); - fd = mkostemp_safe(p, flags); + fd = mkostemp_safe(p); if (fd < 0) return fd; @@ -1297,7 +1312,6 @@ int open_tmpfile_linkable(const char *target, int flags, char **ret_path) { * which case "ret_path" will be returned as NULL. If not possible a the tempoary path name used is returned in * "ret_path". Use link_tmpfile() below to rename the result after writing the file in full. */ -#ifdef O_TMPFILE { _cleanup_free_ char *dn = NULL; @@ -1313,7 +1327,6 @@ int open_tmpfile_linkable(const char *target, int flags, char **ret_path) { log_debug_errno(errno, "Failed to use O_TMPFILE on %s: %m", dn); } -#endif r = tempfn_random(target, NULL, &tmp); if (r < 0) diff --git a/src/basic/fileio.h b/src/basic/fileio.h index 9ac497d9eb..b58c83e64a 100644 --- a/src/basic/fileio.h +++ b/src/basic/fileio.h @@ -71,7 +71,7 @@ int search_and_fopen_nulstr(const char *path, const char *mode, const char *root int fflush_and_check(FILE *f); int fopen_temporary(const char *path, FILE **_f, char **_temp_path); -int mkostemp_safe(char *pattern, int flags); +int mkostemp_safe(char *pattern); int tempfn_xxxxxx(const char *p, const char *extra, char **ret); int tempfn_random(const char *p, const char *extra, char **ret); diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index f0c6f3265e..48952a1c26 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -496,34 +496,94 @@ int get_files_in_directory(const char *path, char ***list) { return n; } -int var_tmp(char **ret) { - const char *tmp_dir = NULL; - const char *env_tmp_dir = NULL; - char *c = NULL; - int r; +static int getenv_tmp_dir(const char **ret_path) { + const char *n; + int r, ret = 0; - assert(ret); + assert(ret_path); - env_tmp_dir = getenv("TMPDIR"); - if (env_tmp_dir != NULL) { - r = is_dir(env_tmp_dir, true); - if (r < 0 && r != -ENOENT) - return r; - if (r > 0) - tmp_dir = env_tmp_dir; + /* We use the same order of environment variables python uses in tempfile.gettempdir(): + * https://docs.python.org/3/library/tempfile.html#tempfile.gettempdir */ + FOREACH_STRING(n, "TMPDIR", "TEMP", "TMP") { + const char *e; + + e = secure_getenv(n); + if (!e) + continue; + if (!path_is_absolute(e)) { + r = -ENOTDIR; + goto next; + } + if (!path_is_safe(e)) { + r = -EPERM; + goto next; + } + + r = is_dir(e, true); + if (r < 0) + goto next; + if (r == 0) { + r = -ENOTDIR; + goto next; + } + + *ret_path = e; + return 1; + + next: + /* Remember first error, to make this more debuggable */ + if (ret >= 0) + ret = r; } - if (!tmp_dir) - tmp_dir = "/var/tmp"; + if (ret < 0) + return ret; - c = strdup(tmp_dir); - if (!c) - return -ENOMEM; - *ret = c; + *ret_path = NULL; + return ret; +} + +static int tmp_dir_internal(const char *def, const char **ret) { + const char *e; + int r, k; + + assert(def); + assert(ret); + r = getenv_tmp_dir(&e); + if (r > 0) { + *ret = e; + return 0; + } + + k = is_dir(def, true); + if (k == 0) + k = -ENOTDIR; + if (k < 0) + return r < 0 ? r : k; + + *ret = def; return 0; } +int var_tmp_dir(const char **ret) { + + /* Returns the location for "larger" temporary files, that is backed by physical storage if available, and thus + * even might survive a boot: /var/tmp. If $TMPDIR (or related environment variables) are set, its value is + * returned preferably however. Note that both this function and tmp_dir() below are affected by $TMPDIR, + * making it a variable that overrides all temporary file storage locations. */ + + return tmp_dir_internal("/var/tmp", ret); +} + +int tmp_dir(const char **ret) { + + /* Similar to var_tmp_dir() above, but returns the location for "smaller" temporary files, which is usually + * backed by an in-memory file system: /tmp. */ + + return tmp_dir_internal("/tmp", ret); +} + int inotify_add_watch_fd(int fd, int what, uint32_t mask) { char path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1]; int r; @@ -537,3 +597,186 @@ int inotify_add_watch_fd(int fd, int what, uint32_t mask) { return r; } + +int chase_symlinks(const char *path, const char *_root, char **ret) { + _cleanup_free_ char *buffer = NULL, *done = NULL, *root = NULL; + _cleanup_close_ int fd = -1; + unsigned max_follow = 32; /* how many symlinks to follow before giving up and returning ELOOP */ + char *todo; + int r; + + assert(path); + + /* This is a lot like canonicalize_file_name(), but takes an additional "root" parameter, that allows following + * symlinks relative to a root directory, instead of the root of the host. + * + * Note that "root" matters only if we encounter an absolute symlink, it's unused otherwise. Most importantly + * this means the path parameter passed in is not prefixed by it. + * + * Algorithmically this operates on two path buffers: "done" are the components of the path we already + * processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we still need to + * process. On each iteration, we move one component from "todo" to "done", processing it's special meaning + * each time. The "todo" path always starts with at least one slash, the "done" path always ends in no + * slash. We always keep an O_PATH fd to the component we are currently processing, thus keeping lookup races + * at a minimum. */ + + r = path_make_absolute_cwd(path, &buffer); + if (r < 0) + return r; + + if (_root) { + r = path_make_absolute_cwd(_root, &root); + if (r < 0) + return r; + } + + fd = open("/", O_CLOEXEC|O_NOFOLLOW|O_PATH); + if (fd < 0) + return -errno; + + todo = buffer; + for (;;) { + _cleanup_free_ char *first = NULL; + _cleanup_close_ int child = -1; + struct stat st; + size_t n, m; + + /* Determine length of first component in the path */ + n = strspn(todo, "/"); /* The slashes */ + m = n + strcspn(todo + n, "/"); /* The entire length of the component */ + + /* Extract the first component. */ + first = strndup(todo, m); + if (!first) + return -ENOMEM; + + todo += m; + + /* Just a single slash? Then we reached the end. */ + if (isempty(first) || path_equal(first, "/")) + break; + + /* Just a dot? Then let's eat this up. */ + if (path_equal(first, "/.")) + continue; + + /* Two dots? Then chop off the last bit of what we already found out. */ + if (path_equal(first, "/..")) { + _cleanup_free_ char *parent = NULL; + int fd_parent = -1; + + if (isempty(done) || path_equal(done, "/")) + return -EINVAL; + + parent = dirname_malloc(done); + if (!parent) + return -ENOMEM; + + /* Don't allow this to leave the root dir */ + if (root && + path_startswith(done, root) && + !path_startswith(parent, root)) + return -EINVAL; + + free_and_replace(done, parent); + + fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH); + if (fd_parent < 0) + return -errno; + + safe_close(fd); + fd = fd_parent; + + continue; + } + + /* Otherwise let's see what this is. */ + child = openat(fd, first + n, O_CLOEXEC|O_NOFOLLOW|O_PATH); + if (child < 0) + return -errno; + + if (fstat(child, &st) < 0) + return -errno; + + if (S_ISLNK(st.st_mode)) { + _cleanup_free_ char *destination = NULL; + + /* This is a symlink, in this case read the destination. But let's make sure we don't follow + * symlinks without bounds. */ + if (--max_follow <= 0) + return -ELOOP; + + r = readlinkat_malloc(fd, first + n, &destination); + if (r < 0) + return r; + if (isempty(destination)) + return -EINVAL; + + if (path_is_absolute(destination)) { + + /* An absolute destination. Start the loop from the beginning, but use the root + * directory as base. */ + + safe_close(fd); + fd = open(root ?: "/", O_CLOEXEC|O_NOFOLLOW|O_PATH); + if (fd < 0) + return -errno; + + free_and_replace(buffer, destination); + + todo = buffer; + free(done); + + /* Note that we do not revalidate the root, we take it as is. */ + if (isempty(root)) + done = NULL; + else { + done = strdup(root); + if (!done) + return -ENOMEM; + } + + } else { + char *joined; + + /* A relative destination. If so, this is what we'll prefix what's left to do with what + * we just read, and start the loop again, but remain in the current directory. */ + + joined = strjoin("/", destination, todo, NULL); + if (!joined) + return -ENOMEM; + + free(buffer); + todo = buffer = joined; + } + + continue; + } + + /* If this is not a symlink, then let's just add the name we read to what we already verified. */ + if (!done) { + done = first; + first = NULL; + } else { + if (!strextend(&done, first, NULL)) + return -ENOMEM; + } + + /* And iterate again, but go one directory further down. */ + safe_close(fd); + fd = child; + child = -1; + } + + if (!done) { + /* Special case, turn the empty string into "/", to indicate the root directory. */ + done = strdup("/"); + if (!done) + return -ENOMEM; + } + + *ret = done; + done = NULL; + + return 0; +} diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index 075e5942b1..31df47cf1e 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -61,7 +61,8 @@ int mkfifo_atomic(const char *path, mode_t mode); int get_files_in_directory(const char *path, char ***list); -int var_tmp(char **ret); +int tmp_dir(const char **ret); +int var_tmp_dir(const char **ret); #define INOTIFY_EVENT_MAX (sizeof(struct inotify_event) + NAME_MAX + 1) @@ -76,3 +77,5 @@ union inotify_event_buffer { }; int inotify_add_watch_fd(int fd, int what, uint32_t mask); + +int chase_symlinks(const char *path, const char *_root, char **ret); diff --git a/src/basic/gunicode.c b/src/basic/gunicode.c index 542110503f..e6ac0545a4 100644 --- a/src/basic/gunicode.c +++ b/src/basic/gunicode.c @@ -26,7 +26,7 @@ char * utf8_prev_char (const char *p) { - while (1) + for (;;) { p--; if ((*p & 0xc0) != 0x80) diff --git a/src/basic/hostname-util.c b/src/basic/hostname-util.c index 13c3bb6446..e44a357287 100644 --- a/src/basic/hostname-util.c +++ b/src/basic/hostname-util.c @@ -163,7 +163,6 @@ char* hostname_cleanup(char *s) { *(d++) = *p; dot = false; } - } if (dot && d > s) diff --git a/src/basic/list.h b/src/basic/list.h index 5962aa4211..c3771a177f 100644 --- a/src/basic/list.h +++ b/src/basic/list.h @@ -142,6 +142,8 @@ } else { \ if ((_b->name##_prev = _a->name##_prev)) \ _b->name##_prev->name##_next = _b; \ + else \ + *_head = _b; \ _b->name##_next = _a; \ _a->name##_prev = _b; \ } \ diff --git a/src/basic/log.c b/src/basic/log.c index 49b4598b7c..4919d175da 100644 --- a/src/basic/log.c +++ b/src/basic/log.c @@ -133,7 +133,7 @@ static int create_log_socket(int type) { if (fd < 0) return -errno; - fd_inc_sndbuf(fd, SNDBUF_SIZE); + (void) fd_inc_sndbuf(fd, SNDBUF_SIZE); /* We need a blocking fd here since we'd otherwise lose messages way too early. However, let's not hang forever in the @@ -330,8 +330,6 @@ static int write_to_console( const char *file, int line, const char *func, - const char *object_field, - const char *object, const char *buffer) { char location[256], prefix[1 + DECIMAL_STR_MAX(int) + 2]; @@ -343,7 +341,7 @@ static int write_to_console( return 0; if (log_target == LOG_TARGET_CONSOLE_PREFIXED) { - sprintf(prefix, "<%i>", level); + xsprintf(prefix, "<%i>", level); IOVEC_SET_STRING(iovec[n++], prefix); } @@ -390,8 +388,6 @@ static int write_to_syslog( const char *file, int line, const char *func, - const char *object_field, - const char *object, const char *buffer) { char header_priority[2 + DECIMAL_STR_MAX(int) + 1], @@ -453,8 +449,6 @@ static int write_to_kmsg( const char *file, int line, const char *func, - const char *object_field, - const char *object, const char *buffer) { char header_priority[2 + DECIMAL_STR_MAX(int) + 1], @@ -485,7 +479,8 @@ static int log_do_header( int level, int error, const char *file, int line, const char *func, - const char *object_field, const char *object) { + const char *object_field, const char *object, + const char *extra_field, const char *extra) { snprintf(header, size, "PRIORITY=%i\n" @@ -495,6 +490,7 @@ static int log_do_header( "%s%s%s" "%s%.*i%s" "%s%s%s" + "%s%s%s" "SYSLOG_IDENTIFIER=%s\n", LOG_PRI(level), LOG_FAC(level), @@ -513,6 +509,9 @@ static int log_do_header( isempty(object) ? "" : object_field, isempty(object) ? "" : object, isempty(object) ? "" : "\n", + isempty(extra) ? "" : extra_field, + isempty(extra) ? "" : extra, + isempty(extra) ? "" : "\n", program_invocation_short_name); return 0; @@ -526,6 +525,8 @@ static int write_to_journal( const char *func, const char *object_field, const char *object, + const char *extra_field, + const char *extra, const char *buffer) { char header[LINE_MAX]; @@ -535,7 +536,7 @@ static int write_to_journal( if (journal_fd < 0) return 0; - log_do_header(header, sizeof(header), level, error, file, line, func, object_field, object); + log_do_header(header, sizeof(header), level, error, file, line, func, object_field, object, extra_field, extra); IOVEC_SET_STRING(iovec[0], header); IOVEC_SET_STRING(iovec[1], "MESSAGE="); @@ -559,10 +560,15 @@ static int log_dispatch( const char *func, const char *object_field, const char *object, + const char *extra, + const char *extra_field, char *buffer) { assert(buffer); + if (error < 0) + error = -error; + if (log_target == LOG_TARGET_NULL) return -error; @@ -570,9 +576,6 @@ static int log_dispatch( if ((level & LOG_FACMASK) == 0) level = log_facility | LOG_PRI(level); - if (error < 0) - error = -error; - do { char *e; int k = 0; @@ -589,7 +592,7 @@ static int log_dispatch( log_target == LOG_TARGET_JOURNAL_OR_KMSG || log_target == LOG_TARGET_JOURNAL) { - k = write_to_journal(level, error, file, line, func, object_field, object, buffer); + k = write_to_journal(level, error, file, line, func, object_field, object, extra_field, extra, buffer); if (k < 0) { if (k != -EAGAIN) log_close_journal(); @@ -600,7 +603,7 @@ static int log_dispatch( if (log_target == LOG_TARGET_SYSLOG_OR_KMSG || log_target == LOG_TARGET_SYSLOG) { - k = write_to_syslog(level, error, file, line, func, object_field, object, buffer); + k = write_to_syslog(level, error, file, line, func, buffer); if (k < 0) { if (k != -EAGAIN) log_close_syslog(); @@ -615,7 +618,7 @@ static int log_dispatch( log_target == LOG_TARGET_JOURNAL_OR_KMSG || log_target == LOG_TARGET_KMSG)) { - k = write_to_kmsg(level, error, file, line, func, object_field, object, buffer); + k = write_to_kmsg(level, error, file, line, func, buffer); if (k < 0) { log_close_kmsg(); log_open_console(); @@ -623,7 +626,7 @@ static int log_dispatch( } if (k <= 0) - (void) write_to_console(level, error, file, line, func, object_field, object, buffer); + (void) write_to_console(level, error, file, line, func, buffer); buffer = e; } while (buffer); @@ -649,7 +652,7 @@ int log_dump_internal( if (_likely_(LOG_PRI(level) > log_max_level)) return -error; - return log_dispatch(level, error, file, line, func, NULL, NULL, buffer); + return log_dispatch(level, error, file, line, func, NULL, NULL, NULL, NULL, buffer); } int log_internalv( @@ -676,7 +679,7 @@ int log_internalv( vsnprintf(buffer, sizeof(buffer), format, ap); - return log_dispatch(level, error, file, line, func, NULL, NULL, buffer); + return log_dispatch(level, error, file, line, func, NULL, NULL, NULL, NULL, buffer); } int log_internal( @@ -705,6 +708,8 @@ int log_object_internalv( const char *func, const char *object_field, const char *object, + const char *extra_field, + const char *extra, const char *format, va_list ap) { @@ -738,7 +743,7 @@ int log_object_internalv( vsnprintf(b, l, format, ap); - return log_dispatch(level, error, file, line, func, object_field, object, buffer); + return log_dispatch(level, error, file, line, func, object_field, object, extra_field, extra, buffer); } int log_object_internal( @@ -749,13 +754,15 @@ int log_object_internal( const char *func, const char *object_field, const char *object, + const char *extra_field, + const char *extra, const char *format, ...) { va_list ap; int r; va_start(ap, format); - r = log_object_internalv(level, error, file, line, func, object_field, object, format, ap); + r = log_object_internalv(level, error, file, line, func, object_field, object, extra_field, extra, format, ap); va_end(ap); return r; @@ -775,12 +782,12 @@ static void log_assert( return; DISABLE_WARNING_FORMAT_NONLITERAL; - xsprintf(buffer, format, text, file, line, func); + snprintf(buffer, sizeof buffer, format, text, file, line, func); REENABLE_WARNING; log_abort_msg = buffer; - log_dispatch(level, 0, file, line, func, NULL, NULL, buffer); + log_dispatch(level, 0, file, line, func, NULL, NULL, NULL, NULL, buffer); } noreturn void log_assert_failed(const char *text, const char *file, int line, const char *func) { @@ -888,7 +895,7 @@ int log_struct_internal( bool fallback = false; /* If the journal is available do structured logging */ - log_do_header(header, sizeof(header), level, error, file, line, func, NULL, NULL); + log_do_header(header, sizeof(header), level, error, file, line, func, NULL, NULL, NULL, NULL); IOVEC_SET_STRING(iovec[n++], header); va_start(ap, format); @@ -935,7 +942,7 @@ int log_struct_internal( if (!found) return -error; - return log_dispatch(level, error, file, line, func, NULL, NULL, buf + 8); + return log_dispatch(level, error, file, line, func, NULL, NULL, NULL, NULL, buf + 8); } int log_set_target_from_string(const char *e) { @@ -960,7 +967,7 @@ int log_set_max_level_from_string(const char *e) { return 0; } -static int parse_proc_cmdline_item(const char *key, const char *value) { +static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { /* * The systemd.log_xyz= settings are parsed by all tools, and @@ -1005,7 +1012,7 @@ void log_parse_environment(void) { /* Only try to read the command line in daemons. We assume that anything that has a controlling tty is user stuff. */ - (void) parse_proc_cmdline(parse_proc_cmdline_item); + (void) parse_proc_cmdline(parse_proc_cmdline_item, NULL, true); e = secure_getenv("SYSTEMD_LOG_TARGET"); if (e && log_set_target_from_string(e) < 0) diff --git a/src/basic/log.h b/src/basic/log.h index b6356228d9..2afee20bb5 100644 --- a/src/basic/log.h +++ b/src/basic/log.h @@ -100,18 +100,22 @@ int log_object_internal( const char *func, const char *object_field, const char *object, - const char *format, ...) _printf_(8,9); + const char *extra_field, + const char *extra, + const char *format, ...) _printf_(10,11); int log_object_internalv( int level, int error, - const char*file, + const char *file, int line, const char *func, const char *object_field, const char *object, + const char *extra_field, + const char *extra, const char *format, - va_list ap) _printf_(8,0); + va_list ap) _printf_(9,0); int log_struct_internal( int level, diff --git a/src/basic/missing.h b/src/basic/missing.h index b1272f8799..4c013be608 100644 --- a/src/basic/missing.h +++ b/src/basic/missing.h @@ -445,6 +445,10 @@ struct btrfs_ioctl_quota_ctl_args { #define CGROUP2_SUPER_MAGIC 0x63677270 #endif +#ifndef CLONE_NEWCGROUP +#define CLONE_NEWCGROUP 0x02000000 +#endif + #ifndef TMPFS_MAGIC #define TMPFS_MAGIC 0x01021994 #endif @@ -469,24 +473,44 @@ struct btrfs_ioctl_quota_ctl_args { #define MS_MOVE 8192 #endif +#ifndef MS_REC +#define MS_REC 16384 +#endif + #ifndef MS_PRIVATE -#define MS_PRIVATE (1 << 18) +#define MS_PRIVATE (1<<18) #endif -#ifndef SCM_SECURITY -#define SCM_SECURITY 0x03 +#ifndef MS_REC +#define MS_REC (1<<19) +#endif + +#ifndef MS_SHARED +#define MS_SHARED (1<<20) +#endif + +#ifndef MS_RELATIME +#define MS_RELATIME (1<<21) +#endif + +#ifndef MS_KERNMOUNT +#define MS_KERNMOUNT (1<<22) +#endif + +#ifndef MS_I_VERSION +#define MS_I_VERSION (1<<23) #endif #ifndef MS_STRICTATIME -#define MS_STRICTATIME (1<<24) +#define MS_STRICTATIME (1<<24) #endif -#ifndef MS_REC -#define MS_REC 16384 +#ifndef MS_LAZYTIME +#define MS_LAZYTIME (1<<25) #endif -#ifndef MS_SHARED -#define MS_SHARED (1<<20) +#ifndef SCM_SECURITY +#define SCM_SECURITY 0x03 #endif #ifndef PR_SET_NO_NEW_PRIVS @@ -533,12 +557,21 @@ struct btrfs_ioctl_quota_ctl_args { # define DRM_IOCTL_DROP_MASTER _IO('d', 0x1f) #endif -#if defined(__i386__) || defined(__x86_64__) - -/* The precise definition of __O_TMPFILE is arch specific, so let's - * just define this on x86 where we know the value. */ +/* The precise definition of __O_TMPFILE is arch specific; use the + * values defined by the kernel (note: some are hexa, some are octal, + * duplicated as-is from the kernel definitions): + * - alpha, parisc, sparc: each has a specific value; + * - others: they use the "generic" value. + */ #ifndef __O_TMPFILE +#if defined(__alpha__) +#define __O_TMPFILE 0100000000 +#elif defined(__parisc__) || defined(__hppa__) +#define __O_TMPFILE 0400000000 +#elif defined(__sparc__) || defined(__sparc64__) +#define __O_TMPFILE 0x2000000 +#else #define __O_TMPFILE 020000000 #endif @@ -1039,6 +1072,10 @@ typedef int32_t key_serial_t; #define ETHERTYPE_LLDP 0x88cc #endif +#ifndef IFA_F_MCAUTOJOIN +#define IFA_F_MCAUTOJOIN 0x400 +#endif + #endif #include "missing_syscall.h" diff --git a/src/basic/mount-util.c b/src/basic/mount-util.c index 28dc778969..c8f8022578 100644 --- a/src/basic/mount-util.c +++ b/src/basic/mount-util.c @@ -36,6 +36,7 @@ #include "set.h" #include "stdio-util.h" #include "string-util.h" +#include "strv.h" static int fd_fdinfo_mnt_id(int fd, const char *filename, int flags, int *mnt_id) { char path[strlen("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)]; @@ -75,7 +76,6 @@ static int fd_fdinfo_mnt_id(int fd, const char *filename, int flags, int *mnt_id return safe_atoi(p, mnt_id); } - int fd_is_mount_point(int fd, const char *filename, int flags) { union file_handle_union h = FILE_HANDLE_INIT, h_parent = FILE_HANDLE_INIT; int mount_id = -1, mount_id_parent = -1; @@ -162,7 +162,7 @@ int fd_is_mount_point(int fd, const char *filename, int flags) { fallback_fdinfo: r = fd_fdinfo_mnt_id(fd, filename, flags, &mount_id); - if (r == -EOPNOTSUPP) + if (IN_SET(r, -EOPNOTSUPP, -EACCES)) goto fallback_fstat; if (r < 0) return r; @@ -288,10 +288,12 @@ int umount_recursive(const char *prefix, int flags) { continue; if (umount2(p, flags) < 0) { - r = -errno; + r = log_debug_errno(errno, "Failed to umount %s: %m", p); continue; } + log_debug("Successfully unmounted %s", p); + again = true; n++; @@ -312,24 +314,21 @@ static int get_mount_flags(const char *path, unsigned long *flags) { return 0; } -int bind_remount_recursive(const char *prefix, bool ro) { +int bind_remount_recursive(const char *prefix, bool ro, char **blacklist) { _cleanup_set_free_free_ Set *done = NULL; _cleanup_free_ char *cleaned = NULL; int r; - /* Recursively remount a directory (and all its submounts) - * read-only or read-write. If the directory is already - * mounted, we reuse the mount and simply mark it - * MS_BIND|MS_RDONLY (or remove the MS_RDONLY for read-write - * operation). If it isn't we first make it one. Afterwards we - * apply MS_BIND|MS_RDONLY (or remove MS_RDONLY) to all - * submounts we can access, too. When mounts are stacked on - * the same mount point we only care for each individual - * "top-level" mount on each point, as we cannot - * influence/access the underlying mounts anyway. We do not - * have any effect on future submounts that might get - * propagated, they migt be writable. This includes future - * submounts that have been triggered via autofs. */ + /* Recursively remount a directory (and all its submounts) read-only or read-write. If the directory is already + * mounted, we reuse the mount and simply mark it MS_BIND|MS_RDONLY (or remove the MS_RDONLY for read-write + * operation). If it isn't we first make it one. Afterwards we apply MS_BIND|MS_RDONLY (or remove MS_RDONLY) to + * all submounts we can access, too. When mounts are stacked on the same mount point we only care for each + * individual "top-level" mount on each point, as we cannot influence/access the underlying mounts anyway. We + * do not have any effect on future submounts that might get propagated, they migt be writable. This includes + * future submounts that have been triggered via autofs. + * + * If the "blacklist" parameter is specified it may contain a list of subtrees to exclude from the + * remount operation. Note that we'll ignore the blacklist for the top-level path. */ cleaned = strdup(prefix); if (!cleaned) @@ -386,6 +385,33 @@ int bind_remount_recursive(const char *prefix, bool ro) { if (r < 0) return r; + if (!path_startswith(p, cleaned)) + continue; + + /* Ignore this mount if it is blacklisted, but only if it isn't the top-level mount we shall + * operate on. */ + if (!path_equal(cleaned, p)) { + bool blacklisted = false; + char **i; + + STRV_FOREACH(i, blacklist) { + + if (path_equal(*i, cleaned)) + continue; + + if (!path_startswith(*i, cleaned)) + continue; + + if (path_startswith(p, *i)) { + blacklisted = true; + log_debug("Not remounting %s, because blacklisted by %s, called for %s", p, *i, cleaned); + break; + } + } + if (blacklisted) + continue; + } + /* Let's ignore autofs mounts. If they aren't * triggered yet, we want to avoid triggering * them, as we don't make any guarantees for @@ -397,12 +423,9 @@ int bind_remount_recursive(const char *prefix, bool ro) { continue; } - if (path_startswith(p, cleaned) && - !set_contains(done, p)) { - + if (!set_contains(done, p)) { r = set_consume(todo, p); p = NULL; - if (r == -EEXIST) continue; if (r < 0) @@ -419,8 +442,7 @@ int bind_remount_recursive(const char *prefix, bool ro) { if (!set_contains(done, cleaned) && !set_contains(todo, cleaned)) { - /* The prefix directory itself is not yet a - * mount, make it one. */ + /* The prefix directory itself is not yet a mount, make it one. */ if (mount(cleaned, cleaned, NULL, MS_BIND|MS_REC, NULL) < 0) return -errno; @@ -431,6 +453,8 @@ int bind_remount_recursive(const char *prefix, bool ro) { if (mount(NULL, prefix, NULL, orig_flags|MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0) return -errno; + log_debug("Made top-level directory %s a mount point.", prefix); + x = strdup(cleaned); if (!x) return -ENOMEM; @@ -448,8 +472,7 @@ int bind_remount_recursive(const char *prefix, bool ro) { if (r < 0) return r; - /* Deal with mount points that are obstructed by a - * later mount */ + /* Deal with mount points that are obstructed by a later mount */ r = path_is_mount_point(x, 0); if (r == -ENOENT || r == 0) continue; @@ -464,6 +487,7 @@ int bind_remount_recursive(const char *prefix, bool ro) { if (mount(NULL, x, NULL, orig_flags|MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0) return -errno; + log_debug("Remounted %s read-only.", x); } } } @@ -501,6 +525,7 @@ bool fstype_is_network(const char *fstype) { "glusterfs\0" "pvfs2\0" /* OrangeFS */ "ocfs2\0" + "lustre\0" ; const char *x; @@ -557,3 +582,108 @@ const char* mode_to_inaccessible_node(mode_t mode) { } return NULL; } + +#define FLAG(name) (flags & name ? STRINGIFY(name) "|" : "") +static char* mount_flags_to_string(long unsigned flags) { + char *x; + _cleanup_free_ char *y = NULL; + long unsigned overflow; + + overflow = flags & ~(MS_RDONLY | + MS_NOSUID | + MS_NODEV | + MS_NOEXEC | + MS_SYNCHRONOUS | + MS_REMOUNT | + MS_MANDLOCK | + MS_DIRSYNC | + MS_NOATIME | + MS_NODIRATIME | + MS_BIND | + MS_MOVE | + MS_REC | + MS_SILENT | + MS_POSIXACL | + MS_UNBINDABLE | + MS_PRIVATE | + MS_SLAVE | + MS_SHARED | + MS_RELATIME | + MS_KERNMOUNT | + MS_I_VERSION | + MS_STRICTATIME | + MS_LAZYTIME); + + if (flags == 0 || overflow != 0) + if (asprintf(&y, "%lx", overflow) < 0) + return NULL; + + x = strjoin(FLAG(MS_RDONLY), + FLAG(MS_NOSUID), + FLAG(MS_NODEV), + FLAG(MS_NOEXEC), + FLAG(MS_SYNCHRONOUS), + FLAG(MS_REMOUNT), + FLAG(MS_MANDLOCK), + FLAG(MS_DIRSYNC), + FLAG(MS_NOATIME), + FLAG(MS_NODIRATIME), + FLAG(MS_BIND), + FLAG(MS_MOVE), + FLAG(MS_REC), + FLAG(MS_SILENT), + FLAG(MS_POSIXACL), + FLAG(MS_UNBINDABLE), + FLAG(MS_PRIVATE), + FLAG(MS_SLAVE), + FLAG(MS_SHARED), + FLAG(MS_RELATIME), + FLAG(MS_KERNMOUNT), + FLAG(MS_I_VERSION), + FLAG(MS_STRICTATIME), + FLAG(MS_LAZYTIME), + y, NULL); + if (!x) + return NULL; + if (!y) + x[strlen(x) - 1] = '\0'; /* truncate the last | */ + return x; +} + +int mount_verbose( + int error_log_level, + const char *what, + const char *where, + const char *type, + unsigned long flags, + const char *options) { + + _cleanup_free_ char *fl = NULL; + + fl = mount_flags_to_string(flags); + + if ((flags & MS_REMOUNT) && !what && !type) + log_debug("Remounting %s (%s \"%s\")...", + where, strnull(fl), strempty(options)); + else if (!what && !type) + log_debug("Mounting %s (%s \"%s\")...", + where, strnull(fl), strempty(options)); + else if ((flags & MS_BIND) && !type) + log_debug("Bind-mounting %s on %s (%s \"%s\")...", + what, where, strnull(fl), strempty(options)); + else + log_debug("Mounting %s on %s (%s \"%s\")...", + strna(type), where, strnull(fl), strempty(options)); + if (mount(what, where, type, flags, options) < 0) + return log_full_errno(error_log_level, errno, + "Failed to mount %s on %s (%s \"%s\"): %m", + strna(type), where, strnull(fl), strempty(options)); + return 0; +} + +int umount_verbose(const char *what) { + log_debug("Umounting %s...", what); + if (umount(what) < 0) + return log_error_errno(errno, "Failed to unmount %s: %m", what); + return 0; +} diff --git a/src/basic/mount-util.h b/src/basic/mount-util.h index f46989ebb3..4f305df19f 100644 --- a/src/basic/mount-util.h +++ b/src/basic/mount-util.h @@ -35,7 +35,7 @@ int path_is_mount_point(const char *path, int flags); int repeat_unmount(const char *path, int flags); int umount_recursive(const char *target, int flags); -int bind_remount_recursive(const char *prefix, bool ro); +int bind_remount_recursive(const char *prefix, bool ro, char **blacklist); int mount_move_root(const char *path); @@ -52,3 +52,12 @@ union file_handle_union { const char* mode_to_inaccessible_node(mode_t mode); #define FILE_HANDLE_INIT { .handle.handle_bytes = MAX_HANDLE_SZ } + +int mount_verbose( + int error_log_level, + const char *what, + const char *where, + const char *type, + unsigned long flags, + const char *options); +int umount_verbose(const char *where); diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c index 503a895731..c98815b9bc 100644 --- a/src/basic/parse-util.c +++ b/src/basic/parse-util.c @@ -29,6 +29,7 @@ #include "extract-word.h" #include "macro.h" #include "parse-util.h" +#include "process-util.h" #include "string-util.h" int parse_boolean(const char *v) { @@ -533,7 +534,7 @@ int parse_fractional_part_u(const char **p, size_t digits, unsigned *res) { return 0; } -int parse_percent(const char *p) { +int parse_percent_unbounded(const char *p) { const char *pc, *n; unsigned v; int r; @@ -546,8 +547,30 @@ int parse_percent(const char *p) { r = safe_atou(n, &v); if (r < 0) return r; + + return (int) v; +} + +int parse_percent(const char *p) { + int v; + + v = parse_percent_unbounded(p); if (v > 100) return -ERANGE; - return (int) v; + return v; +} + +int parse_nice(const char *p, int *ret) { + int n, r; + + r = safe_atoi(p, &n); + if (r < 0) + return r; + + if (!nice_is_valid(n)) + return -ERANGE; + + *ret = n; + return 0; } diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h index 73441bb6fd..461e1cd4d8 100644 --- a/src/basic/parse-util.h +++ b/src/basic/parse-util.h @@ -106,4 +106,7 @@ int safe_atod(const char *s, double *ret_d); int parse_fractional_part_u(const char **s, size_t digits, unsigned *res); +int parse_percent_unbounded(const char *p); int parse_percent(const char *p); + +int parse_nice(const char *p, int *ret); diff --git a/src/basic/path-util.c b/src/basic/path-util.c index b2fa81a294..fd38f51c4c 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -34,9 +34,11 @@ #include "alloc-util.h" #include "extract-word.h" #include "fs-util.h" +#include "glob-util.h" #include "log.h" #include "macro.h" #include "missing.h" +#include "parse-util.h" #include "path-util.h" #include "stat-util.h" #include "string-util.h" @@ -286,9 +288,7 @@ char **path_strv_resolve(char **l, const char *prefix) { } else { /* canonicalized path goes outside of * prefix, keep the original path instead */ - free(u); - u = orig; - orig = NULL; + free_and_replace(u, orig); } } else free(t); @@ -354,6 +354,16 @@ char* path_startswith(const char *path, const char *prefix) { assert(path); assert(prefix); + /* Returns a pointer to the start of the first component after the parts matched by + * the prefix, iff + * - both paths are absolute or both paths are relative, + * and + * - each component in prefix in turn matches a component in path at the same position. + * An empty string will be returned when the prefix and path are equivalent. + * + * Returns NULL otherwise. + */ + if ((path[0] == '/') != (prefix[0] == '/')) return NULL; @@ -810,7 +820,78 @@ bool is_device_path(const char *path) { /* Returns true on paths that refer to a device, either in * sysfs or in /dev */ - return - path_startswith(path, "/dev/") || - path_startswith(path, "/sys/"); + return path_startswith(path, "/dev/") || + path_startswith(path, "/sys/"); +} + +bool is_deviceallow_pattern(const char *path) { + return path_startswith(path, "/dev/") || + startswith(path, "block-") || + startswith(path, "char-"); +} + +int systemd_installation_has_version(const char *root, unsigned minimal_version) { + const char *pattern; + int r; + + /* Try to guess if systemd installation is later than the specified version. This + * is hacky and likely to yield false negatives, particularly if the installation + * is non-standard. False positives should be relatively rare. + */ + + NULSTR_FOREACH(pattern, + /* /lib works for systems without usr-merge, and for systems with a sane + * usr-merge, where /lib is a symlink to /usr/lib. /usr/lib is necessary + * for Gentoo which does a merge without making /lib a symlink. + */ + "lib/systemd/libsystemd-shared-*.so\0" + "usr/lib/systemd/libsystemd-shared-*.so\0") { + + _cleanup_strv_free_ char **names = NULL; + _cleanup_free_ char *path = NULL; + char *c, **name; + + path = prefix_root(root, pattern); + if (!path) + return -ENOMEM; + + r = glob_extend(&names, path); + if (r == -ENOENT) + continue; + if (r < 0) + return r; + + assert_se((c = endswith(path, "*.so"))); + *c = '\0'; /* truncate the glob part */ + + STRV_FOREACH(name, names) { + /* This is most likely to run only once, hence let's not optimize anything. */ + char *t, *t2; + unsigned version; + + t = startswith(*name, path); + if (!t) + continue; + + t2 = endswith(t, ".so"); + if (!t2) + continue; + + t2[0] = '\0'; /* truncate the suffix */ + + r = safe_atou(t, &version); + if (r < 0) { + log_debug_errno(r, "Found libsystemd shared at \"%s.so\", but failed to parse version: %m", *name); + continue; + } + + log_debug("Found libsystemd shared at \"%s.so\", version %u (%s).", + *name, version, + version >= minimal_version ? "OK" : "too old"); + if (version >= minimal_version) + return true; + } + } + + return false; } diff --git a/src/basic/path-util.h b/src/basic/path-util.h index a27c13fcc3..66545f52d9 100644 --- a/src/basic/path-util.h +++ b/src/basic/path-util.h @@ -125,3 +125,6 @@ char *file_in_same_dir(const char *path, const char *filename); bool hidden_or_backup_file(const char *filename) _pure_; bool is_device_path(const char *path); +bool is_deviceallow_pattern(const char *path); + +int systemd_installation_has_version(const char *root, unsigned minimal_version); diff --git a/src/basic/prioq.c b/src/basic/prioq.c index d2ec516d29..4570b8e4ba 100644 --- a/src/basic/prioq.c +++ b/src/basic/prioq.c @@ -62,9 +62,7 @@ Prioq* prioq_free(Prioq *q) { return NULL; free(q->items); - free(q); - - return NULL; + return mfree(q); } int prioq_ensure_allocated(Prioq **q, compare_func_t compare_func) { diff --git a/src/basic/proc-cmdline.c b/src/basic/proc-cmdline.c index 0430beadaa..8297a222b7 100644 --- a/src/basic/proc-cmdline.c +++ b/src/basic/proc-cmdline.c @@ -42,7 +42,9 @@ int proc_cmdline(char **ret) { return read_one_line_file("/proc/cmdline", ret); } -int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value)) { +int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value, void *data), + void *data, + bool strip_prefix) { _cleanup_free_ char *line = NULL; const char *p; int r; @@ -56,7 +58,7 @@ int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value)) { p = line; for (;;) { _cleanup_free_ char *word = NULL; - char *value = NULL; + char *value = NULL, *unprefixed; r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); if (r < 0) @@ -66,14 +68,15 @@ int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value)) { /* Filter out arguments that are intended only for the * initrd */ - if (!in_initrd() && startswith(word, "rd.")) + unprefixed = startswith(word, "rd."); + if (unprefixed && !in_initrd()) continue; value = strchr(word, '='); if (value) *(value++) = 0; - r = parse_item(word, value); + r = parse_item(strip_prefix && unprefixed ? unprefixed : word, value, data); if (r < 0) return r; } diff --git a/src/basic/proc-cmdline.h b/src/basic/proc-cmdline.h index 452642a2f5..6d6ee95c11 100644 --- a/src/basic/proc-cmdline.h +++ b/src/basic/proc-cmdline.h @@ -20,7 +20,9 @@ ***/ int proc_cmdline(char **ret); -int parse_proc_cmdline(int (*parse_word)(const char *key, const char *value)); +int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value, void *data), + void *data, + bool strip_prefix); int get_proc_cmdline_key(const char *parameter, char **value); int shall_restore_state(void); diff --git a/src/basic/process-util.h b/src/basic/process-util.h index 9f75088796..2568e3834f 100644 --- a/src/basic/process-util.h +++ b/src/basic/process-util.h @@ -26,6 +26,7 @@ #include <stdio.h> #include <string.h> #include <sys/types.h> +#include <sys/resource.h> #include "formats-util.h" #include "macro.h" @@ -103,3 +104,7 @@ int sched_policy_from_string(const char *s); void valgrind_summary_hack(void); int pid_compare_func(const void *a, const void *b); + +static inline bool nice_is_valid(int n) { + return n >= PRIO_MIN && n < PRIO_MAX; +} diff --git a/src/basic/replace-var.c b/src/basic/replace-var.c index 6a204b9ec3..0d21423a9c 100644 --- a/src/basic/replace-var.c +++ b/src/basic/replace-var.c @@ -107,6 +107,5 @@ char *replace_var(const char *text, char *(*lookup)(const char *variable, void*u return r; oom: - free(r); - return NULL; + return mfree(r); } diff --git a/src/basic/rm-rf.c b/src/basic/rm-rf.c index 43816fd1bb..baa70c2c8d 100644 --- a/src/basic/rm-rf.c +++ b/src/basic/rm-rf.c @@ -27,6 +27,7 @@ #include <unistd.h> #include "btrfs-util.h" +#include "cgroup-util.h" #include "fd-util.h" #include "log.h" #include "macro.h" @@ -36,9 +37,14 @@ #include "stat-util.h" #include "string-util.h" +static bool is_physical_fs(const struct statfs *sfs) { + return !is_temporary_fs(sfs) && !is_cgroup_fs(sfs); +} + int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) { _cleanup_closedir_ DIR *d = NULL; int ret = 0, r; + struct statfs sfs; assert(fd >= 0); @@ -47,13 +53,13 @@ int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) { if (!(flags & REMOVE_PHYSICAL)) { - r = fd_is_temporary_fs(fd); + r = fstatfs(fd, &sfs); if (r < 0) { safe_close(fd); - return r; + return -errno; } - if (!r) { + if (is_physical_fs(&sfs)) { /* We refuse to clean physical file systems * with this call, unless explicitly * requested. This is extra paranoia just to @@ -210,7 +216,7 @@ int rm_rf(const char *path, RemoveFlags flags) { if (statfs(path, &s) < 0) return -errno; - if (!is_temporary_fs(&s)) { + if (is_physical_fs(&s)) { log_error("Attempted to remove disk file system, and we can't allow that."); return -EPERM; } diff --git a/src/basic/set.h b/src/basic/set.h index 12f64a8c57..a5f8beb0c4 100644 --- a/src/basic/set.h +++ b/src/basic/set.h @@ -23,8 +23,8 @@ #include "hashmap.h" #include "macro.h" -Set *internal_set_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); -#define set_new(ops) internal_set_new(ops HASHMAP_DEBUG_SRC_ARGS) +Set *internal_set_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); +#define set_new(ops) internal_set_new(ops HASHMAP_DEBUG_SRC_ARGS) static inline Set *set_free(Set *s) { internal_hashmap_free(HASHMAP_BASE(s)); @@ -42,8 +42,8 @@ static inline Set *set_copy(Set *s) { return (Set*) internal_hashmap_copy(HASHMAP_BASE(s)); } -int internal_set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); -#define set_ensure_allocated(h, ops) internal_set_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) +int internal_set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); +#define set_ensure_allocated(h, ops) internal_set_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) int set_put(Set *s, const void *key); /* no set_update */ diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c index 385c3e4df3..1662c04705 100644 --- a/src/basic/socket-util.c +++ b/src/basic/socket-util.c @@ -441,7 +441,7 @@ const char* socket_address_get_path(const SocketAddress *a) { } bool socket_ipv6_is_supported(void) { - if (access("/proc/net/sockstat6", F_OK) != 0) + if (access("/proc/net/if_inet6", F_OK) != 0) return false; return true; @@ -1046,3 +1046,34 @@ int flush_accept(int fd) { close(cfd); } } + +struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t length) { + struct cmsghdr *cmsg; + + assert(mh); + + CMSG_FOREACH(cmsg, mh) + if (cmsg->cmsg_level == level && + cmsg->cmsg_type == type && + (length == (socklen_t) -1 || length == cmsg->cmsg_len)) + return cmsg; + + return NULL; +} + +int socket_ioctl_fd(void) { + int fd; + + /* Create a socket to invoke the various network interface ioctl()s on. Traditionally only AF_INET was good for + * that. Since kernel 4.6 AF_NETLINK works for this too. We first try to use AF_INET hence, but if that's not + * available (for example, because it is made unavailable via SECCOMP or such), we'll fall back to the more + * generic AF_NETLINK. */ + + fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC, 0); + if (fd < 0) + fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_GENERIC); + if (fd < 0) + return -errno; + + return fd; +} diff --git a/src/basic/socket-util.h b/src/basic/socket-util.h index e9230e4a9f..2ef572badb 100644 --- a/src/basic/socket-util.h +++ b/src/basic/socket-util.h @@ -142,6 +142,8 @@ int flush_accept(int fd); #define CMSG_FOREACH(cmsg, mh) \ for ((cmsg) = CMSG_FIRSTHDR(mh); (cmsg); (cmsg) = CMSG_NXTHDR((mh), (cmsg))) +struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t length); + /* Covers only file system and abstract AF_UNIX socket addresses, but not unnamed socket addresses. */ #define SOCKADDR_UN_LEN(sa) \ ({ \ @@ -152,3 +154,5 @@ int flush_accept(int fd); 1 + strnlen(_sa->sun_path+1, sizeof(_sa->sun_path)-1) : \ strnlen(_sa->sun_path, sizeof(_sa->sun_path))); \ }) + +int socket_ioctl_fd(void); diff --git a/src/basic/special.h b/src/basic/special.h index 084d3dfa23..5276bcf598 100644 --- a/src/basic/special.h +++ b/src/basic/special.h @@ -117,3 +117,6 @@ /* The scope unit systemd itself lives in. */ #define SPECIAL_INIT_SCOPE "init.scope" + +/* The root directory. */ +#define SPECIAL_ROOT_MOUNT "-.mount" diff --git a/src/basic/strbuf.c b/src/basic/strbuf.c index 4bef87d3c2..00aaf9e621 100644 --- a/src/basic/strbuf.c +++ b/src/basic/strbuf.c @@ -62,8 +62,7 @@ struct strbuf *strbuf_new(void) { err: free(str->buf); free(str->root); - free(str); - return NULL; + return mfree(str); } static void strbuf_node_cleanup(struct strbuf_node *node) { diff --git a/src/basic/string-util.c b/src/basic/string-util.c index 293a15f9c0..6b06e643c9 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -22,6 +22,7 @@ #include <stdint.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> #include "alloc-util.h" #include "gunicode.h" @@ -323,6 +324,14 @@ char ascii_tolower(char x) { return x; } +char ascii_toupper(char x) { + + if (x >= 'a' && x <= 'z') + return x - 'a' + 'A'; + + return x; +} + char *ascii_strlower(char *t) { char *p; @@ -334,6 +343,17 @@ char *ascii_strlower(char *t) { return t; } +char *ascii_strupper(char *t) { + char *p; + + assert(t); + + for (p = t; *p; p++) + *p = ascii_toupper(*p); + + return t; +} + char *ascii_strlower_n(char *t, size_t n) { size_t i; @@ -423,7 +443,7 @@ static char *ascii_ellipsize_mem(const char *s, size_t old_length, size_t new_le if (old_length <= 3 || old_length <= new_length) return strndup(s, old_length); - r = new0(char, new_length+1); + r = new0(char, new_length+3); if (!r) return NULL; @@ -433,12 +453,12 @@ static char *ascii_ellipsize_mem(const char *s, size_t old_length, size_t new_le x = new_length - 3; memcpy(r, s, x); - r[x] = '.'; - r[x+1] = '.'; - r[x+2] = '.'; + r[x] = 0xe2; /* tri-dot ellipsis: … */ + r[x+1] = 0x80; + r[x+2] = 0xa6; memcpy(r + x + 3, - s + old_length - (new_length - x - 3), - new_length - x - 3); + s + old_length - (new_length - x - 1), + new_length - x - 1); return r; } @@ -590,8 +610,7 @@ char *strreplace(const char *text, const char *old_string, const char *new_strin return r; oom: - free(r); - return NULL; + return mfree(r); } char *strip_tab_ansi(char **ibuf, size_t *_isz) { @@ -662,8 +681,7 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz) { if (ferror(f)) { fclose(f); - free(obuf); - return NULL; + return mfree(obuf); } fclose(f); @@ -803,25 +821,20 @@ int free_and_strdup(char **p, const char *s) { return 1; } -#pragma GCC push_options -#pragma GCC optimize("O0") +/* + * Pointer to memset is volatile so that compiler must de-reference + * the pointer and can't assume that it points to any function in + * particular (such as memset, which it then might further "optimize") + * This approach is inspired by openssl's crypto/mem_clr.c. + */ +typedef void *(*memset_t)(void *,int,size_t); -void* memory_erase(void *p, size_t l) { - volatile uint8_t* x = (volatile uint8_t*) p; - - /* This basically does what memset() does, but hopefully isn't - * optimized away by the compiler. One of those days, when - * glibc learns memset_s() we should replace this call by - * memset_s(), but until then this has to do. */ - - for (; l > 0; l--) - *(x++) = 'x'; +static volatile memset_t memset_func = memset; - return p; +void* memory_erase(void *p, size_t l) { + return memset_func(p, 'x', l); } -#pragma GCC pop_options - char* string_erase(char *x) { if (!x) diff --git a/src/basic/string-util.h b/src/basic/string-util.h index 1209e1e2e1..d029d538bd 100644 --- a/src/basic/string-util.h +++ b/src/basic/string-util.h @@ -70,6 +70,10 @@ static inline const char *empty_to_null(const char *p) { return isempty(p) ? NULL : p; } +static inline const char *strdash_if_empty(const char *str) { + return isempty(str) ? "-" : str; +} + static inline char *startswith(const char *s, const char *prefix) { size_t l; @@ -137,6 +141,9 @@ char ascii_tolower(char x); char *ascii_strlower(char *s); char *ascii_strlower_n(char *s, size_t n); +char ascii_toupper(char x); +char *ascii_strupper(char *s); + int ascii_strcasecmp_n(const char *a, const char *b, size_t n); int ascii_strcasecmp_nn(const char *a, size_t n, const char *b, size_t m); diff --git a/src/basic/strv.c b/src/basic/strv.c index 34e464d253..0eec868eed 100644 --- a/src/basic/strv.c +++ b/src/basic/strv.c @@ -87,8 +87,7 @@ void strv_clear(char **l) { char **strv_free(char **l) { strv_clear(l); - free(l); - return NULL; + return mfree(l); } char **strv_free_erase(char **l) { @@ -426,8 +425,7 @@ char *strv_join_quoted(char **l) { return buf; oom: - free(buf); - return NULL; + return mfree(buf); } int strv_push(char ***l, char *value) { @@ -869,8 +867,7 @@ char ***strv_free_free(char ***l) { for (i = l; *i; i++) strv_free(*i); - free(l); - return NULL; + return mfree(l); } char **strv_skip(char **l, size_t n) { diff --git a/src/basic/strv.h b/src/basic/strv.h index 683ce83a2a..385ad17779 100644 --- a/src/basic/strv.h +++ b/src/basic/strv.h @@ -96,10 +96,13 @@ bool strv_overlap(char **a, char **b) _pure_; #define STRV_FOREACH(s, l) \ for ((s) = (l); (s) && *(s); (s)++) -#define STRV_FOREACH_BACKWARDS(s, l) \ - STRV_FOREACH(s, l) \ - ; \ - for ((s)--; (l) && ((s) >= (l)); (s)--) +#define STRV_FOREACH_BACKWARDS(s, l) \ + for (s = ({ \ + char **_l = l; \ + _l ? _l + strv_length(_l) - 1U : NULL; \ + }); \ + (l) && ((s) >= (l)); \ + (s)--) #define STRV_FOREACH_PAIR(x, y, l) \ for ((x) = (l), (y) = (x+1); (x) && *(x) && *(y); (x) += 2, (y) = (x + 1)) @@ -141,6 +144,11 @@ void strv_print(char **l); }) #define STR_IN_SET(x, ...) strv_contains(STRV_MAKE(__VA_ARGS__), x) +#define STRPTR_IN_SET(x, ...) \ + ({ \ + const char* _x = (x); \ + _x && strv_contains(STRV_MAKE(__VA_ARGS__), _x); \ + }) #define FOREACH_STRING(x, ...) \ for (char **_l = ({ \ diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index df56d85317..eafdea9eb3 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -39,6 +39,7 @@ #include <unistd.h> #include "alloc-util.h" +#include "env-util.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" @@ -345,12 +346,7 @@ int open_terminal(const char *name, int mode) { } r = isatty(fd); - if (r < 0) { - safe_close(fd); - return -errno; - } - - if (!r) { + if (r == 0) { safe_close(fd); return -ENOTTY; } @@ -785,7 +781,7 @@ bool tty_is_vc_resolve(const char *tty) { } const char *default_term_for_tty(const char *tty) { - return tty && tty_is_vc_resolve(tty) ? "TERM=linux" : "TERM=vt220"; + return tty && tty_is_vc_resolve(tty) ? "linux" : "vt220"; } int fd_columns(int fd) { @@ -1191,12 +1187,9 @@ int open_terminal_in_namespace(pid_t pid, const char *name, int mode) { return receive_one_fd(pair[0], 0); } -bool terminal_is_dumb(void) { +static bool getenv_terminal_is_dumb(void) { const char *e; - if (!on_tty()) - return true; - e = getenv("TERM"); if (!e) return true; @@ -1204,15 +1197,25 @@ bool terminal_is_dumb(void) { return streq(e, "dumb"); } +bool terminal_is_dumb(void) { + if (!on_tty()) + return true; + + return getenv_terminal_is_dumb(); +} + bool colors_enabled(void) { static int enabled = -1; if (_unlikely_(enabled < 0)) { - const char *colors; - - colors = getenv("SYSTEMD_COLORS"); - if (colors) - enabled = parse_boolean(colors) != 0; + int val; + + val = getenv_bool("SYSTEMD_COLORS"); + if (val >= 0) + enabled = val; + else if (getpid() == 1) + /* PID1 outputs to the console without holding it open all the time */ + enabled = !getenv_terminal_is_dumb(); else enabled = !terminal_is_dumb(); } diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index 169ab772ff..b862bfaf05 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -36,6 +36,10 @@ #define ANSI_HIGHLIGHT_YELLOW "\x1B[0;1;33m" #define ANSI_HIGHLIGHT_BLUE "\x1B[0;1;34m" #define ANSI_HIGHLIGHT_UNDERLINE "\x1B[0;1;4m" +#define ANSI_HIGHLIGHT_RED_UNDERLINE "\x1B[0;1;4;31m" +#define ANSI_HIGHLIGHT_GREEN_UNDERLINE "\x1B[0;1;4;32m" +#define ANSI_HIGHLIGHT_YELLOW_UNDERLINE "\x1B[0;1;4;33m" +#define ANSI_HIGHLIGHT_BLUE_UNDERLINE "\x1B[0;1;4;34m" #define ANSI_NORMAL "\x1B[0m" #define ANSI_ERASE_TO_END_OF_LINE "\x1B[K" @@ -83,37 +87,24 @@ bool on_tty(void); bool terminal_is_dumb(void); bool colors_enabled(void); -static inline const char *ansi_underline(void) { - return colors_enabled() ? ANSI_UNDERLINE : ""; -} - -static inline const char *ansi_highlight(void) { - return colors_enabled() ? ANSI_HIGHLIGHT : ""; -} - -static inline const char *ansi_highlight_underline(void) { - return colors_enabled() ? ANSI_HIGHLIGHT_UNDERLINE : ""; -} - -static inline const char *ansi_highlight_red(void) { - return colors_enabled() ? ANSI_HIGHLIGHT_RED : ""; -} - -static inline const char *ansi_highlight_green(void) { - return colors_enabled() ? ANSI_HIGHLIGHT_GREEN : ""; -} - -static inline const char *ansi_highlight_yellow(void) { - return colors_enabled() ? ANSI_HIGHLIGHT_YELLOW : ""; -} - -static inline const char *ansi_highlight_blue(void) { - return colors_enabled() ? ANSI_HIGHLIGHT_BLUE : ""; -} - -static inline const char *ansi_normal(void) { - return colors_enabled() ? ANSI_NORMAL : ""; -} +#define DEFINE_ANSI_FUNC(name, NAME) \ + static inline const char *ansi_##name(void) { \ + return colors_enabled() ? ANSI_##NAME : ""; \ + } \ + struct __useless_struct_to_allow_trailing_semicolon__ + +DEFINE_ANSI_FUNC(underline, UNDERLINE); +DEFINE_ANSI_FUNC(highlight, HIGHLIGHT); +DEFINE_ANSI_FUNC(highlight_underline, HIGHLIGHT_UNDERLINE); +DEFINE_ANSI_FUNC(highlight_red, HIGHLIGHT_RED); +DEFINE_ANSI_FUNC(highlight_green, HIGHLIGHT_GREEN); +DEFINE_ANSI_FUNC(highlight_yellow, HIGHLIGHT_YELLOW); +DEFINE_ANSI_FUNC(highlight_blue, HIGHLIGHT_BLUE); +DEFINE_ANSI_FUNC(highlight_red_underline, HIGHLIGHT_RED_UNDERLINE); +DEFINE_ANSI_FUNC(highlight_green_underline, HIGHLIGHT_GREEN_UNDERLINE); +DEFINE_ANSI_FUNC(highlight_yellow_underline, HIGHLIGHT_YELLOW_UNDERLINE); +DEFINE_ANSI_FUNC(highlight_blue_underline, HIGHLIGHT_BLUE_UNDERLINE); +DEFINE_ANSI_FUNC(normal, NORMAL); int get_ctty_devnr(pid_t pid, dev_t *d); int get_ctty(pid_t, dev_t *_devnr, char **r); diff --git a/src/basic/time-util.c b/src/basic/time-util.c index 24e681bf85..fedff1362c 100644 --- a/src/basic/time-util.c +++ b/src/basic/time-util.c @@ -40,8 +40,6 @@ #include "strv.h" #include "time-util.h" -static nsec_t timespec_load_nsec(const struct timespec *ts); - static clockid_t map_clock_id(clockid_t c) { /* Some more exotic archs (s390, ppc, …) lack the "ALARM" flavour of the clocks. Thus, clock_gettime() will @@ -198,7 +196,7 @@ usec_t timespec_load(const struct timespec *ts) { (usec_t) ts->tv_nsec / NSEC_PER_USEC; } -static nsec_t timespec_load_nsec(const struct timespec *ts) { +nsec_t timespec_load_nsec(const struct timespec *ts) { assert(ts); if (ts->tv_sec == (time_t) -1 && ts->tv_nsec == (long) -1) @@ -254,32 +252,95 @@ struct timeval *timeval_store(struct timeval *tv, usec_t u) { return tv; } -static char *format_timestamp_internal(char *buf, size_t l, usec_t t, - bool utc, bool us) { +static char *format_timestamp_internal( + char *buf, + size_t l, + usec_t t, + bool utc, + bool us) { + + /* The weekdays in non-localized (English) form. We use this instead of the localized form, so that our + * generated timestamps may be parsed with parse_timestamp(), and always read the same. */ + static const char * const weekdays[] = { + [0] = "Sun", + [1] = "Mon", + [2] = "Tue", + [3] = "Wed", + [4] = "Thu", + [5] = "Fri", + [6] = "Sat", + }; + struct tm tm; time_t sec; - int k; + size_t n; assert(buf); - assert(l > 0); + if (l < + 3 + /* week day */ + 1 + 10 + /* space and date */ + 1 + 8 + /* space and time */ + (us ? 1 + 6 : 0) + /* "." and microsecond part */ + 1 + 1 + /* space and shortest possible zone */ + 1) + return NULL; /* Not enough space even for the shortest form. */ if (t <= 0 || t == USEC_INFINITY) + return NULL; /* Timestamp is unset */ + + sec = (time_t) (t / USEC_PER_SEC); /* Round down */ + if ((usec_t) sec != (t / USEC_PER_SEC)) + return NULL; /* overflow? */ + + if (!localtime_or_gmtime_r(&sec, &tm, utc)) return NULL; - sec = (time_t) (t / USEC_PER_SEC); - localtime_or_gmtime_r(&sec, &tm, utc); + /* Start with the week day */ + assert((size_t) tm.tm_wday < ELEMENTSOF(weekdays)); + memcpy(buf, weekdays[tm.tm_wday], 4); - if (us) - k = strftime(buf, l, "%a %Y-%m-%d %H:%M:%S", &tm); - else - k = strftime(buf, l, "%a %Y-%m-%d %H:%M:%S %Z", &tm); + /* Add the main components */ + if (strftime(buf + 3, l - 3, " %Y-%m-%d %H:%M:%S", &tm) <= 0) + return NULL; /* Doesn't fit */ - if (k <= 0) - return NULL; + /* Append the microseconds part, if that's requested */ if (us) { - snprintf(buf + strlen(buf), l - strlen(buf), ".%06llu", (unsigned long long) (t % USEC_PER_SEC)); - if (strftime(buf + strlen(buf), l - strlen(buf), " %Z", &tm) <= 0) - return NULL; + n = strlen(buf); + if (n + 8 > l) + return NULL; /* Microseconds part doesn't fit. */ + + sprintf(buf + n, ".%06llu", (unsigned long long) (t % USEC_PER_SEC)); + } + + /* Append the timezone */ + n = strlen(buf); + if (utc) { + /* If this is UTC then let's explicitly use the "UTC" string here, because gmtime_r() normally uses the + * obsolete "GMT" instead. */ + if (n + 5 > l) + return NULL; /* "UTC" doesn't fit. */ + + strcpy(buf + n, " UTC"); + + } else if (!isempty(tm.tm_zone)) { + size_t tn; + + /* An explicit timezone is specified, let's use it, if it fits */ + tn = strlen(tm.tm_zone); + if (n + 1 + tn + 1 > l) { + /* The full time zone does not fit in. Yuck. */ + + if (n + 1 + _POSIX_TZNAME_MAX + 1 > l) + return NULL; /* Not even enough space for the POSIX minimum (of 6)? In that case, complain that it doesn't fit */ + + /* So the time zone doesn't fit in fully, but the caller passed enough space for the POSIX + * minimum time zone length. In this case suppress the timezone entirely, in order not to dump + * an overly long, hard to read string on the user. This should be safe, because the user will + * assume the local timezone anyway if none is shown. And so does parse_timestamp(). */ + } else { + buf[n++] = ' '; + strcpy(buf + n, tm.tm_zone); + } } return buf; @@ -539,12 +600,11 @@ int parse_timestamp(const char *t, usec_t *usec) { { "Sat", 6 }, }; - const char *k; - const char *utc; + const char *k, *utc, *tzn = NULL; struct tm tm, copy; time_t x; usec_t x_usec, plus = 0, minus = 0, ret; - int r, weekday = -1; + int r, weekday = -1, dst = -1; unsigned i; /* @@ -609,15 +669,55 @@ int parse_timestamp(const char *t, usec_t *usec) { goto finish; } + /* See if the timestamp is suffixed with UTC */ utc = endswith_no_case(t, " UTC"); if (utc) t = strndupa(t, utc - t); + else { + const char *e = NULL; + int j; - x = ret / USEC_PER_SEC; + tzset(); + + /* See if the timestamp is suffixed by either the DST or non-DST local timezone. Note that we only + * support the local timezones here, nothing else. Not because we wouldn't want to, but simply because + * there are no nice APIs available to cover this. By accepting the local time zone strings, we make + * sure that all timestamps written by format_timestamp() can be parsed correctly, even though we don't + * support arbitrary timezone specifications. */ + + for (j = 0; j <= 1; j++) { + + if (isempty(tzname[j])) + continue; + + e = endswith_no_case(t, tzname[j]); + if (!e) + continue; + if (e == t) + continue; + if (e[-1] != ' ') + continue; + + break; + } + + if (IN_SET(j, 0, 1)) { + /* Found one of the two timezones specified. */ + t = strndupa(t, e - t - 1); + dst = j; + tzn = tzname[j]; + } + } + + x = (time_t) (ret / USEC_PER_SEC); x_usec = 0; - assert_se(localtime_or_gmtime_r(&x, &tm, utc)); - tm.tm_isdst = -1; + if (!localtime_or_gmtime_r(&x, &tm, utc)) + return -EINVAL; + + tm.tm_isdst = dst; + if (tzn) + tm.tm_zone = tzn; if (streq(t, "today")) { tm.tm_sec = tm.tm_min = tm.tm_hour = 0; @@ -634,7 +734,6 @@ int parse_timestamp(const char *t, usec_t *usec) { goto from_tm; } - for (i = 0; i < ELEMENTSOF(day_nr); i++) { size_t skip; @@ -727,7 +826,6 @@ parse_usec: return -EINVAL; x_usec = add; - } from_tm: diff --git a/src/basic/time-util.h b/src/basic/time-util.h index 1b058f0e49..558b0b5b7f 100644 --- a/src/basic/time-util.h +++ b/src/basic/time-util.h @@ -68,7 +68,9 @@ typedef struct triple_timestamp { #define USEC_PER_YEAR ((usec_t) (31557600ULL*USEC_PER_SEC)) #define NSEC_PER_YEAR ((nsec_t) (31557600ULL*NSEC_PER_SEC)) -#define FORMAT_TIMESTAMP_MAX ((4*4+1)+11+9+4+1) /* weekdays can be unicode */ +/* We assume a maximum timezone length of 6. TZNAME_MAX is not defined on Linux, but glibc internally initializes this + * to 6. Let's rely on that. */ +#define FORMAT_TIMESTAMP_MAX (3+1+10+1+8+1+6+1+6+1) #define FORMAT_TIMESTAMP_WIDTH 28 /* when outputting, assume this width */ #define FORMAT_TIMESTAMP_RELATIVE_MAX 256 #define FORMAT_TIMESPAN_MAX 64 @@ -109,6 +111,7 @@ static inline bool triple_timestamp_is_set(triple_timestamp *ts) { usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock); usec_t timespec_load(const struct timespec *ts) _pure_; +nsec_t timespec_load_nsec(const struct timespec *ts) _pure_; struct timespec *timespec_store(struct timespec *ts, usec_t u); usec_t timeval_load(const struct timeval *tv) _pure_; diff --git a/src/basic/user-util.c b/src/basic/user-util.c index e9d668ddfc..de6c93056e 100644 --- a/src/basic/user-util.c +++ b/src/basic/user-util.c @@ -29,16 +29,20 @@ #include <string.h> #include <sys/stat.h> #include <unistd.h> +#include <utmp.h> -#include "missing.h" #include "alloc-util.h" #include "fd-util.h" +#include "fileio.h" #include "formats-util.h" #include "macro.h" +#include "missing.h" #include "parse-util.h" #include "path-util.h" #include "string-util.h" +#include "strv.h" #include "user-util.h" +#include "utf8.h" bool uid_is_valid(uid_t uid) { @@ -173,6 +177,35 @@ int get_user_creds( return 0; } +int get_user_creds_clean( + const char **username, + uid_t *uid, gid_t *gid, + const char **home, + const char **shell) { + + int r; + + /* Like get_user_creds(), but resets home/shell to NULL if they don't contain anything relevant. */ + + r = get_user_creds(username, uid, gid, home, shell); + if (r < 0) + return r; + + if (shell && + (isempty(*shell) || PATH_IN_SET(*shell, + "/bin/nologin", + "/sbin/nologin", + "/usr/bin/nologin", + "/usr/sbin/nologin"))) + *shell = NULL; + + if (home && + (isempty(*home) || path_equal(*home, "/"))) + *home = NULL; + + return 0; +} + int get_group_creds(const char **groupname, gid_t *gid) { struct group *g; gid_t id; @@ -427,9 +460,11 @@ int get_shell(char **_s) { } int reset_uid_gid(void) { + int r; - if (setgroups(0, NULL) < 0) - return -errno; + r = maybe_setgroups(0, NULL); + if (r < 0) + return r; if (setresgid(0, 0, 0) < 0) return -errno; @@ -479,3 +514,123 @@ int take_etc_passwd_lock(const char *root) { return fd; } + +bool valid_user_group_name(const char *u) { + const char *i; + long sz; + + /* Checks if the specified name is a valid user/group name. */ + + if (isempty(u)) + return false; + + if (!(u[0] >= 'a' && u[0] <= 'z') && + !(u[0] >= 'A' && u[0] <= 'Z') && + u[0] != '_') + return false; + + for (i = u+1; *i; i++) { + if (!(*i >= 'a' && *i <= 'z') && + !(*i >= 'A' && *i <= 'Z') && + !(*i >= '0' && *i <= '9') && + *i != '_' && + *i != '-') + return false; + } + + sz = sysconf(_SC_LOGIN_NAME_MAX); + assert_se(sz > 0); + + if ((size_t) (i-u) > (size_t) sz) + return false; + + if ((size_t) (i-u) > UT_NAMESIZE - 1) + return false; + + return true; +} + +bool valid_user_group_name_or_id(const char *u) { + + /* Similar as above, but is also fine with numeric UID/GID specifications, as long as they are in the right + * range, and not the invalid user ids. */ + + if (isempty(u)) + return false; + + if (valid_user_group_name(u)) + return true; + + return parse_uid(u, NULL) >= 0; +} + +bool valid_gecos(const char *d) { + + if (!d) + return false; + + if (!utf8_is_valid(d)) + return false; + + if (string_has_cc(d, NULL)) + return false; + + /* Colons are used as field separators, and hence not OK */ + if (strchr(d, ':')) + return false; + + return true; +} + +bool valid_home(const char *p) { + + if (isempty(p)) + return false; + + if (!utf8_is_valid(p)) + return false; + + if (string_has_cc(p, NULL)) + return false; + + if (!path_is_absolute(p)) + return false; + + if (!path_is_safe(p)) + return false; + + /* Colons are used as field separators, and hence not OK */ + if (strchr(p, ':')) + return false; + + return true; +} + +int maybe_setgroups(size_t size, const gid_t *list) { + int r; + + /* Check if setgroups is allowed before we try to drop all the auxiliary groups */ + if (size == 0) { /* Dropping all aux groups? */ + _cleanup_free_ char *setgroups_content = NULL; + bool can_setgroups; + + r = read_one_line_file("/proc/self/setgroups", &setgroups_content); + if (r == -ENOENT) + /* Old kernels don't have /proc/self/setgroups, so assume we can use setgroups */ + can_setgroups = true; + else if (r < 0) + return r; + else + can_setgroups = streq(setgroups_content, "allow"); + + if (!can_setgroups) { + log_debug("Skipping setgroups(), /proc/self/setgroups is set to 'deny'"); + return 0; + } + } + + if (setgroups(size, list) < 0) + return -errno; + + return 0; +} diff --git a/src/basic/user-util.h b/src/basic/user-util.h index 8026eca3f4..dfea561bde 100644 --- a/src/basic/user-util.h +++ b/src/basic/user-util.h @@ -20,6 +20,7 @@ ***/ #include <stdbool.h> +#include <stdint.h> #include <sys/types.h> #include <unistd.h> @@ -39,6 +40,7 @@ char* getlogname_malloc(void); char* getusername_malloc(void); int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **home, const char **shell); +int get_user_creds_clean(const char **username, uid_t *uid, gid_t *gid, const char **home, const char **shell); int get_group_creds(const char **groupname, gid_t *gid); char* uid_to_name(uid_t uid); @@ -57,8 +59,19 @@ int take_etc_passwd_lock(const char *root); #define UID_INVALID ((uid_t) -1) #define GID_INVALID ((gid_t) -1) -/* The following macros add 1 when converting things, since UID 0 is a - * valid UID, while the pointer NULL is special */ +/* Let's pick a UIDs within the 16bit range, so that we are compatible with containers using 16bit + * user namespacing. At least on Fedora normal users are allocated until UID 60000, hence do not + * allocate from below this. Also stay away from the upper end of the range as that is often used + * for overflow/nobody users. */ +#define DYNAMIC_UID_MIN ((uid_t) UINT32_C(0x0000EF00)) +#define DYNAMIC_UID_MAX ((uid_t) UINT32_C(0x0000FFEF)) + +static inline bool uid_is_dynamic(uid_t uid) { + return DYNAMIC_UID_MIN <= uid && uid <= DYNAMIC_UID_MAX; +} + +/* The following macros add 1 when converting things, since UID 0 is a valid UID, while the pointer + * NULL is special */ #define PTR_TO_UID(p) ((uid_t) (((uintptr_t) (p))-1)) #define UID_TO_PTR(u) ((void*) (((uintptr_t) (u))+1)) @@ -68,3 +81,10 @@ int take_etc_passwd_lock(const char *root); static inline bool userns_supported(void) { return access("/proc/self/uid_map", F_OK) >= 0; } + +bool valid_user_group_name(const char *u); +bool valid_user_group_name_or_id(const char *u); +bool valid_gecos(const char *d); +bool valid_home(const char *p); + +int maybe_setgroups(size_t size, const gid_t *list); diff --git a/src/basic/util.c b/src/basic/util.c index 9d66d28eb7..ec7939dc83 100644 --- a/src/basic/util.c +++ b/src/basic/util.c @@ -467,7 +467,7 @@ bool in_initrd(void) { * 2. the root file system must be a memory file system * * The second check is extra paranoia, since misdetecting an - * initrd can have bad bad consequences due the initrd + * initrd can have bad consequences due the initrd * emptying when transititioning to the main systemd. */ diff --git a/src/basic/util.h b/src/basic/util.h index 44497dcd78..bb2fc318ef 100644 --- a/src/basic/util.h +++ b/src/basic/util.h @@ -61,6 +61,10 @@ static inline const char* one_zero(bool b) { return b ? "1" : "0"; } +static inline const char* enable_disable(bool b) { + return b ? "enable" : "disable"; +} + void execute_directories(const char* const* directories, usec_t timeout, char *argv[]); bool plymouth_running(void); diff --git a/src/basic/virt.c b/src/basic/virt.c index dace1f4328..d8d57381ad 100644 --- a/src/basic/virt.c +++ b/src/basic/virt.c @@ -33,6 +33,7 @@ #include "string-table.h" #include "string-util.h" #include "virt.h" +#include "env-util.h" static int detect_vm_cpuid(void) { @@ -49,6 +50,8 @@ static int detect_vm_cpuid(void) { { "VMwareVMware", VIRTUALIZATION_VMWARE }, /* http://msdn.microsoft.com/en-us/library/ff542428.aspx */ { "Microsoft Hv", VIRTUALIZATION_MICROSOFT }, + /* https://wiki.freebsd.org/bhyve */ + { "bhyve bhyve ", VIRTUALIZATION_BHYVE }, }; uint32_t eax, ecx; @@ -178,6 +181,8 @@ static int detect_vm_dmi(void) { { "Xen", VIRTUALIZATION_XEN }, { "Bochs", VIRTUALIZATION_BOCHS }, { "Parallels", VIRTUALIZATION_PARALLELS }, + /* https://wiki.freebsd.org/bhyve */ + { "BHYVE", VIRTUALIZATION_BHYVE }, }; unsigned i; int r; @@ -480,9 +485,82 @@ int detect_virtualization(void) { return r; } +static int userns_has_mapping(const char *name) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *buf = NULL; + size_t n_allocated = 0; + ssize_t n; + uint32_t a, b, c; + int r; + + f = fopen(name, "re"); + if (!f) { + log_debug_errno(errno, "Failed to open %s: %m", name); + return errno == ENOENT ? false : -errno; + } + + n = getline(&buf, &n_allocated, f); + if (n < 0) { + if (feof(f)) { + log_debug("%s is empty, we're in an uninitialized user namespace", name); + return true; + } + + return log_debug_errno(errno, "Failed to read %s: %m", name); + } + + r = sscanf(buf, "%"PRIu32" %"PRIu32" %"PRIu32, &a, &b, &c); + if (r < 3) + return log_debug_errno(errno, "Failed to parse %s: %m", name); + + if (a == 0 && b == 0 && c == UINT32_MAX) { + /* The kernel calls mappings_overlap() and does not allow overlaps */ + log_debug("%s has a full 1:1 mapping", name); + return false; + } + + /* Anything else implies that we are in a user namespace */ + log_debug("Mapping found in %s, we're in a user namespace", name); + return true; +} + +int running_in_userns(void) { + _cleanup_free_ char *line = NULL; + int r; + + r = userns_has_mapping("/proc/self/uid_map"); + if (r != 0) + return r; + + r = userns_has_mapping("/proc/self/gid_map"); + if (r != 0) + return r; + + /* "setgroups" file was added in kernel v3.18-rc6-15-g9cc46516dd. It is also + * possible to compile a kernel without CONFIG_USER_NS, in which case "setgroups" + * also does not exist. We cannot distinguish those two cases, so assume that + * we're running on a stripped-down recent kernel, rather than on an old one, + * and if the file is not found, return false. + */ + r = read_one_line_file("/proc/self/setgroups", &line); + if (r < 0) { + log_debug_errno(r, "/proc/self/setgroups: %m"); + return r == -ENOENT ? false : r; + } + + truncate_nl(line); + r = streq(line, "deny"); + /* See user_namespaces(7) for a description of this "setgroups" contents. */ + log_debug("/proc/self/setgroups contains \"%s\", %s user namespace", line, r ? "in" : "not in"); + return r; +} + int running_in_chroot(void) { int ret; + if (getenv_bool("SYSTEMD_IGNORE_CHROOT") > 0) + return 0; + ret = files_same("/proc/1/root", "/"); if (ret < 0) return ret; @@ -502,6 +580,7 @@ static const char *const virtualization_table[_VIRTUALIZATION_MAX] = { [VIRTUALIZATION_MICROSOFT] = "microsoft", [VIRTUALIZATION_ZVM] = "zvm", [VIRTUALIZATION_PARALLELS] = "parallels", + [VIRTUALIZATION_BHYVE] = "bhyve", [VIRTUALIZATION_VM_OTHER] = "vm-other", [VIRTUALIZATION_SYSTEMD_NSPAWN] = "systemd-nspawn", diff --git a/src/basic/virt.h b/src/basic/virt.h index a538f07f6b..7d15169112 100644 --- a/src/basic/virt.h +++ b/src/basic/virt.h @@ -37,6 +37,7 @@ enum { VIRTUALIZATION_MICROSOFT, VIRTUALIZATION_ZVM, VIRTUALIZATION_PARALLELS, + VIRTUALIZATION_BHYVE, VIRTUALIZATION_VM_OTHER, VIRTUALIZATION_VM_LAST = VIRTUALIZATION_VM_OTHER, @@ -66,6 +67,7 @@ int detect_vm(void); int detect_container(void); int detect_virtualization(void); +int running_in_userns(void); int running_in_chroot(void); const char *virtualization_to_string(int v) _const_; diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index 056a0790bd..d53f8b2a6f 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -26,6 +26,7 @@ #include <ftw.h> #include <getopt.h> #include <limits.h> +#include <linux/magic.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> @@ -42,22 +43,53 @@ #include "fd-util.h" #include "fileio.h" #include "locale-util.h" +#include "parse-util.h" #include "rm-rf.h" #include "string-util.h" +#include "strv.h" +#include "umask-util.h" #include "util.h" +#include "verbs.h" +#include "virt.h" +#include "stat-util.h" + +static char *arg_path = NULL; +static bool arg_touch_variables = true; + +static int verify_esp( + bool searching, + const char *p, + uint32_t *ret_part, + uint64_t *ret_pstart, + uint64_t *ret_psize, + sd_id128_t *ret_uuid) { -static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t *psize, sd_id128_t *uuid) { - struct statfs sfs; - struct stat st, st2; - _cleanup_free_ char *t = NULL; _cleanup_blkid_free_probe_ blkid_probe b = NULL; - int r; + _cleanup_free_ char *t = NULL; + uint64_t pstart = 0, psize = 0; + struct stat st, st2; const char *v, *t2; + struct statfs sfs; + sd_id128_t uuid = SD_ID128_NULL; + uint32_t part = 0; + int r; + + assert(p); + + if (statfs(p, &sfs) < 0) { + + /* If we are searching for the mount point, don't generate a log message if we can't find the path */ + if (errno == ENOENT && searching) + return -ENOENT; - if (statfs(p, &sfs) < 0) return log_error_errno(errno, "Failed to check file system type of \"%s\": %m", p); + } + + if (!F_TYPE_EQUAL(sfs.f_type, MSDOS_SUPER_MAGIC)) { + + if (searching) + return -EADDRNOTAVAIL; - if (sfs.f_type != 0x4d44) { log_error("File system \"%s\" is not a FAT EFI System Partition (ESP) file system.", p); return -ENODEV; } @@ -80,6 +112,11 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t return -ENODEV; } + /* In a container we don't have access to block devices, skip this part of the verification, we trust the + * container manager set everything up correctly on its own. */ + if (detect_container() > 0) + goto finish; + r = asprintf(&t, "/dev/block/%u:%u", major(st.st_dev), minor(st.st_dev)); if (r < 0) return log_oom(); @@ -117,7 +154,6 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t r = errno ? -errno : -EIO; return log_error_errno(r, "Failed to probe file system type \"%s\": %m", p); } - if (!streq(v, "vfat")) { log_error("File system \"%s\" is not FAT.", p); return -ENODEV; @@ -129,7 +165,6 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t r = errno ? -errno : -EIO; return log_error_errno(r, "Failed to probe partition scheme \"%s\": %m", p); } - if (!streq(v, "gpt")) { log_error("File system \"%s\" is not on a GPT partition table.", p); return -ENODEV; @@ -141,7 +176,6 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t r = errno ? -errno : -EIO; return log_error_errno(r, "Failed to probe partition type UUID \"%s\": %m", p); } - if (!streq(v, "c12a7328-f81f-11d2-ba4b-00a0c93ec93b")) { log_error("File system \"%s\" has wrong type for an EFI System Partition (ESP).", p); return -ENODEV; @@ -153,8 +187,7 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t r = errno ? -errno : -EIO; return log_error_errno(r, "Failed to probe partition entry UUID \"%s\": %m", p); } - - r = sd_id128_from_string(v, uuid); + r = sd_id128_from_string(v, &uuid); if (r < 0) { log_error("Partition \"%s\" has invalid UUID \"%s\".", p, v); return -EIO; @@ -166,7 +199,9 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t r = errno ? -errno : -EIO; return log_error_errno(r, "Failed to probe partition number \"%s\": m", p); } - *part = strtoul(v, NULL, 10); + r = safe_atou32(v, &part); + if (r < 0) + return log_error_errno(r, "Failed to parse PART_ENTRY_NUMBER field."); errno = 0; r = blkid_probe_lookup_value(b, "PART_ENTRY_OFFSET", &v, NULL); @@ -174,7 +209,9 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t r = errno ? -errno : -EIO; return log_error_errno(r, "Failed to probe partition offset \"%s\": %m", p); } - *pstart = strtoul(v, NULL, 10); + r = safe_atou64(v, &pstart); + if (r < 0) + return log_error_errno(r, "Failed to parse PART_ENTRY_OFFSET field."); errno = 0; r = blkid_probe_lookup_value(b, "PART_ENTRY_SIZE", &v, NULL); @@ -182,11 +219,50 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t r = errno ? -errno : -EIO; return log_error_errno(r, "Failed to probe partition size \"%s\": %m", p); } - *psize = strtoul(v, NULL, 10); + r = safe_atou64(v, &psize); + if (r < 0) + return log_error_errno(r, "Failed to parse PART_ENTRY_SIZE field."); + +finish: + if (ret_part) + *ret_part = part; + if (ret_pstart) + *ret_pstart = pstart; + if (ret_psize) + *ret_psize = psize; + if (ret_uuid) + *ret_uuid = uuid; return 0; } +static int find_esp(uint32_t *part, uint64_t *pstart, uint64_t *psize, sd_id128_t *uuid) { + const char *path; + int r; + + if (arg_path) + return verify_esp(false, arg_path, part, pstart, psize, uuid); + + FOREACH_STRING(path, "/efi", "/boot", "/boot/efi") { + + r = verify_esp(true, path, part, pstart, psize, uuid); + if (IN_SET(r, -ENOENT, -EADDRNOTAVAIL)) /* This one is not it */ + continue; + if (r < 0) + return r; + + arg_path = strdup(path); + if (!arg_path) + return log_oom(); + + log_info("Using EFI System Parition at %s.", path); + return 0; + } + + log_error("Couldn't find EFI system partition. It is recommended to mount it to /boot. Alternatively, use --path= to specify path to mount point."); + return -ENOENT; +} + /* search for "#### LoaderInfo: systemd-boot 218 ####" string inside the binary */ static int get_file_version(int fd, char **v) { struct stat st; @@ -199,14 +275,16 @@ static int get_file_version(int fd, char **v) { assert(v); if (fstat(fd, &st) < 0) - return -errno; + return log_error_errno(errno, "Failed to stat EFI binary: %m"); - if (st.st_size < 27) + if (st.st_size < 27) { + *v = NULL; return 0; + } buf = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); if (buf == MAP_FAILED) - return -errno; + return log_error_errno(errno, "Failed to memory map EFI binary: %m"); s = memmem(buf, st.st_size - 8, "#### LoaderInfo: ", 17); if (!s) @@ -228,7 +306,7 @@ static int get_file_version(int fd, char **v) { r = 1; finish: - munmap(buf, st.st_size); + (void) munmap(buf, st.st_size); *v = x; return r; } @@ -338,9 +416,10 @@ static int status_variables(void) { n_options = efi_get_boot_options(&options); if (n_options == -ENOENT) - return log_error_errno(ENOENT, "Failed to access EFI variables, efivarfs" + return log_error_errno(n_options, + "Failed to access EFI variables, efivarfs" " needs to be available at /sys/firmware/efi/efivars/."); - else if (n_options < 0) + if (n_options < 0) return log_error_errno(n_options, "Failed to read EFI boot entries: %m"); n_order = efi_get_boot_order(&order); @@ -360,10 +439,11 @@ static int status_variables(void) { for (j = 0; j < n_order; j++) if (options[i] == order[j]) - goto next; + goto next_option; print_efi_option(options[i], false); - next: + + next_option: continue; } @@ -523,15 +603,6 @@ error: return r; } -static char* strupper(char *s) { - char *p; - - for (p = s; *p; p++) - *p = toupper(*p); - - return s; -} - static int mkdir_one(const char *prefix, const char *suffix) { char *p; @@ -550,15 +621,16 @@ static const char *efi_subdirs[] = { "EFI/systemd", "EFI/BOOT", "loader", - "loader/entries" + "loader/entries", + NULL }; static int create_dirs(const char *esp_path) { + const char **i; int r; - unsigned i; - for (i = 0; i < ELEMENTSOF(efi_subdirs); i++) { - r = mkdir_one(esp_path, efi_subdirs[i]); + STRV_FOREACH(i, efi_subdirs) { + r = mkdir_one(esp_path, *i); if (r < 0) return r; } @@ -580,7 +652,7 @@ static int copy_one_file(const char *esp_path, const char *name, bool force) { /* Create the EFI default boot loader name (specified for removable devices) */ v = strjoina(esp_path, "/EFI/BOOT/BOOT", name + strlen("systemd-boot")); - strupper(strrchr(v, '/') + 1); + ascii_strupper(strrchr(v, '/') + 1); k = copy_file(p, v, force); if (k < 0 && r == 0) @@ -751,8 +823,8 @@ static int install_variables(const char *esp_path, if (access(p, F_OK) < 0) { if (errno == ENOENT) return 0; - else - return log_error_errno(errno, "Cannot access \"%s\": %m", p); + + return log_error_errno(errno, "Cannot access \"%s\": %m", p); } r = find_slot(uuid, path, &slot); @@ -762,7 +834,7 @@ static int install_variables(const char *esp_path, "Failed to access EFI variables. Is the \"efivarfs\" filesystem mounted?" : "Failed to determine current boot order: %m"); - if (first || r == false) { + if (first || r == 0) { r = efi_add_boot_option(slot, "Systemd Boot Manager", part, pstart, psize, uuid, path); @@ -846,7 +918,7 @@ static int remove_binaries(const char *esp_path) { if (q < 0 && r == 0) r = q; - for (i = ELEMENTSOF(efi_subdirs); i > 0; i--) { + for (i = ELEMENTSOF(efi_subdirs)-1; i > 0; i--) { q = rmdir_one(esp_path, efi_subdirs[i-1]); if (q < 0 && r == 0) r = q; @@ -872,46 +944,39 @@ static int remove_variables(sd_id128_t uuid, const char *path, bool in_order) { if (in_order) return remove_from_order(slot); - else - return 0; + + return 0; } static int install_loader_config(const char *esp_path) { - char *p; - char line[64]; - char *machine = NULL; - _cleanup_fclose_ FILE *f = NULL, *g = NULL; - f = fopen("/etc/machine-id", "re"); - if (!f) - return errno == ENOENT ? 0 : -errno; + _cleanup_fclose_ FILE *f = NULL; + char machine_string[SD_ID128_STRING_MAX]; + sd_id128_t machine_id; + const char *p; + int r; - if (fgets(line, sizeof(line), f) != NULL) { - char *s; + r = sd_id128_get_machine(&machine_id); + if (r < 0) + return log_error_errno(r, "Failed to get machine did: %m"); - s = strchr(line, '\n'); - if (s) - s[0] = '\0'; - if (strlen(line) == 32) - machine = line; - } + p = strjoina(esp_path, "/loader/loader.conf"); + f = fopen(p, "wxe"); + if (!f) + return log_error_errno(errno, "Failed to open loader.conf for writing: %m"); - if (!machine) - return -ESRCH; + fprintf(f, "#timeout 3\n"); + fprintf(f, "default %s-*\n", sd_id128_to_string(machine_id, machine_string)); - p = strjoina(esp_path, "/loader/loader.conf"); - g = fopen(p, "wxe"); - if (g) { - fprintf(g, "#timeout 3\n"); - fprintf(g, "default %s-*\n", machine); - if (ferror(g)) - return log_error_errno(EIO, "Failed to write \"%s\": %m", p); - } + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write \"%s\": %m", p); return 0; } -static int help(void) { +static int help(int argc, char *argv[], void *userdata) { + printf("%s [COMMAND] [OPTIONS...]\n" "\n" "Install, update or remove the systemd-boot EFI boot manager.\n\n" @@ -930,9 +995,6 @@ static int help(void) { return 0; } -static const char *arg_path = "/boot"; -static bool arg_touch_variables = true; - static int parse_argv(int argc, char *argv[]) { enum { ARG_PATH = 0x100, @@ -948,7 +1010,7 @@ static int parse_argv(int argc, char *argv[]) { { NULL, 0, NULL, 0 } }; - int c; + int c, r; assert(argc >= 0); assert(argv); @@ -957,14 +1019,16 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { case 'h': - help(); + help(0, NULL, NULL); return 0; case ARG_VERSION: return version(); case ARG_PATH: - arg_path = optarg; + r = free_and_strdup(&arg_path, optarg); + if (r < 0) + return log_oom(); break; case ARG_NO_VARIABLES: @@ -989,149 +1053,170 @@ static void read_loader_efi_var(const char *name, char **var) { log_warning_errno(r, "Failed to read EFI variable %s: %m", name); } -static int bootctl_main(int argc, char*argv[]) { - enum action { - ACTION_STATUS, - ACTION_INSTALL, - ACTION_UPDATE, - ACTION_REMOVE - } arg_action = ACTION_STATUS; - static const struct { - const char* verb; - enum action action; - } verbs[] = { - { "status", ACTION_STATUS }, - { "install", ACTION_INSTALL }, - { "update", ACTION_UPDATE }, - { "remove", ACTION_REMOVE }, - }; +static int must_be_root(void) { - sd_id128_t uuid = {}; - uint32_t part = 0; - uint64_t pstart = 0, psize = 0; - int r, q; + if (geteuid() == 0) + return 0; - if (argv[optind]) { - unsigned i; + log_error("Need to be root."); + return -EPERM; +} - for (i = 0; i < ELEMENTSOF(verbs); i++) { - if (!streq(argv[optind], verbs[i].verb)) - continue; - arg_action = verbs[i].action; - break; - } - if (i >= ELEMENTSOF(verbs)) { - log_error("Unknown operation \"%s\"", argv[optind]); - return -EINVAL; - } - } +static int verb_status(int argc, char *argv[], void *userdata) { - if (geteuid() != 0) - return log_error_errno(EPERM, "Need to be root."); + sd_id128_t uuid = SD_ID128_NULL; + int r; - r = verify_esp(arg_path, &part, &pstart, &psize, &uuid); - if (r == -ENODEV && !arg_path) - log_notice("You might want to use --path= to indicate the path to your ESP, in case it is not mounted on /boot."); + r = must_be_root(); if (r < 0) return r; - switch (arg_action) { - case ACTION_STATUS: { - _cleanup_free_ char *fw_type = NULL; - _cleanup_free_ char *fw_info = NULL; - _cleanup_free_ char *loader = NULL; - _cleanup_free_ char *loader_path = NULL; - sd_id128_t loader_part_uuid = {}; - - if (is_efi_boot()) { - read_loader_efi_var("LoaderFirmwareType", &fw_type); - read_loader_efi_var("LoaderFirmwareInfo", &fw_info); - read_loader_efi_var("LoaderInfo", &loader); - read_loader_efi_var("LoaderImageIdentifier", &loader_path); - if (loader_path) - efi_tilt_backslashes(loader_path); - r = efi_loader_get_device_part_uuid(&loader_part_uuid); - if (r < 0 && r == -ENOENT) - log_warning_errno(r, "Failed to read EFI variable LoaderDevicePartUUID: %m"); - - printf("System:\n"); - printf(" Firmware: %s (%s)\n", strna(fw_type), strna(fw_info)); - - r = is_efi_secure_boot(); - if (r < 0) - log_warning_errno(r, "Failed to query secure boot status: %m"); - else - printf(" Secure Boot: %s\n", r ? "enabled" : "disabled"); + r = find_esp(NULL, NULL, NULL, &uuid); + if (r < 0) + return r; - r = is_efi_secure_boot_setup_mode(); - if (r < 0) - log_warning_errno(r, "Failed to query secure boot mode: %m"); - else - printf(" Setup Mode: %s\n", r ? "setup" : "user"); - printf("\n"); - - printf("Loader:\n"); - printf(" Product: %s\n", strna(loader)); - if (!sd_id128_is_null(loader_part_uuid)) - printf(" Partition: /dev/disk/by-partuuid/%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", - SD_ID128_FORMAT_VAL(loader_part_uuid)); - else - printf(" Partition: n/a\n"); - printf(" File: %s%s\n", special_glyph(TREE_RIGHT), strna(loader_path)); - printf("\n"); - } else - printf("System:\n Not booted with EFI\n"); - - r = status_binaries(arg_path, uuid); + if (is_efi_boot()) { + _cleanup_free_ char *fw_type = NULL, *fw_info = NULL, *loader = NULL, *loader_path = NULL; + sd_id128_t loader_part_uuid = SD_ID128_NULL; + + read_loader_efi_var("LoaderFirmwareType", &fw_type); + read_loader_efi_var("LoaderFirmwareInfo", &fw_info); + read_loader_efi_var("LoaderInfo", &loader); + read_loader_efi_var("LoaderImageIdentifier", &loader_path); + + if (loader_path) + efi_tilt_backslashes(loader_path); + + r = efi_loader_get_device_part_uuid(&loader_part_uuid); + if (r < 0 && r != -ENOENT) + log_warning_errno(r, "Failed to read EFI variable LoaderDevicePartUUID: %m"); + + printf("System:\n"); + printf(" Firmware: %s (%s)\n", strna(fw_type), strna(fw_info)); + + r = is_efi_secure_boot(); if (r < 0) - return r; + log_warning_errno(r, "Failed to query secure boot status: %m"); + else + printf(" Secure Boot: %sd\n", enable_disable(r)); - if (arg_touch_variables) - r = status_variables(); - break; - } + r = is_efi_secure_boot_setup_mode(); + if (r < 0) + log_warning_errno(r, "Failed to query secure boot mode: %m"); + else + printf(" Setup Mode: %s\n", r ? "setup" : "user"); + printf("\n"); + + printf("Loader:\n"); + printf(" Product: %s\n", strna(loader)); + if (!sd_id128_is_null(loader_part_uuid)) + printf(" Partition: /dev/disk/by-partuuid/%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", + SD_ID128_FORMAT_VAL(loader_part_uuid)); + else + printf(" Partition: n/a\n"); + printf(" File: %s%s\n", special_glyph(TREE_RIGHT), strna(loader_path)); + printf("\n"); + } else + printf("System:\n Not booted with EFI\n"); - case ACTION_INSTALL: - case ACTION_UPDATE: - umask(0002); + r = status_binaries(arg_path, uuid); + if (r < 0) + return r; + + if (arg_touch_variables) + r = status_variables(); - r = install_binaries(arg_path, arg_action == ACTION_INSTALL); + return r; +} + +static int verb_install(int argc, char *argv[], void *userdata) { + + sd_id128_t uuid = SD_ID128_NULL; + uint64_t pstart = 0, psize = 0; + uint32_t part = 0; + bool install; + int r; + + r = must_be_root(); + if (r < 0) + return r; + + r = find_esp(&part, &pstart, &psize, &uuid); + if (r < 0) + return r; + + install = streq(argv[0], "install"); + + RUN_WITH_UMASK(0002) { + r = install_binaries(arg_path, install); if (r < 0) return r; - if (arg_action == ACTION_INSTALL) { + if (install) { r = install_loader_config(arg_path); if (r < 0) return r; } + } - if (arg_touch_variables) - r = install_variables(arg_path, - part, pstart, psize, uuid, - "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", - arg_action == ACTION_INSTALL); - break; + if (arg_touch_variables) + r = install_variables(arg_path, + part, pstart, psize, uuid, + "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", + install); - case ACTION_REMOVE: - r = remove_binaries(arg_path); + return r; +} - if (arg_touch_variables) { - q = remove_variables(uuid, "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", true); - if (q < 0 && r == 0) - r = q; - } - break; +static int verb_remove(int argc, char *argv[], void *userdata) { + sd_id128_t uuid = SD_ID128_NULL; + int r; + + r = must_be_root(); + if (r < 0) + return r; + + r = find_esp(NULL, NULL, NULL, &uuid); + if (r < 0) + return r; + + r = remove_binaries(arg_path); + + if (arg_touch_variables) { + int q; + + q = remove_variables(uuid, "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", true); + if (q < 0 && r == 0) + r = q; } return r; } +static int bootctl_main(int argc, char *argv[]) { + + static const Verb verbs[] = { + { "help", VERB_ANY, VERB_ANY, 0, help }, + { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, + { "install", VERB_ANY, 1, 0, verb_install }, + { "update", VERB_ANY, 1, 0, verb_install }, + { "remove", VERB_ANY, 1, 0, verb_remove }, + {} + }; + + return dispatch_verb(argc, argv, verbs, NULL); +} + int main(int argc, char *argv[]) { int r; log_parse_environment(); log_open(); + /* If we run in a container, automatically turn of EFI file system access */ + if (detect_container() > 0) + arg_touch_variables = false; + r = parse_argv(argc, argv); if (r <= 0) goto finish; @@ -1139,5 +1224,6 @@ int main(int argc, char *argv[]) { r = bootctl_main(argc, argv); finish: + free(arg_path); return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; } diff --git a/src/boot/efi/measure.c b/src/boot/efi/measure.c index 7c016387c1..4ac11a9bb0 100644 --- a/src/boot/efi/measure.c +++ b/src/boot/efi/measure.c @@ -209,12 +209,35 @@ static EFI_STATUS tpm1_measure_to_pcr_and_event_log(const EFI_TCG *tcg, UINT32 p return EFI_SUCCESS; } +/* + * According to TCG EFI Protocol Specification for TPM 2.0 family, + * all events generated after the invocation of EFI_TCG2_GET_EVENT_LOG + * shall be stored in an instance of an EFI_CONFIGURATION_TABLE aka + * EFI TCG 2.0 final events table. Hence, it is necessary to trigger the + * internal switch through calling get_event_log() in order to allow + * to retrieve the logs from OS runtime. + */ +static EFI_STATUS trigger_tcg2_final_events_table(const EFI_TCG2 *tcg) +{ + return uefi_call_wrapper(tcg->GetEventLog, 5, tcg, + EFI_TCG2_EVENT_LOG_FORMAT_TCG_2, NULL, + NULL, NULL); +} static EFI_STATUS tpm2_measure_to_pcr_and_event_log(const EFI_TCG2 *tcg, UINT32 pcrindex, const EFI_PHYSICAL_ADDRESS buffer, UINT64 buffer_size, const CHAR16 *description) { EFI_STATUS status; EFI_TCG2_EVENT *tcg_event; UINTN desc_len; + static BOOLEAN triggered = FALSE; + + if (triggered == FALSE) { + status = trigger_tcg2_final_events_table(tcg); + if (EFI_ERROR(status)) + return status; + + triggered = TRUE; + } desc_len = StrLen(description) * sizeof(CHAR16); diff --git a/src/cgls/cgls.c b/src/cgls/cgls.c index dcb5912b83..adf488e8e1 100644 --- a/src/cgls/cgls.c +++ b/src/cgls/cgls.c @@ -166,7 +166,7 @@ static int get_cgroup_root(char **ret) { static void show_cg_info(const char *controller, const char *path) { - if (cg_unified() <= 0 && controller && !streq(controller, SYSTEMD_CGROUP_CONTROLLER)) + if (cg_all_unified() <= 0 && controller && !streq(controller, SYSTEMD_CGROUP_CONTROLLER)) printf("Controller %s; ", controller); printf("Control group %s:\n", isempty(path) ? "/" : path); diff --git a/src/cgtop/cgtop.c b/src/cgtop/cgtop.c index c67b328b38..aba17c9829 100644 --- a/src/cgtop/cgtop.c +++ b/src/cgtop/cgtop.c @@ -208,24 +208,47 @@ static int process( if (g->n_tasks > 0) g->n_tasks_valid = true; - } else if (streq(controller, "cpuacct") && cg_unified() <= 0) { + } else if (streq(controller, "cpu") || streq(controller, "cpuacct")) { _cleanup_free_ char *p = NULL, *v = NULL; uint64_t new_usage; nsec_t timestamp; - r = cg_get_path(controller, path, "cpuacct.usage", &p); - if (r < 0) - return r; + if (cg_all_unified() > 0) { + const char *keys[] = { "usage_usec", NULL }; + _cleanup_free_ char *val = NULL; - r = read_one_line_file(p, &v); - if (r == -ENOENT) - return 0; - if (r < 0) - return r; + if (!streq(controller, "cpu")) + return 0; - r = safe_atou64(v, &new_usage); - if (r < 0) - return r; + r = cg_get_keyed_attribute("cpu", path, "cpu.stat", keys, &val); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + + r = safe_atou64(val, &new_usage); + if (r < 0) + return r; + + new_usage *= NSEC_PER_USEC; + } else { + if (!streq(controller, "cpuacct")) + return 0; + + r = cg_get_path(controller, path, "cpuacct.usage", &p); + if (r < 0) + return r; + + r = read_one_line_file(p, &v); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + + r = safe_atou64(v, &new_usage); + if (r < 0) + return r; + } timestamp = now_nsec(CLOCK_MONOTONIC); @@ -250,7 +273,7 @@ static int process( } else if (streq(controller, "memory")) { _cleanup_free_ char *p = NULL, *v = NULL; - if (cg_unified() <= 0) + if (cg_all_unified() <= 0) r = cg_get_path(controller, path, "memory.usage_in_bytes", &p); else r = cg_get_path(controller, path, "memory.current", &p); @@ -270,11 +293,11 @@ static int process( if (g->memory > 0) g->memory_valid = true; - } else if ((streq(controller, "io") && cg_unified() > 0) || - (streq(controller, "blkio") && cg_unified() <= 0)) { + } else if ((streq(controller, "io") && cg_all_unified() > 0) || + (streq(controller, "blkio") && cg_all_unified() <= 0)) { _cleanup_fclose_ FILE *f = NULL; _cleanup_free_ char *p = NULL; - bool unified = cg_unified() > 0; + bool unified = cg_all_unified() > 0; uint64_t wr = 0, rd = 0; nsec_t timestamp; @@ -449,6 +472,9 @@ static int refresh(const char *root, Hashmap *a, Hashmap *b, unsigned iteration) r = refresh_one(SYSTEMD_CGROUP_CONTROLLER, root, a, b, iteration, 0, NULL); if (r < 0) return r; + r = refresh_one("cpu", root, a, b, iteration, 0, NULL); + if (r < 0) + return r; r = refresh_one("cpuacct", root, a, b, iteration, 0, NULL); if (r < 0) return r; diff --git a/src/core/automount.c b/src/core/automount.c index 4e9891569c..7d7a0a6e46 100644 --- a/src/core/automount.c +++ b/src/core/automount.c @@ -271,6 +271,11 @@ static int automount_coldplug(Unit *u) { return r; (void) sd_event_source_set_description(a->pipe_event_source, "automount-io"); + if (a->deserialized_state == AUTOMOUNT_RUNNING) { + r = automount_start_expire(a); + if (r < 0) + log_unit_warning_errno(UNIT(a), r, "Failed to start expiration timer, ignoring: %m"); + } } automount_set_state(a, a->deserialized_state); @@ -301,7 +306,7 @@ static void automount_dump(Unit *u, FILE *f, const char *prefix) { static void automount_enter_dead(Automount *a, AutomountResult f) { assert(a); - if (f != AUTOMOUNT_SUCCESS) + if (a->result == AUTOMOUNT_SUCCESS) a->result = f; automount_set_state(a, a->result != AUTOMOUNT_SUCCESS ? AUTOMOUNT_FAILED : AUTOMOUNT_DEAD); @@ -795,6 +800,10 @@ static int automount_start(Unit *u) { return r; } + r = unit_acquire_invocation_id(u); + if (r < 0) + return r; + a->result = AUTOMOUNT_SUCCESS; automount_enter_waiting(a); return 1; @@ -1105,6 +1114,9 @@ const UnitVTable automount_vtable = { .reset_failed = automount_reset_failed, .bus_vtable = bus_automount_vtable, + .bus_set_property = bus_automount_set_property, + + .can_transient = true, .shutdown = automount_shutdown, .supported = automount_supported, diff --git a/src/core/busname.c b/src/core/busname.c index 730be2ee14..b96ec09e67 100644 --- a/src/core/busname.c +++ b/src/core/busname.c @@ -442,7 +442,7 @@ fail: static void busname_enter_dead(BusName *n, BusNameResult f) { assert(n); - if (f != BUSNAME_SUCCESS) + if (n->result == BUSNAME_SUCCESS) n->result = f; busname_set_state(n, n->result != BUSNAME_SUCCESS ? BUSNAME_FAILED : BUSNAME_DEAD); @@ -454,7 +454,7 @@ static void busname_enter_signal(BusName *n, BusNameState state, BusNameResult f assert(n); - if (f != BUSNAME_SUCCESS) + if (n->result == BUSNAME_SUCCESS) n->result = f; kill_context_init(&kill_context); @@ -639,6 +639,10 @@ static int busname_start(Unit *u) { return r; } + r = unit_acquire_invocation_id(u); + if (r < 0) + return r; + n->result = BUSNAME_SUCCESS; busname_enter_making(n); @@ -868,7 +872,7 @@ static void busname_sigchld_event(Unit *u, pid_t pid, int code, int status) { n->control_pid = 0; - if (is_clean_exit(code, status, NULL)) + if (is_clean_exit(code, status, EXIT_CLEAN_COMMAND, NULL)) f = BUSNAME_SUCCESS; else if (code == CLD_EXITED) f = BUSNAME_FAILURE_EXIT_CODE; @@ -882,7 +886,7 @@ static void busname_sigchld_event(Unit *u, pid_t pid, int code, int status) { log_unit_full(u, f == BUSNAME_SUCCESS ? LOG_DEBUG : LOG_NOTICE, 0, "Control process exited, code=%s status=%i", sigchld_code_to_string(code), status); - if (f != BUSNAME_SUCCESS) + if (n->result == BUSNAME_SUCCESS) n->result = f; switch (n->state) { diff --git a/src/core/cgroup.c b/src/core/cgroup.c index c19e43f571..23a92f9651 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -57,12 +57,16 @@ void cgroup_context_init(CGroupContext *c) { /* Initialize everything to the kernel defaults, assuming the * structure is preinitialized to 0 */ + c->cpu_weight = CGROUP_WEIGHT_INVALID; + c->startup_cpu_weight = CGROUP_WEIGHT_INVALID; + c->cpu_quota_per_sec_usec = USEC_INFINITY; + c->cpu_shares = CGROUP_CPU_SHARES_INVALID; c->startup_cpu_shares = CGROUP_CPU_SHARES_INVALID; - c->cpu_quota_per_sec_usec = USEC_INFINITY; c->memory_high = CGROUP_LIMIT_MAX; c->memory_max = CGROUP_LIMIT_MAX; + c->memory_swap_max = CGROUP_LIMIT_MAX; c->memory_limit = CGROUP_LIMIT_MAX; @@ -158,6 +162,8 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) { "%sBlockIOAccounting=%s\n" "%sMemoryAccounting=%s\n" "%sTasksAccounting=%s\n" + "%sCPUWeight=%" PRIu64 "\n" + "%sStartupCPUWeight=%" PRIu64 "\n" "%sCPUShares=%" PRIu64 "\n" "%sStartupCPUShares=%" PRIu64 "\n" "%sCPUQuotaPerSecSec=%s\n" @@ -168,6 +174,7 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) { "%sMemoryLow=%" PRIu64 "\n" "%sMemoryHigh=%" PRIu64 "\n" "%sMemoryMax=%" PRIu64 "\n" + "%sMemorySwapMax=%" PRIu64 "\n" "%sMemoryLimit=%" PRIu64 "\n" "%sTasksMax=%" PRIu64 "\n" "%sDevicePolicy=%s\n" @@ -177,6 +184,8 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) { prefix, yes_no(c->blockio_accounting), prefix, yes_no(c->memory_accounting), prefix, yes_no(c->tasks_accounting), + prefix, c->cpu_weight, + prefix, c->startup_cpu_weight, prefix, c->cpu_shares, prefix, c->startup_cpu_shares, prefix, format_timespan(u, sizeof(u), c->cpu_quota_per_sec_usec, 1), @@ -187,6 +196,7 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) { prefix, c->memory_low, prefix, c->memory_high, prefix, c->memory_max, + prefix, c->memory_swap_max, prefix, c->memory_limit, prefix, c->tasks_max, prefix, cgroup_device_policy_to_string(c->device_policy), @@ -382,6 +392,95 @@ fail: return -errno; } +static bool cgroup_context_has_cpu_weight(CGroupContext *c) { + return c->cpu_weight != CGROUP_WEIGHT_INVALID || + c->startup_cpu_weight != CGROUP_WEIGHT_INVALID; +} + +static bool cgroup_context_has_cpu_shares(CGroupContext *c) { + return c->cpu_shares != CGROUP_CPU_SHARES_INVALID || + c->startup_cpu_shares != CGROUP_CPU_SHARES_INVALID; +} + +static uint64_t cgroup_context_cpu_weight(CGroupContext *c, ManagerState state) { + if (IN_SET(state, MANAGER_STARTING, MANAGER_INITIALIZING) && + c->startup_cpu_weight != CGROUP_WEIGHT_INVALID) + return c->startup_cpu_weight; + else if (c->cpu_weight != CGROUP_WEIGHT_INVALID) + return c->cpu_weight; + else + return CGROUP_WEIGHT_DEFAULT; +} + +static uint64_t cgroup_context_cpu_shares(CGroupContext *c, ManagerState state) { + if (IN_SET(state, MANAGER_STARTING, MANAGER_INITIALIZING) && + c->startup_cpu_shares != CGROUP_CPU_SHARES_INVALID) + return c->startup_cpu_shares; + else if (c->cpu_shares != CGROUP_CPU_SHARES_INVALID) + return c->cpu_shares; + else + return CGROUP_CPU_SHARES_DEFAULT; +} + +static void cgroup_apply_unified_cpu_config(Unit *u, uint64_t weight, uint64_t quota) { + char buf[MAX(DECIMAL_STR_MAX(uint64_t) + 1, (DECIMAL_STR_MAX(usec_t) + 1) * 2)]; + int r; + + xsprintf(buf, "%" PRIu64 "\n", weight); + r = cg_set_attribute("cpu", u->cgroup_path, "cpu.weight", buf); + if (r < 0) + log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set cpu.weight: %m"); + + if (quota != USEC_INFINITY) + xsprintf(buf, USEC_FMT " " USEC_FMT "\n", + quota * CGROUP_CPU_QUOTA_PERIOD_USEC / USEC_PER_SEC, CGROUP_CPU_QUOTA_PERIOD_USEC); + else + xsprintf(buf, "max " USEC_FMT "\n", CGROUP_CPU_QUOTA_PERIOD_USEC); + + r = cg_set_attribute("cpu", u->cgroup_path, "cpu.max", buf); + + if (r < 0) + log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set cpu.max: %m"); +} + +static void cgroup_apply_legacy_cpu_config(Unit *u, uint64_t shares, uint64_t quota) { + char buf[MAX(DECIMAL_STR_MAX(uint64_t), DECIMAL_STR_MAX(usec_t)) + 1]; + int r; + + xsprintf(buf, "%" PRIu64 "\n", shares); + r = cg_set_attribute("cpu", u->cgroup_path, "cpu.shares", buf); + if (r < 0) + log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set cpu.shares: %m"); + + xsprintf(buf, USEC_FMT "\n", CGROUP_CPU_QUOTA_PERIOD_USEC); + r = cg_set_attribute("cpu", u->cgroup_path, "cpu.cfs_period_us", buf); + if (r < 0) + log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set cpu.cfs_period_us: %m"); + + if (quota != USEC_INFINITY) { + xsprintf(buf, USEC_FMT "\n", quota * CGROUP_CPU_QUOTA_PERIOD_USEC / USEC_PER_SEC); + r = cg_set_attribute("cpu", u->cgroup_path, "cpu.cfs_quota_us", buf); + } else + r = cg_set_attribute("cpu", u->cgroup_path, "cpu.cfs_quota_us", "-1"); + if (r < 0) + log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set cpu.cfs_quota_us: %m"); +} + +static uint64_t cgroup_cpu_shares_to_weight(uint64_t shares) { + return CLAMP(shares * CGROUP_WEIGHT_DEFAULT / CGROUP_CPU_SHARES_DEFAULT, + CGROUP_WEIGHT_MIN, CGROUP_WEIGHT_MAX); +} + +static uint64_t cgroup_cpu_weight_to_shares(uint64_t weight) { + return CLAMP(weight * CGROUP_CPU_SHARES_DEFAULT / CGROUP_WEIGHT_DEFAULT, + CGROUP_CPU_SHARES_MIN, CGROUP_CPU_SHARES_MAX); +} + static bool cgroup_context_has_io_config(CGroupContext *c) { return c->io_accounting || c->io_weight != CGROUP_WEIGHT_INVALID || @@ -521,7 +620,7 @@ static unsigned cgroup_apply_blkio_device_limit(Unit *u, const char *dev_path, u } static bool cgroup_context_has_unified_memory_config(CGroupContext *c) { - return c->memory_low > 0 || c->memory_high != CGROUP_LIMIT_MAX || c->memory_max != CGROUP_LIMIT_MAX; + return c->memory_low > 0 || c->memory_high != CGROUP_LIMIT_MAX || c->memory_max != CGROUP_LIMIT_MAX || c->memory_swap_max != CGROUP_LIMIT_MAX; } static void cgroup_apply_unified_memory_limit(Unit *u, const char *file, uint64_t v) { @@ -566,30 +665,42 @@ static void cgroup_context_apply(Unit *u, CGroupMask mask, ManagerState state) { * and missing cgroups, i.e. EROFS and ENOENT. */ if ((mask & CGROUP_MASK_CPU) && !is_root) { - char buf[MAX(DECIMAL_STR_MAX(uint64_t), DECIMAL_STR_MAX(usec_t)) + 1]; + bool has_weight = cgroup_context_has_cpu_weight(c); + bool has_shares = cgroup_context_has_cpu_shares(c); - sprintf(buf, "%" PRIu64 "\n", - IN_SET(state, MANAGER_STARTING, MANAGER_INITIALIZING) && c->startup_cpu_shares != CGROUP_CPU_SHARES_INVALID ? c->startup_cpu_shares : - c->cpu_shares != CGROUP_CPU_SHARES_INVALID ? c->cpu_shares : CGROUP_CPU_SHARES_DEFAULT); - r = cg_set_attribute("cpu", path, "cpu.shares", buf); - if (r < 0) - log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set cpu.shares: %m"); + if (cg_all_unified() > 0) { + uint64_t weight; - sprintf(buf, USEC_FMT "\n", CGROUP_CPU_QUOTA_PERIOD_USEC); - r = cg_set_attribute("cpu", path, "cpu.cfs_period_us", buf); - if (r < 0) - log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set cpu.cfs_period_us: %m"); + if (has_weight) + weight = cgroup_context_cpu_weight(c, state); + else if (has_shares) { + uint64_t shares = cgroup_context_cpu_shares(c, state); - if (c->cpu_quota_per_sec_usec != USEC_INFINITY) { - sprintf(buf, USEC_FMT "\n", c->cpu_quota_per_sec_usec * CGROUP_CPU_QUOTA_PERIOD_USEC / USEC_PER_SEC); - r = cg_set_attribute("cpu", path, "cpu.cfs_quota_us", buf); - } else - r = cg_set_attribute("cpu", path, "cpu.cfs_quota_us", "-1"); - if (r < 0) - log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set cpu.cfs_quota_us: %m"); + weight = cgroup_cpu_shares_to_weight(shares); + + log_cgroup_compat(u, "Applying [Startup]CpuShares %" PRIu64 " as [Startup]CpuWeight %" PRIu64 " on %s", + shares, weight, path); + } else + weight = CGROUP_WEIGHT_DEFAULT; + + cgroup_apply_unified_cpu_config(u, weight, c->cpu_quota_per_sec_usec); + } else { + uint64_t shares; + + if (has_weight) { + uint64_t weight = cgroup_context_cpu_weight(c, state); + + shares = cgroup_cpu_weight_to_shares(weight); + + log_cgroup_compat(u, "Applying [Startup]CpuWeight %" PRIu64 " as [Startup]CpuShares %" PRIu64 " on %s", + weight, shares, path); + } else if (has_shares) + shares = cgroup_context_cpu_shares(c, state); + else + shares = CGROUP_CPU_SHARES_DEFAULT; + + cgroup_apply_legacy_cpu_config(u, shares, c->cpu_quota_per_sec_usec); + } } if (mask & CGROUP_MASK_IO) { @@ -677,16 +788,16 @@ static void cgroup_context_apply(Unit *u, CGroupMask mask, ManagerState state) { char buf[DECIMAL_STR_MAX(uint64_t)+1]; uint64_t weight; - if (has_blockio) - weight = cgroup_context_blkio_weight(c, state); - else if (has_io) { + if (has_io) { uint64_t io_weight = cgroup_context_io_weight(c, state); weight = cgroup_weight_io_to_blkio(cgroup_context_io_weight(c, state)); log_cgroup_compat(u, "Applying [Startup]IOWeight %" PRIu64 " as [Startup]BlockIOWeight %" PRIu64, io_weight, weight); - } else + } else if (has_blockio) + weight = cgroup_context_blkio_weight(c, state); + else weight = CGROUP_BLKIO_WEIGHT_DEFAULT; xsprintf(buf, "%" PRIu64 "\n", weight); @@ -695,13 +806,7 @@ static void cgroup_context_apply(Unit *u, CGroupMask mask, ManagerState state) { log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, "Failed to set blkio.weight: %m"); - if (has_blockio) { - CGroupBlockIODeviceWeight *w; - - /* FIXME: no way to reset this list */ - LIST_FOREACH(device_weights, w, c->blockio_device_weights) - cgroup_apply_blkio_device_weight(u, w->path, w->weight); - } else if (has_io) { + if (has_io) { CGroupIODeviceWeight *w; /* FIXME: no way to reset this list */ @@ -713,18 +818,17 @@ static void cgroup_context_apply(Unit *u, CGroupMask mask, ManagerState state) { cgroup_apply_blkio_device_weight(u, w->path, weight); } + } else if (has_blockio) { + CGroupBlockIODeviceWeight *w; + + /* FIXME: no way to reset this list */ + LIST_FOREACH(device_weights, w, c->blockio_device_weights) + cgroup_apply_blkio_device_weight(u, w->path, w->weight); } } /* Apply limits and free ones without config. */ - if (has_blockio) { - CGroupBlockIODeviceBandwidth *b, *next; - - LIST_FOREACH_SAFE(device_bandwidths, b, next, c->blockio_device_bandwidths) { - if (!cgroup_apply_blkio_device_limit(u, b->path, b->rbps, b->wbps)) - cgroup_context_free_blockio_device_bandwidth(c, b); - } - } else if (has_io) { + if (has_io) { CGroupIODeviceLimit *l, *next; LIST_FOREACH_SAFE(device_limits, l, next, c->io_device_limits) { @@ -734,16 +838,24 @@ static void cgroup_context_apply(Unit *u, CGroupMask mask, ManagerState state) { if (!cgroup_apply_blkio_device_limit(u, l->path, l->limits[CGROUP_IO_RBPS_MAX], l->limits[CGROUP_IO_WBPS_MAX])) cgroup_context_free_io_device_limit(c, l); } + } else if (has_blockio) { + CGroupBlockIODeviceBandwidth *b, *next; + + LIST_FOREACH_SAFE(device_bandwidths, b, next, c->blockio_device_bandwidths) + if (!cgroup_apply_blkio_device_limit(u, b->path, b->rbps, b->wbps)) + cgroup_context_free_blockio_device_bandwidth(c, b); } } if ((mask & CGROUP_MASK_MEMORY) && !is_root) { - if (cg_unified() > 0) { - uint64_t max = c->memory_max; + if (cg_all_unified() > 0) { + uint64_t max; + uint64_t swap_max = CGROUP_LIMIT_MAX; - if (cgroup_context_has_unified_memory_config(c)) + if (cgroup_context_has_unified_memory_config(c)) { max = c->memory_max; - else { + swap_max = c->memory_swap_max; + } else { max = c->memory_limit; if (max != CGROUP_LIMIT_MAX) @@ -753,16 +865,16 @@ static void cgroup_context_apply(Unit *u, CGroupMask mask, ManagerState state) { cgroup_apply_unified_memory_limit(u, "memory.low", c->memory_low); cgroup_apply_unified_memory_limit(u, "memory.high", c->memory_high); cgroup_apply_unified_memory_limit(u, "memory.max", max); + cgroup_apply_unified_memory_limit(u, "memory.swap.max", swap_max); } else { char buf[DECIMAL_STR_MAX(uint64_t) + 1]; - uint64_t val = c->memory_limit; + uint64_t val; - if (val == CGROUP_LIMIT_MAX) { + if (cgroup_context_has_unified_memory_config(c)) { val = c->memory_max; - - if (val != CGROUP_LIMIT_MAX) - log_cgroup_compat(u, "Applying MemoryMax %" PRIi64 " as MemoryLimit", c->memory_max); - } + log_cgroup_compat(u, "Applying MemoryMax %" PRIi64 " as MemoryLimit", val); + } else + val = c->memory_limit; if (val == CGROUP_LIMIT_MAX) strncpy(buf, "-1\n", sizeof(buf)); @@ -844,7 +956,7 @@ static void cgroup_context_apply(Unit *u, CGroupMask mask, ManagerState state) { if ((mask & CGROUP_MASK_PIDS) && !is_root) { - if (c->tasks_max != (uint64_t) -1) { + if (c->tasks_max != CGROUP_LIMIT_MAX) { char buf[DECIMAL_STR_MAX(uint64_t) + 2]; sprintf(buf, "%" PRIu64 "\n", c->tasks_max); @@ -864,8 +976,8 @@ CGroupMask cgroup_context_get_mask(CGroupContext *c) { /* Figure out which controllers we need */ if (c->cpu_accounting || - c->cpu_shares != CGROUP_CPU_SHARES_INVALID || - c->startup_cpu_shares != CGROUP_CPU_SHARES_INVALID || + cgroup_context_has_cpu_weight(c) || + cgroup_context_has_cpu_shares(c) || c->cpu_quota_per_sec_usec != USEC_INFINITY) mask |= CGROUP_MASK_CPUACCT | CGROUP_MASK_CPU; @@ -911,7 +1023,7 @@ CGroupMask unit_get_own_mask(Unit *u) { e = unit_get_exec_context(u); if (!e || exec_context_maintains_privileges(e) || - cg_unified() > 0) + cg_all_unified() > 0) return _CGROUP_MASK_ALL; } @@ -1137,7 +1249,7 @@ int unit_watch_cgroup(Unit *u) { return 0; /* Only applies to the unified hierarchy */ - r = cg_unified(); + r = cg_unified(SYSTEMD_CGROUP_CONTROLLER); if (r < 0) return log_unit_error_errno(u, r, "Failed detect whether the unified hierarchy is used: %m"); if (r == 0) @@ -1247,6 +1359,26 @@ int unit_attach_pids_to_cgroup(Unit *u) { return 0; } +static void cgroup_xattr_apply(Unit *u) { + char ids[SD_ID128_STRING_MAX]; + int r; + + assert(u); + + if (!MANAGER_IS_SYSTEM(u->manager)) + return; + + if (sd_id128_is_null(u->invocation_id)) + return; + + r = cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, + "trusted.invocation_id", + sd_id128_to_string(u->invocation_id, ids), 32, + 0); + if (r < 0) + log_unit_warning_errno(u, r, "Failed to set invocation ID on control group %s, ignoring: %m", u->cgroup_path); +} + static bool unit_has_mask_realized(Unit *u, CGroupMask target_mask, CGroupMask enable_mask) { assert(u); @@ -1290,6 +1422,7 @@ static int unit_realize_cgroup_now(Unit *u, ManagerState state) { /* Finally, apply the necessary attributes. */ cgroup_context_apply(u, target_mask, state); + cgroup_xattr_apply(u); return 0; } @@ -1416,6 +1549,8 @@ void unit_prune_cgroup(Unit *u) { if (!u->cgroup_path) return; + (void) unit_get_cpu_usage(u, NULL); /* Cache the last CPU usage value before we destroy the cgroup */ + is_root_slice = unit_has_name(u, SPECIAL_ROOT_SLICE); r = cg_trim_everywhere(u->manager->cgroup_supported, u->cgroup_path, !is_root_slice); @@ -1537,7 +1672,7 @@ int unit_watch_all_pids(Unit *u) { if (!u->cgroup_path) return -ENOENT; - if (cg_unified() > 0) /* On unified we can use proper notifications */ + if (cg_unified(SYSTEMD_CGROUP_CONTROLLER) > 0) /* On unified we can use proper notifications */ return 0; return unit_watch_pids_in_path(u, u->cgroup_path); @@ -1610,7 +1745,7 @@ static int on_cgroup_inotify_event(sd_event_source *s, int fd, uint32_t revents, int manager_setup_cgroup(Manager *m) { _cleanup_free_ char *path = NULL; CGroupController c; - int r, unified; + int r, all_unified, systemd_unified; char *e; assert(m); @@ -1647,11 +1782,17 @@ int manager_setup_cgroup(Manager *m) { if (r < 0) return log_error_errno(r, "Cannot find cgroup mount point: %m"); - unified = cg_unified(); - if (unified < 0) - return log_error_errno(r, "Couldn't determine if we are running in the unified hierarchy: %m"); - if (unified > 0) + all_unified = cg_all_unified(); + systemd_unified = cg_unified(SYSTEMD_CGROUP_CONTROLLER); + + if (all_unified < 0 || systemd_unified < 0) + return log_error_errno(all_unified < 0 ? all_unified : systemd_unified, + "Couldn't determine if we are running in the unified hierarchy: %m"); + + if (all_unified > 0) log_debug("Unified cgroup hierarchy is located at %s.", path); + else if (systemd_unified > 0) + log_debug("Unified cgroup hierarchy is located at %s. Controllers are on legacy hierarchies.", path); else log_debug("Using cgroup controller " SYSTEMD_CGROUP_CONTROLLER ". File system hierarchy is at %s.", path); @@ -1659,7 +1800,7 @@ int manager_setup_cgroup(Manager *m) { const char *scope_path; /* 3. Install agent */ - if (unified) { + if (systemd_unified) { /* In the unified hierarchy we can get * cgroup empty notifications via inotify. */ @@ -1719,7 +1860,7 @@ int manager_setup_cgroup(Manager *m) { return log_error_errno(errno, "Failed to open pin file: %m"); /* 6. Always enable hierarchical support if it exists... */ - if (!unified) + if (!all_unified) (void) cg_set_attribute("memory", "/", "memory.use_hierarchy", "1"); } @@ -1845,7 +1986,7 @@ int unit_get_memory_current(Unit *u, uint64_t *ret) { if ((u->cgroup_realized_mask & CGROUP_MASK_MEMORY) == 0) return -ENODATA; - if (cg_unified() <= 0) + if (cg_all_unified() <= 0) r = cg_get_attribute("memory", u->cgroup_path, "memory.usage_in_bytes", &v); else r = cg_get_attribute("memory", u->cgroup_path, "memory.current", &v); @@ -1890,18 +2031,37 @@ static int unit_get_cpu_usage_raw(Unit *u, nsec_t *ret) { if (!u->cgroup_path) return -ENODATA; - if ((u->cgroup_realized_mask & CGROUP_MASK_CPUACCT) == 0) - return -ENODATA; + if (cg_all_unified() > 0) { + const char *keys[] = { "usage_usec", NULL }; + _cleanup_free_ char *val = NULL; + uint64_t us; - r = cg_get_attribute("cpuacct", u->cgroup_path, "cpuacct.usage", &v); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; + if ((u->cgroup_realized_mask & CGROUP_MASK_CPU) == 0) + return -ENODATA; - r = safe_atou64(v, &ns); - if (r < 0) - return r; + r = cg_get_keyed_attribute("cpu", u->cgroup_path, "cpu.stat", keys, &val); + if (r < 0) + return r; + + r = safe_atou64(val, &us); + if (r < 0) + return r; + + ns = us * NSEC_PER_USEC; + } else { + if ((u->cgroup_realized_mask & CGROUP_MASK_CPUACCT) == 0) + return -ENODATA; + + r = cg_get_attribute("cpuacct", u->cgroup_path, "cpuacct.usage", &v); + if (r == -ENOENT) + return -ENODATA; + if (r < 0) + return r; + + r = safe_atou64(v, &ns); + if (r < 0) + return r; + } *ret = ns; return 0; @@ -1911,16 +2071,33 @@ int unit_get_cpu_usage(Unit *u, nsec_t *ret) { nsec_t ns; int r; + assert(u); + + /* Retrieve the current CPU usage counter. This will subtract the CPU counter taken when the unit was + * started. If the cgroup has been removed already, returns the last cached value. To cache the value, simply + * call this function with a NULL return value. */ + r = unit_get_cpu_usage_raw(u, &ns); + if (r == -ENODATA && u->cpu_usage_last != NSEC_INFINITY) { + /* If we can't get the CPU usage anymore (because the cgroup was already removed, for example), use our + * cached value. */ + + if (ret) + *ret = u->cpu_usage_last; + return 0; + } if (r < 0) return r; - if (ns > u->cpuacct_usage_base) - ns -= u->cpuacct_usage_base; + if (ns > u->cpu_usage_base) + ns -= u->cpu_usage_base; else ns = 0; - *ret = ns; + u->cpu_usage_last = ns; + if (ret) + *ret = ns; + return 0; } @@ -1930,13 +2107,15 @@ int unit_reset_cpu_usage(Unit *u) { assert(u); + u->cpu_usage_last = NSEC_INFINITY; + r = unit_get_cpu_usage_raw(u, &ns); if (r < 0) { - u->cpuacct_usage_base = 0; + u->cpu_usage_base = 0; return r; } - u->cpuacct_usage_base = ns; + u->cpu_usage_base = ns; return 0; } diff --git a/src/core/cgroup.h b/src/core/cgroup.h index a57403e79f..4cd168f63e 100644 --- a/src/core/cgroup.h +++ b/src/core/cgroup.h @@ -89,6 +89,10 @@ struct CGroupContext { bool tasks_accounting; /* For unified hierarchy */ + uint64_t cpu_weight; + uint64_t startup_cpu_weight; + usec_t cpu_quota_per_sec_usec; + uint64_t io_weight; uint64_t startup_io_weight; LIST_HEAD(CGroupIODeviceWeight, io_device_weights); @@ -97,11 +101,11 @@ struct CGroupContext { uint64_t memory_low; uint64_t memory_high; uint64_t memory_max; + uint64_t memory_swap_max; /* For legacy hierarchies */ uint64_t cpu_shares; uint64_t startup_cpu_shares; - usec_t cpu_quota_per_sec_usec; uint64_t blockio_weight; uint64_t startup_blockio_weight; diff --git a/src/core/dbus-automount.c b/src/core/dbus-automount.c index b2806ad86f..26212b3a95 100644 --- a/src/core/dbus-automount.c +++ b/src/core/dbus-automount.c @@ -32,3 +32,57 @@ const sd_bus_vtable bus_automount_vtable[] = { SD_BUS_PROPERTY("TimeoutIdleUSec", "t", bus_property_get_usec, offsetof(Automount, timeout_idle_usec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_VTABLE_END }; + +static int bus_automount_set_transient_property( + Automount *a, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + int r; + + assert(a); + assert(name); + assert(message); + + if (streq(name, "TimeoutIdleUSec")) { + usec_t timeout_idle_usec; + r = sd_bus_message_read(message, "t", &timeout_idle_usec); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + char time[FORMAT_TIMESPAN_MAX]; + + a->timeout_idle_usec = timeout_idle_usec; + unit_write_drop_in_format(UNIT(a), mode, name, "[Automount]\nTimeoutIdleSec=%s\n", + format_timespan(time, sizeof(time), timeout_idle_usec, USEC_PER_MSEC)); + } + } else + return 0; + + return 1; +} + +int bus_automount_set_property( + Unit *u, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + Automount *a = AUTOMOUNT(u); + int r = 0; + + assert(a); + assert(name); + assert(message); + + if (u->transient && u->load_state == UNIT_STUB) + /* This is a transient unit, let's load a little more */ + + r = bus_automount_set_transient_property(a, name, message, mode, error); + + return r; +} diff --git a/src/core/dbus-automount.h b/src/core/dbus-automount.h index 7b51eb973a..f41adda2a6 100644 --- a/src/core/dbus-automount.h +++ b/src/core/dbus-automount.h @@ -21,3 +21,5 @@ extern const sd_bus_vtable bus_automount_vtable[]; + +int bus_automount_set_property(Unit *u, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c index 85b0c86a2f..c4067a95bf 100644 --- a/src/core/dbus-cgroup.c +++ b/src/core/dbus-cgroup.c @@ -210,6 +210,8 @@ const sd_bus_vtable bus_cgroup_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Delegate", "b", bus_property_get_bool, offsetof(CGroupContext, delegate), 0), SD_BUS_PROPERTY("CPUAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, cpu_accounting), 0), + SD_BUS_PROPERTY("CPUWeight", "t", NULL, offsetof(CGroupContext, cpu_weight), 0), + SD_BUS_PROPERTY("StartupCPUWeight", "t", NULL, offsetof(CGroupContext, startup_cpu_weight), 0), SD_BUS_PROPERTY("CPUShares", "t", NULL, offsetof(CGroupContext, cpu_shares), 0), SD_BUS_PROPERTY("StartupCPUShares", "t", NULL, offsetof(CGroupContext, startup_cpu_shares), 0), SD_BUS_PROPERTY("CPUQuotaPerSecUSec", "t", bus_property_get_usec, offsetof(CGroupContext, cpu_quota_per_sec_usec), 0), @@ -231,6 +233,7 @@ const sd_bus_vtable bus_cgroup_vtable[] = { SD_BUS_PROPERTY("MemoryLow", "t", NULL, offsetof(CGroupContext, memory_low), 0), SD_BUS_PROPERTY("MemoryHigh", "t", NULL, offsetof(CGroupContext, memory_high), 0), SD_BUS_PROPERTY("MemoryMax", "t", NULL, offsetof(CGroupContext, memory_max), 0), + SD_BUS_PROPERTY("MemorySwapMax", "t", NULL, offsetof(CGroupContext, memory_swap_max), 0), SD_BUS_PROPERTY("MemoryLimit", "t", NULL, offsetof(CGroupContext, memory_limit), 0), SD_BUS_PROPERTY("DevicePolicy", "s", property_get_cgroup_device_policy, offsetof(CGroupContext, device_policy), 0), SD_BUS_PROPERTY("DeviceAllow", "a(ss)", property_get_device_allow, 0, 0), @@ -303,6 +306,50 @@ int bus_cgroup_set_property( return 1; + } else if (streq(name, "CPUWeight")) { + uint64_t weight; + + r = sd_bus_message_read(message, "t", &weight); + if (r < 0) + return r; + + if (!CGROUP_WEIGHT_IS_OK(weight)) + return sd_bus_error_set_errnof(error, EINVAL, "CPUWeight value out of range"); + + if (mode != UNIT_CHECK) { + c->cpu_weight = weight; + unit_invalidate_cgroup(u, CGROUP_MASK_CPU); + + if (weight == CGROUP_WEIGHT_INVALID) + unit_write_drop_in_private(u, mode, name, "CPUWeight="); + else + unit_write_drop_in_private_format(u, mode, name, "CPUWeight=%" PRIu64, weight); + } + + return 1; + + } else if (streq(name, "StartupCPUWeight")) { + uint64_t weight; + + r = sd_bus_message_read(message, "t", &weight); + if (r < 0) + return r; + + if (!CGROUP_WEIGHT_IS_OK(weight)) + return sd_bus_error_set_errnof(error, EINVAL, "StartupCPUWeight value out of range"); + + if (mode != UNIT_CHECK) { + c->startup_cpu_weight = weight; + unit_invalidate_cgroup(u, CGROUP_MASK_CPU); + + if (weight == CGROUP_CPU_SHARES_INVALID) + unit_write_drop_in_private(u, mode, name, "StartupCPUWeight="); + else + unit_write_drop_in_private_format(u, mode, name, "StartupCPUWeight=%" PRIu64, weight); + } + + return 1; + } else if (streq(name, "CPUShares")) { uint64_t shares; @@ -829,7 +876,7 @@ int bus_cgroup_set_property( return 1; - } else if (STR_IN_SET(name, "MemoryLow", "MemoryHigh", "MemoryMax")) { + } else if (STR_IN_SET(name, "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax")) { uint64_t v; r = sd_bus_message_read(message, "t", &v); @@ -843,6 +890,8 @@ int bus_cgroup_set_property( c->memory_low = v; else if (streq(name, "MemoryHigh")) c->memory_high = v; + else if (streq(name, "MemorySwapMax")) + c->memory_swap_max = v; else c->memory_max = v; diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 307c3d8e7a..1a7f770db1 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -44,6 +44,7 @@ #endif #include "strv.h" #include "syslog-util.h" +#include "user-util.h" #include "utf8.h" BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_exec_output, exec_output, ExecOutput); @@ -626,6 +627,53 @@ static int property_get_syslog_facility( return sd_bus_message_append(reply, "i", LOG_FAC(c->syslog_priority)); } +static int property_get_input_fdname( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + const char *name; + + assert(bus); + assert(c); + assert(property); + assert(reply); + + name = exec_context_fdname(c, STDIN_FILENO); + + return sd_bus_message_append(reply, "s", name); +} + +static int property_get_output_fdname( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + const char *name = NULL; + + assert(bus); + assert(c); + assert(property); + assert(reply); + + if (c->std_output == EXEC_OUTPUT_NAMED_FD && streq(property, "StandardOutputFileDescriptorName")) + name = exec_context_fdname(c, STDOUT_FILENO); + else if (c->std_error == EXEC_OUTPUT_NAMED_FD && streq(property, "StandardErrorFileDescriptorName")) + name = exec_context_fdname(c, STDERR_FILENO); + + return sd_bus_message_append(reply, "s", name); +} + const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(ExecContext, environment), SD_BUS_VTABLE_PROPERTY_CONST), @@ -676,8 +724,11 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_PROPERTY("CPUSchedulingResetOnFork", "b", bus_property_get_bool, offsetof(ExecContext, cpu_sched_reset_on_fork), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("NonBlocking", "b", bus_property_get_bool, offsetof(ExecContext, non_blocking), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("StandardInput", "s", property_get_exec_input, offsetof(ExecContext, std_input), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("StandardInputFileDescriptorName", "s", property_get_input_fdname, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("StandardOutput", "s", bus_property_get_exec_output, offsetof(ExecContext, std_output), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("StandardOutputFileDescriptorName", "s", property_get_output_fdname, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("StandardError", "s", bus_property_get_exec_output, offsetof(ExecContext, std_error), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("StandardErrorFileDescriptorName", "s", property_get_output_fdname, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("TTYPath", "s", NULL, offsetof(ExecContext, tty_path), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("TTYReset", "b", bus_property_get_bool, offsetof(ExecContext, tty_reset), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("TTYVHangup", "b", bus_property_get_bool, offsetof(ExecContext, tty_vhangup), SD_BUS_VTABLE_PROPERTY_CONST), @@ -693,6 +744,8 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_PROPERTY("AmbientCapabilities", "t", property_get_ambient_capabilities, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("User", "s", NULL, offsetof(ExecContext, user), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Group", "s", NULL, offsetof(ExecContext, group), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DynamicUser", "b", bus_property_get_bool, offsetof(ExecContext, dynamic_user), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RemoveIPC", "b", bus_property_get_bool, offsetof(ExecContext, remove_ipc), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("SupplementaryGroups", "as", NULL, offsetof(ExecContext, supplementary_groups), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("PAMName", "s", NULL, offsetof(ExecContext, pam_name), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ReadWriteDirectories", "as", NULL, offsetof(ExecContext, read_write_paths), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), @@ -703,8 +756,12 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_PROPERTY("InaccessiblePaths", "as", NULL, offsetof(ExecContext, inaccessible_paths), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("MountFlags", "t", bus_property_get_ulong, offsetof(ExecContext, mount_flags), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("PrivateTmp", "b", bus_property_get_bool, offsetof(ExecContext, private_tmp), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("PrivateNetwork", "b", bus_property_get_bool, offsetof(ExecContext, private_network), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("PrivateDevices", "b", bus_property_get_bool, offsetof(ExecContext, private_devices), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ProtectKernelTunables", "b", bus_property_get_bool, offsetof(ExecContext, protect_kernel_tunables), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ProtectKernelModules", "b", bus_property_get_bool, offsetof(ExecContext, protect_kernel_modules), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ProtectControlGroups", "b", bus_property_get_bool, offsetof(ExecContext, protect_control_groups), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PrivateNetwork", "b", bus_property_get_bool, offsetof(ExecContext, private_network), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PrivateUsers", "b", bus_property_get_bool, offsetof(ExecContext, private_users), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ProtectHome", "s", bus_property_get_protect_home, offsetof(ExecContext, protect_home), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ProtectSystem", "s", bus_property_get_protect_system, offsetof(ExecContext, protect_system), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("SameProcessGroup", "b", bus_property_get_bool, offsetof(ExecContext, same_pgrp), SD_BUS_VTABLE_PROPERTY_CONST), @@ -840,6 +897,9 @@ int bus_exec_context_set_transient_property( if (r < 0) return r; + if (!isempty(uu) && !valid_user_group_name_or_id(uu)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user name: %s", uu); + if (mode != UNIT_CHECK) { if (isempty(uu)) @@ -859,6 +919,9 @@ int bus_exec_context_set_transient_property( if (r < 0) return r; + if (!isempty(gg) && !valid_user_group_name_or_id(gg)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group name: %s", gg); + if (mode != UNIT_CHECK) { if (isempty(gg)) @@ -927,7 +990,7 @@ int bus_exec_context_set_transient_property( if (r < 0) return r; - if (n < PRIO_MIN || n >= PRIO_MAX) + if (!nice_is_valid(n)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Nice value out of range"); if (mode != UNIT_CHECK) { @@ -1017,7 +1080,6 @@ int bus_exec_context_set_transient_property( return 1; - } else if (streq(name, "StandardOutput")) { const char *s; ExecOutput p; @@ -1059,9 +1121,46 @@ int bus_exec_context_set_transient_property( return 1; } else if (STR_IN_SET(name, + "StandardInputFileDescriptorName", "StandardOutputFileDescriptorName", "StandardErrorFileDescriptorName")) { + const char *s; + + r = sd_bus_message_read(message, "s", &s); + if (r < 0) + return r; + + if (!fdname_is_valid(s)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid file descriptor name"); + + if (mode != UNIT_CHECK) { + if (streq(name, "StandardInputFileDescriptorName")) { + c->std_input = EXEC_INPUT_NAMED_FD; + r = free_and_strdup(&c->stdio_fdname[STDIN_FILENO], s); + if (r < 0) + return r; + unit_write_drop_in_private_format(u, mode, name, "StandardInput=fd:%s", s); + } else if (streq(name, "StandardOutputFileDescriptorName")) { + c->std_output = EXEC_OUTPUT_NAMED_FD; + r = free_and_strdup(&c->stdio_fdname[STDOUT_FILENO], s); + if (r < 0) + return r; + unit_write_drop_in_private_format(u, mode, name, "StandardOutput=fd:%s", s); + } else if (streq(name, "StandardErrorFileDescriptorName")) { + c->std_error = EXEC_OUTPUT_NAMED_FD; + r = free_and_strdup(&c->stdio_fdname[STDERR_FILENO], s); + if (r < 0) + return r; + unit_write_drop_in_private_format(u, mode, name, "StandardError=fd:%s", s); + } + } + + return 1; + + } else if (STR_IN_SET(name, "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", - "PrivateTmp", "PrivateDevices", "PrivateNetwork", - "NoNewPrivileges", "SyslogLevelPrefix", "MemoryDenyWriteExecute", "RestrictRealtime")) { + "PrivateTmp", "PrivateDevices", "PrivateNetwork", "PrivateUsers", + "NoNewPrivileges", "SyslogLevelPrefix", "MemoryDenyWriteExecute", + "RestrictRealtime", "DynamicUser", "RemoveIPC", "ProtectKernelTunables", + "ProtectKernelModules", "ProtectControlGroups")) { int b; r = sd_bus_message_read(message, "b", &b); @@ -1081,6 +1180,8 @@ int bus_exec_context_set_transient_property( c->private_devices = b; else if (streq(name, "PrivateNetwork")) c->private_network = b; + else if (streq(name, "PrivateUsers")) + c->private_users = b; else if (streq(name, "NoNewPrivileges")) c->no_new_privileges = b; else if (streq(name, "SyslogLevelPrefix")) @@ -1089,6 +1190,16 @@ int bus_exec_context_set_transient_property( c->memory_deny_write_execute = b; else if (streq(name, "RestrictRealtime")) c->restrict_realtime = b; + else if (streq(name, "DynamicUser")) + c->dynamic_user = b; + else if (streq(name, "RemoveIPC")) + c->remove_ipc = b; + else if (streq(name, "ProtectKernelTunables")) + c->protect_kernel_tunables = b; + else if (streq(name, "ProtectKernelModules")) + c->protect_kernel_modules = b; + else if (streq(name, "ProtectControlGroups")) + c->protect_control_groups = b; unit_write_drop_in_private_format(u, mode, name, "%s=%s", name, yes_no(b)); } diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index d05968bd65..d7d3d3c8ce 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -43,9 +43,15 @@ #include "string-util.h" #include "strv.h" #include "syslog-util.h" +#include "user-util.h" #include "virt.h" #include "watchdog.h" +static UnitFileFlags unit_file_bools_to_flags(bool runtime, bool force) { + return (runtime ? UNIT_FILE_RUNTIME : 0) | + (force ? UNIT_FILE_FORCE : 0); +} + static int property_get_version( sd_bus *bus, const char *path, @@ -463,6 +469,64 @@ static int method_get_unit_by_pid(sd_bus_message *message, void *userdata, sd_bu return sd_bus_reply_method_return(message, "o", path); } +static int method_get_unit_by_invocation_id(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *path = NULL; + Manager *m = userdata; + sd_id128_t id; + const void *a; + Unit *u; + size_t sz; + int r; + + assert(message); + assert(m); + + /* Anyone can call this method */ + + r = sd_bus_message_read_array(message, 'y', &a, &sz); + if (r < 0) + return r; + if (sz == 0) + id = SD_ID128_NULL; + else if (sz == 16) + memcpy(&id, a, sz); + else + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid invocation ID"); + + if (sd_id128_is_null(id)) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + pid_t pid; + + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); + if (r < 0) + return r; + + r = sd_bus_creds_get_pid(creds, &pid); + if (r < 0) + return r; + + u = manager_get_unit_by_pid(m, pid); + if (!u) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Client " PID_FMT " not member of any unit.", pid); + } else { + u = hashmap_get(m->units_by_invocation_id, &id); + if (!u) + return sd_bus_error_setf(error, BUS_ERROR_NO_UNIT_FOR_INVOCATION_ID, "No unit with the specified invocation ID " SD_ID128_FORMAT_STR " known.", SD_ID128_FORMAT_VAL(id)); + } + + r = mac_selinux_unit_access_check(u, message, "status", error); + if (r < 0) + return r; + + /* So here's a special trick: the bus path we return actually references the unit by its invocation ID instead + * of the unit name. This means it stays valid only as long as the invocation ID stays the same. */ + path = unit_dbus_path_invocation_id(u); + if (!path) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "o", path); +} + static int method_load_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { _cleanup_free_ char *path = NULL; Manager *m = userdata; @@ -642,6 +706,54 @@ static int method_set_unit_properties(sd_bus_message *message, void *userdata, s return bus_unit_method_set_properties(message, u, error); } +static int method_ref_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + const char *name; + Unit *u; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + r = manager_load_unit(m, name, NULL, error, &u); + if (r < 0) + return r; + + r = bus_unit_check_load_state(u, error); + if (r < 0) + return r; + + return bus_unit_method_ref(message, u, error); +} + +static int method_unref_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + const char *name; + Unit *u; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + r = manager_load_unit(m, name, NULL, error, &u); + if (r < 0) + return r; + + r = bus_unit_check_load_state(u, error); + if (r < 0) + return r; + + return bus_unit_method_unref(message, u, error); +} + static int reply_unit_info(sd_bus_message *reply, Unit *u) { _cleanup_free_ char *unit_path = NULL, *job_path = NULL; Unit *following; @@ -780,6 +892,13 @@ static int transient_unit_from_message( if (r < 0) return r; + /* If the client asked for it, automatically add a reference to this unit. */ + if (u->bus_track_add) { + r = bus_unit_track_add_sender(u, message); + if (r < 0) + return log_error_errno(r, "Failed to watch sender: %m"); + } + /* Now load the missing bits of the unit we just created */ unit_add_to_load_queue(u); manager_dispatch_load_queue(m); @@ -1511,8 +1630,8 @@ static int method_unset_and_set_environment(sd_bus_message *message, void *userd } static int method_set_exit_code(sd_bus_message *message, void *userdata, sd_bus_error *error) { - uint8_t code; Manager *m = userdata; + uint8_t code; int r; assert(message); @@ -1534,6 +1653,61 @@ static int method_set_exit_code(sd_bus_message *message, void *userdata, sd_bus_ return sd_bus_reply_method_return(message, NULL); } +static int method_lookup_dynamic_user_by_name(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + const char *name; + uid_t uid; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read_basic(message, 's', &name); + if (r < 0) + return r; + + if (!MANAGER_IS_SYSTEM(m)) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Dynamic users are only supported in the system instance."); + if (!valid_user_group_name(name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name invalid: %s", name); + + r = dynamic_user_lookup_name(m, name, &uid); + if (r == -ESRCH) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_DYNAMIC_USER, "Dynamic user %s does not exist.", name); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, "u", (uint32_t) uid); +} + +static int method_lookup_dynamic_user_by_uid(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *name = NULL; + Manager *m = userdata; + uid_t uid; + int r; + + assert(message); + assert(m); + + assert_cc(sizeof(uid) == sizeof(uint32_t)); + r = sd_bus_message_read_basic(message, 'u', &uid); + if (r < 0) + return r; + + if (!MANAGER_IS_SYSTEM(m)) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Dynamic users are only supported in the system instance."); + if (!uid_is_valid(uid)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User ID invalid: " UID_FMT, uid); + + r = dynamic_user_lookup_uid(m, uid, &name); + if (r == -ESRCH) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_DYNAMIC_USER, "Dynamic user ID " UID_FMT " does not exist.", uid); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, "s", name); +} + static int list_unit_files_by_patterns(sd_bus_message *message, void *userdata, sd_bus_error *error, char **states, char **patterns) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; Manager *m = userdata; @@ -1779,13 +1953,14 @@ static int install_error( static int method_enable_unit_files_generic( sd_bus_message *message, Manager *m, - int (*call)(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], bool force, UnitFileChange **changes, unsigned *n_changes), + int (*call)(UnitFileScope scope, UnitFileFlags flags, const char *root_dir, char *files[], UnitFileChange **changes, unsigned *n_changes), bool carries_install_info, sd_bus_error *error) { _cleanup_strv_free_ char **l = NULL; UnitFileChange *changes = NULL; unsigned n_changes = 0; + UnitFileFlags flags; int runtime, force, r; assert(message); @@ -1799,13 +1974,15 @@ static int method_enable_unit_files_generic( if (r < 0) return r; + flags = unit_file_bools_to_flags(runtime, force); + r = bus_verify_manage_unit_files_async(m, message, error); if (r < 0) return r; if (r == 0) return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - r = call(m->unit_file_scope, runtime, NULL, l, force, &changes, &n_changes); + r = call(m->unit_file_scope, flags, NULL, l, &changes, &n_changes); if (r < 0) return install_error(error, r, changes, n_changes); @@ -1824,8 +2001,8 @@ static int method_link_unit_files(sd_bus_message *message, void *userdata, sd_bu return method_enable_unit_files_generic(message, userdata, unit_file_link, false, error); } -static int unit_file_preset_without_mode(UnitFileScope scope, bool runtime, const char *root_dir, char **files, bool force, UnitFileChange **changes, unsigned *n_changes) { - return unit_file_preset(scope, runtime, root_dir, files, UNIT_FILE_PRESET_FULL, force, changes, n_changes); +static int unit_file_preset_without_mode(UnitFileScope scope, UnitFileFlags flags, const char *root_dir, char **files, UnitFileChange **changes, unsigned *n_changes) { + return unit_file_preset(scope, flags, root_dir, files, UNIT_FILE_PRESET_FULL, changes, n_changes); } static int method_preset_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { @@ -1844,6 +2021,7 @@ static int method_preset_unit_files_with_mode(sd_bus_message *message, void *use Manager *m = userdata; UnitFilePresetMode mm; int runtime, force, r; + UnitFileFlags flags; const char *mode; assert(message); @@ -1857,6 +2035,8 @@ static int method_preset_unit_files_with_mode(sd_bus_message *message, void *use if (r < 0) return r; + flags = unit_file_bools_to_flags(runtime, force); + if (isempty(mode)) mm = UNIT_FILE_PRESET_FULL; else { @@ -1871,7 +2051,7 @@ static int method_preset_unit_files_with_mode(sd_bus_message *message, void *use if (r == 0) return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - r = unit_file_preset(m->unit_file_scope, runtime, NULL, l, mm, force, &changes, &n_changes); + r = unit_file_preset(m->unit_file_scope, flags, NULL, l, mm, &changes, &n_changes); if (r < 0) return install_error(error, r, changes, n_changes); @@ -1881,7 +2061,7 @@ static int method_preset_unit_files_with_mode(sd_bus_message *message, void *use static int method_disable_unit_files_generic( sd_bus_message *message, Manager *m, - int (*call)(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], UnitFileChange **changes, unsigned *n_changes), + int (*call)(UnitFileScope scope, UnitFileFlags flags, const char *root_dir, char *files[], UnitFileChange **changes, unsigned *n_changes), sd_bus_error *error) { _cleanup_strv_free_ char **l = NULL; @@ -1906,7 +2086,7 @@ static int method_disable_unit_files_generic( if (r == 0) return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - r = call(m->unit_file_scope, runtime, NULL, l, &changes, &n_changes); + r = call(m->unit_file_scope, runtime ? UNIT_FILE_RUNTIME : 0, NULL, l, &changes, &n_changes); if (r < 0) return install_error(error, r, changes, n_changes); @@ -1972,7 +2152,7 @@ static int method_set_default_target(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 */ - r = unit_file_set_default(m->unit_file_scope, NULL, name, force, &changes, &n_changes); + r = unit_file_set_default(m->unit_file_scope, force ? UNIT_FILE_FORCE : 0, NULL, name, &changes, &n_changes); if (r < 0) return install_error(error, r, changes, n_changes); @@ -1985,6 +2165,7 @@ static int method_preset_all_unit_files(sd_bus_message *message, void *userdata, Manager *m = userdata; UnitFilePresetMode mm; const char *mode; + UnitFileFlags flags; int force, runtime, r; assert(message); @@ -1998,6 +2179,8 @@ static int method_preset_all_unit_files(sd_bus_message *message, void *userdata, if (r < 0) return r; + flags = unit_file_bools_to_flags(runtime, force); + if (isempty(mode)) mm = UNIT_FILE_PRESET_FULL; else { @@ -2012,7 +2195,7 @@ static int method_preset_all_unit_files(sd_bus_message *message, void *userdata, if (r == 0) return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - r = unit_file_preset_all(m->unit_file_scope, runtime, NULL, mm, force, &changes, &n_changes); + r = unit_file_preset_all(m->unit_file_scope, flags, NULL, mm, &changes, &n_changes); if (r < 0) return install_error(error, r, changes, n_changes); @@ -2027,6 +2210,7 @@ static int method_add_dependency_unit_files(sd_bus_message *message, void *userd int runtime, force, r; char *target, *type; UnitDependency dep; + UnitFileFlags flags; assert(message); assert(m); @@ -2045,17 +2229,62 @@ static int method_add_dependency_unit_files(sd_bus_message *message, void *userd if (r < 0) return r; + flags = unit_file_bools_to_flags(runtime, force); + dep = unit_dependency_from_string(type); if (dep < 0) return -EINVAL; - r = unit_file_add_dependency(m->unit_file_scope, runtime, NULL, l, target, dep, force, &changes, &n_changes); + r = unit_file_add_dependency(m->unit_file_scope, flags, NULL, l, target, dep, &changes, &n_changes); if (r < 0) return install_error(error, r, changes, n_changes); return reply_unit_file_changes_and_free(m, message, -1, changes, n_changes); } +static int method_get_unit_file_links(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + UnitFileChange *changes = NULL; + unsigned n_changes = 0, i; + UnitFileFlags flags; + const char *name; + char **p; + int runtime, r; + + r = sd_bus_message_read(message, "sb", &name, &runtime); + if (r < 0) + return r; + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, SD_BUS_TYPE_ARRAY, "s"); + if (r < 0) + return r; + + p = STRV_MAKE(name); + flags = UNIT_FILE_DRY_RUN | + (runtime ? UNIT_FILE_RUNTIME : 0); + + r = unit_file_disable(UNIT_FILE_SYSTEM, flags, NULL, p, &changes, &n_changes); + if (r < 0) + return log_error_errno(r, "Failed to get file links for %s: %m", name); + + for (i = 0; i < n_changes; i++) + if (changes[i].type == UNIT_FILE_UNLINK) { + r = sd_bus_message_append(reply, "s", changes[i].path); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + const sd_bus_vtable bus_manager_vtable[] = { SD_BUS_VTABLE_START(0), @@ -2143,6 +2372,7 @@ const sd_bus_vtable bus_manager_vtable[] = { SD_BUS_METHOD("GetUnit", "s", "o", method_get_unit, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("GetUnitByPID", "u", "o", method_get_unit_by_pid, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetUnitByInvocationID", "ay", "o", method_get_unit_by_invocation_id, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("LoadUnit", "s", "o", method_load_unit, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("StartUnit", "ss", "o", method_start_unit, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("StartUnitReplace", "sss", "o", method_start_unit_replace, SD_BUS_VTABLE_UNPRIVILEGED), @@ -2155,6 +2385,8 @@ const sd_bus_vtable bus_manager_vtable[] = { SD_BUS_METHOD("KillUnit", "ssi", NULL, method_kill_unit, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("ResetFailedUnit", "s", NULL, method_reset_failed_unit, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("SetUnitProperties", "sba(sv)", NULL, method_set_unit_properties, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("RefUnit", "s", NULL, method_ref_unit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("UnrefUnit", "s", NULL, method_unref_unit, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("StartTransientUnit", "ssa(sv)a(sa(sv))", "o", method_start_transient_unit, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("GetUnitProcesses", "s", "a(sus)", method_get_unit_processes, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("GetJob", "u", "o", method_get_job, SD_BUS_VTABLE_UNPRIVILEGED), @@ -2198,7 +2430,10 @@ const sd_bus_vtable bus_manager_vtable[] = { SD_BUS_METHOD("GetDefaultTarget", NULL, "s", method_get_default_target, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("PresetAllUnitFiles", "sbb", "a(sss)", method_preset_all_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("AddDependencyUnitFiles", "asssbb", "a(sss)", method_add_dependency_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetUnitFileLinks", "sb", "as", method_get_unit_file_links, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("SetExitCode", "y", NULL, method_set_exit_code, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("LookupDynamicUserByName", "s", "u", method_lookup_dynamic_user_by_name, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("LookupDynamicUserByUID", "u", "s", method_lookup_dynamic_user_by_uid, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_SIGNAL("UnitNew", "so", 0), SD_BUS_SIGNAL("UnitRemoved", "so", 0), diff --git a/src/core/dbus-mount.c b/src/core/dbus-mount.c index 935db7c48b..76a7a7ce97 100644 --- a/src/core/dbus-mount.c +++ b/src/core/dbus-mount.c @@ -116,7 +116,11 @@ const sd_bus_vtable bus_mount_vtable[] = { SD_BUS_PROPERTY("ControlPID", "u", bus_property_get_pid, offsetof(Mount, control_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("DirectoryMode", "u", bus_property_get_mode, offsetof(Mount, directory_mode), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("SloppyOptions", "b", bus_property_get_bool, offsetof(Mount, sloppy_options), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LazyUnmount", "b", bus_property_get_bool, offsetof(Mount, lazy_unmount), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ForceUnmount", "b", bus_property_get_bool, offsetof(Mount, force_unmount), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Mount, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("UID", "u", NULL, offsetof(Unit, ref_uid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("GID", "u", NULL, offsetof(Unit, ref_gid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), BUS_EXEC_COMMAND_VTABLE("ExecMount", offsetof(Mount, exec_command[MOUNT_EXEC_MOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), BUS_EXEC_COMMAND_VTABLE("ExecUnmount", offsetof(Mount, exec_command[MOUNT_EXEC_UNMOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), BUS_EXEC_COMMAND_VTABLE("ExecRemount", offsetof(Mount, exec_command[MOUNT_EXEC_REMOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), @@ -157,6 +161,9 @@ static int bus_mount_set_transient_property( if (!p) return -ENOMEM; + unit_write_drop_in_format(UNIT(m), mode, name, "[Mount]\n%s=%s\n", + name, new_property); + free(*property); *property = p; } diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c index fab3677a01..61b83d2d62 100644 --- a/src/core/dbus-service.c +++ b/src/core/dbus-service.c @@ -36,7 +36,7 @@ static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, service_type, ServiceType static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, service_result, ServiceResult); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_restart, service_restart, ServiceRestart); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_notify_access, notify_access, NotifyAccess); -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_failure_action, failure_action, FailureAction); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_emergency_action, emergency_action, EmergencyAction); const sd_bus_vtable bus_service_vtable[] = { SD_BUS_VTABLE_START(0), @@ -50,12 +50,7 @@ const sd_bus_vtable bus_service_vtable[] = { SD_BUS_PROPERTY("RuntimeMaxUSec", "t", bus_property_get_usec, offsetof(Service, runtime_max_usec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("WatchdogUSec", "t", bus_property_get_usec, offsetof(Service, watchdog_usec), SD_BUS_VTABLE_PROPERTY_CONST), BUS_PROPERTY_DUAL_TIMESTAMP("WatchdogTimestamp", offsetof(Service, watchdog_timestamp), 0), - /* The following four are obsolete, and thus marked hidden here. They moved into the Unit interface */ - SD_BUS_PROPERTY("StartLimitInterval", "t", bus_property_get_usec, offsetof(Unit, start_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), - SD_BUS_PROPERTY("StartLimitBurst", "u", bus_property_get_unsigned, offsetof(Unit, start_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), - SD_BUS_PROPERTY("StartLimitAction", "s", property_get_failure_action, offsetof(Unit, start_limit_action), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), - SD_BUS_PROPERTY("RebootArgument", "s", NULL, offsetof(Unit, reboot_arg), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), - SD_BUS_PROPERTY("FailureAction", "s", property_get_failure_action, offsetof(Service, failure_action), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("FailureAction", "s", property_get_emergency_action, offsetof(Service, emergency_action), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("PermissionsStartOnly", "b", bus_property_get_bool, offsetof(Service, permissions_start_only), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RootDirectoryStartOnly", "b", bus_property_get_bool, offsetof(Service, root_directory_start_only), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RemainAfterExit", "b", bus_property_get_bool, offsetof(Service, remain_after_exit), SD_BUS_VTABLE_PROPERTY_CONST), @@ -70,6 +65,9 @@ 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("USBFunctionDescriptors", "s", NULL, offsetof(Service, usb_function_descriptors), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("USBFunctionStrings", "s", NULL, offsetof(Service, usb_function_strings), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("UID", "u", NULL, offsetof(Unit, ref_uid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("GID", "u", NULL, offsetof(Unit, ref_gid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + BUS_EXEC_STATUS_VTABLE("ExecMain", offsetof(Service, main_exec_status), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPre", offsetof(Service, exec_command[SERVICE_EXEC_START_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), BUS_EXEC_COMMAND_LIST_VTABLE("ExecStart", offsetof(Service, exec_command[SERVICE_EXEC_START]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), @@ -77,6 +75,12 @@ const sd_bus_vtable bus_service_vtable[] = { BUS_EXEC_COMMAND_LIST_VTABLE("ExecReload", offsetof(Service, exec_command[SERVICE_EXEC_RELOAD]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), BUS_EXEC_COMMAND_LIST_VTABLE("ExecStop", offsetof(Service, exec_command[SERVICE_EXEC_STOP]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), BUS_EXEC_COMMAND_LIST_VTABLE("ExecStopPost", offsetof(Service, exec_command[SERVICE_EXEC_STOP_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + + /* The following four are obsolete, and thus marked hidden here. They moved into the Unit interface */ + SD_BUS_PROPERTY("StartLimitInterval", "t", bus_property_get_usec, offsetof(Unit, start_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), + SD_BUS_PROPERTY("StartLimitBurst", "u", bus_property_get_unsigned, offsetof(Unit, start_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), + SD_BUS_PROPERTY("StartLimitAction", "s", property_get_emergency_action, offsetof(Unit, start_limit_action), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), + SD_BUS_PROPERTY("RebootArgument", "s", NULL, offsetof(Unit, reboot_arg), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), SD_BUS_VTABLE_END }; diff --git a/src/core/dbus-socket.c b/src/core/dbus-socket.c index 961340608d..21adb64e15 100644 --- a/src/core/dbus-socket.c +++ b/src/core/dbus-socket.c @@ -137,6 +137,7 @@ const sd_bus_vtable bus_socket_vtable[] = { SD_BUS_PROPERTY("Symlinks", "as", NULL, offsetof(Socket, symlinks), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Mark", "i", bus_property_get_int, offsetof(Socket, mark), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("MaxConnections", "u", bus_property_get_unsigned, offsetof(Socket, max_connections), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("MaxConnectionsPerSource", "u", bus_property_get_unsigned, offsetof(Socket, max_connections_per_source), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("MessageQueueMaxMessages", "x", bus_property_get_long, offsetof(Socket, mq_maxmsg), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("MessageQueueMessageSize", "x", bus_property_get_long, offsetof(Socket, mq_msgsize), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ReusePort", "b", bus_property_get_bool, offsetof(Socket, reuse_port), SD_BUS_VTABLE_PROPERTY_CONST), @@ -151,6 +152,8 @@ const sd_bus_vtable bus_socket_vtable[] = { SD_BUS_PROPERTY("SocketProtocol", "i", bus_property_get_int, offsetof(Socket, socket_protocol), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("TriggerLimitIntervalUSec", "t", bus_property_get_usec, offsetof(Socket, trigger_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("TriggerLimitBurst", "u", bus_property_get_unsigned, offsetof(Socket, trigger_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("UID", "u", NULL, offsetof(Unit, ref_uid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("GID", "u", NULL, offsetof(Unit, ref_gid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPre", offsetof(Socket, exec_command[SOCKET_EXEC_START_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPost", offsetof(Socket, exec_command[SOCKET_EXEC_START_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), BUS_EXEC_COMMAND_LIST_VTABLE("ExecStopPre", offsetof(Socket, exec_command[SOCKET_EXEC_STOP_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), diff --git a/src/core/dbus-swap.c b/src/core/dbus-swap.c index 292f8738c6..85a2c26b98 100644 --- a/src/core/dbus-swap.c +++ b/src/core/dbus-swap.c @@ -84,6 +84,8 @@ const sd_bus_vtable bus_swap_vtable[] = { SD_BUS_PROPERTY("TimeoutUSec", "t", bus_property_get_usec, offsetof(Swap, timeout_usec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ControlPID", "u", bus_property_get_pid, offsetof(Swap, control_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Swap, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("UID", "u", NULL, offsetof(Unit, ref_uid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("GID", "u", NULL, offsetof(Unit, ref_gid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), BUS_EXEC_COMMAND_VTABLE("ExecActivate", offsetof(Swap, exec_command[SWAP_EXEC_ACTIVATE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), BUS_EXEC_COMMAND_VTABLE("ExecDeactivate", offsetof(Swap, exec_command[SWAP_EXEC_DEACTIVATE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), SD_BUS_VTABLE_END diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c index b55d2cf735..69e249c844 100644 --- a/src/core/dbus-unit.c +++ b/src/core/dbus-unit.c @@ -37,7 +37,7 @@ static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_load_state, unit_load_state, UnitLoadState); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_job_mode, job_mode, JobMode); -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_failure_action, failure_action, FailureAction); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_emergency_action, emergency_action, EmergencyAction); static int property_get_names( sd_bus *bus, @@ -263,10 +263,7 @@ static int property_get_can_stop( assert(reply); assert(u); - /* On the lower levels we assume that every unit we can start - * we can also stop */ - - return sd_bus_message_append(reply, "b", unit_can_start(u) && !u->refuse_manual_stop); + return sd_bus_message_append(reply, "b", unit_can_stop(u) && !u->refuse_manual_stop); } static int property_get_can_reload( @@ -418,6 +415,7 @@ static int bus_verify_manage_units_async_full( const char *verb, int capability, const char *polkit_message, + bool interactive, sd_bus_message *call, sd_bus_error *error) { @@ -433,7 +431,15 @@ static int bus_verify_manage_units_async_full( details[7] = GETTEXT_PACKAGE; } - return bus_verify_polkit_async(call, capability, "org.freedesktop.systemd1.manage-units", details, false, UID_INVALID, &u->manager->polkit_registry, error); + return bus_verify_polkit_async( + call, + capability, + "org.freedesktop.systemd1.manage-units", + details, + interactive, + UID_INVALID, + &u->manager->polkit_registry, + error); } int bus_unit_method_start_generic( @@ -486,6 +492,7 @@ int bus_unit_method_start_generic( verb, CAP_SYS_ADMIN, job_type < _JOB_TYPE_MAX ? polkit_message_for_job[job_type] : NULL, + true, message, error); if (r < 0) @@ -558,6 +565,7 @@ int bus_unit_method_kill(sd_bus_message *message, void *userdata, sd_bus_error * "kill", CAP_KILL, N_("Authentication is required to kill '$(unit)'."), + true, message, error); if (r < 0) @@ -588,6 +596,7 @@ int bus_unit_method_reset_failed(sd_bus_message *message, void *userdata, sd_bus "reset-failed", CAP_SYS_ADMIN, N_("Authentication is required to reset the \"failed\" state of '$(unit)'."), + true, message, error); if (r < 0) @@ -620,6 +629,7 @@ int bus_unit_method_set_properties(sd_bus_message *message, void *userdata, sd_b "set-property", CAP_SYS_ADMIN, N_("Authentication is required to set properties on '$(unit)'."), + true, message, error); if (r < 0) @@ -634,6 +644,53 @@ int bus_unit_method_set_properties(sd_bus_message *message, void *userdata, sd_b return sd_bus_reply_method_return(message, NULL); } +int bus_unit_method_ref(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Unit *u = userdata; + int r; + + assert(message); + assert(u); + + r = mac_selinux_unit_access_check(u, message, "start", error); + if (r < 0) + return r; + + r = bus_verify_manage_units_async_full( + u, + "ref", + CAP_SYS_ADMIN, + NULL, + false, + message, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = bus_unit_track_add_sender(u, message); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_unit_method_unref(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Unit *u = userdata; + int r; + + assert(message); + assert(u); + + r = bus_unit_track_remove_sender(u, message); + if (r == -EUNATCH) + return sd_bus_error_setf(error, BUS_ERROR_NOT_REFERENCED, "Unit has not been referenced yet."); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + const sd_bus_vtable bus_unit_vtable[] = { SD_BUS_VTABLE_START(0), @@ -660,10 +717,6 @@ const sd_bus_vtable bus_unit_vtable[] = { SD_BUS_PROPERTY("PropagatesReloadTo", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_PROPAGATES_RELOAD_TO]), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ReloadPropagatedFrom", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_RELOAD_PROPAGATED_FROM]), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("JoinsNamespaceOf", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_JOINS_NAMESPACE_OF]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RequiresOverridable", "as", property_get_obsolete_dependencies, 0, SD_BUS_VTABLE_HIDDEN), - SD_BUS_PROPERTY("RequisiteOverridable", "as", property_get_obsolete_dependencies, 0, SD_BUS_VTABLE_HIDDEN), - SD_BUS_PROPERTY("RequiredByOverridable", "as", property_get_obsolete_dependencies, 0, SD_BUS_VTABLE_HIDDEN), - SD_BUS_PROPERTY("RequisiteOfOverridable", "as", property_get_obsolete_dependencies, 0, SD_BUS_VTABLE_HIDDEN), SD_BUS_PROPERTY("RequiresMountsFor", "as", NULL, offsetof(Unit, requires_mounts_for), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Documentation", "as", NULL, offsetof(Unit, documentation), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Description", "s", property_get_description, 0, SD_BUS_VTABLE_PROPERTY_CONST), @@ -694,7 +747,7 @@ const sd_bus_vtable bus_unit_vtable[] = { SD_BUS_PROPERTY("IgnoreOnIsolate", "b", bus_property_get_bool, offsetof(Unit, ignore_on_isolate), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("NeedDaemonReload", "b", property_get_need_daemon_reload, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("JobTimeoutUSec", "t", bus_property_get_usec, offsetof(Unit, job_timeout), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("JobTimeoutAction", "s", property_get_failure_action, offsetof(Unit, job_timeout_action), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("JobTimeoutAction", "s", property_get_emergency_action, offsetof(Unit, job_timeout_action), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("JobTimeoutRebootArgument", "s", NULL, offsetof(Unit, job_timeout_reboot_arg), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ConditionResult", "b", bus_property_get_bool, offsetof(Unit, condition_result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("AssertResult", "b", bus_property_get_bool, offsetof(Unit, assert_result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), @@ -704,11 +757,12 @@ const sd_bus_vtable bus_unit_vtable[] = { SD_BUS_PROPERTY("Asserts", "a(sbbsi)", property_get_conditions, offsetof(Unit, asserts), 0), SD_BUS_PROPERTY("LoadError", "(ss)", property_get_load_error, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Transient", "b", bus_property_get_bool, offsetof(Unit, transient), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Perpetual", "b", bus_property_get_bool, offsetof(Unit, perpetual), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("StartLimitIntervalSec", "t", bus_property_get_usec, offsetof(Unit, start_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("StartLimitInterval", "t", bus_property_get_usec, offsetof(Unit, start_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), /* obsolete alias name */ SD_BUS_PROPERTY("StartLimitBurst", "u", bus_property_get_unsigned, offsetof(Unit, start_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("StartLimitAction", "s", property_get_failure_action, offsetof(Unit, start_limit_action), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("StartLimitAction", "s", property_get_emergency_action, offsetof(Unit, start_limit_action), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RebootArgument", "s", NULL, offsetof(Unit, reboot_arg), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("InvocationID", "ay", bus_property_get_id128, offsetof(Unit, invocation_id), 0), SD_BUS_METHOD("Start", "s", "o", method_start, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("Stop", "s", "o", method_stop, SD_BUS_VTABLE_UNPRIVILEGED), @@ -720,7 +774,15 @@ const sd_bus_vtable bus_unit_vtable[] = { SD_BUS_METHOD("Kill", "si", NULL, bus_unit_method_kill, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("ResetFailed", NULL, NULL, bus_unit_method_reset_failed, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("SetProperties", "ba(sv)", NULL, bus_unit_method_set_properties, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Ref", NULL, NULL, bus_unit_method_ref, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Unref", NULL, NULL, bus_unit_method_unref, SD_BUS_VTABLE_UNPRIVILEGED), + /* Obsolete properties or obsolete alias names */ + SD_BUS_PROPERTY("RequiresOverridable", "as", property_get_obsolete_dependencies, 0, SD_BUS_VTABLE_HIDDEN), + SD_BUS_PROPERTY("RequisiteOverridable", "as", property_get_obsolete_dependencies, 0, SD_BUS_VTABLE_HIDDEN), + SD_BUS_PROPERTY("RequiredByOverridable", "as", property_get_obsolete_dependencies, 0, SD_BUS_VTABLE_HIDDEN), + SD_BUS_PROPERTY("RequisiteOfOverridable", "as", property_get_obsolete_dependencies, 0, SD_BUS_VTABLE_HIDDEN), + SD_BUS_PROPERTY("StartLimitInterval", "t", bus_property_get_usec, offsetof(Unit, start_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), SD_BUS_VTABLE_END }; @@ -1104,7 +1166,7 @@ void bus_unit_send_removed_signal(Unit *u) { int r; assert(u); - if (!u->sent_dbus_new_signal) + if (!u->sent_dbus_new_signal || u->in_dbus_queue) bus_unit_send_change_signal(u); if (!u->id) @@ -1317,6 +1379,29 @@ static int bus_unit_set_transient_property( return r; return 1; + + } else if (streq(name, "AddRef")) { + + int b; + + /* Why is this called "AddRef" rather than just "Ref", or "Reference"? There's already a "Ref()" method + * on the Unit interface, and it's probably not a good idea to expose a property and a method on the + * same interface (well, strictly speaking AddRef isn't exposed as full property, we just read it for + * transient units, but still). And "References" and "ReferencedBy" is already used as unit reference + * dependency type, hence let's not confuse things with that. + * + * Note that we don't acually add the reference to the bus track. We do that only after the setup of + * the transient unit is complete, so that setting this property multiple times in the same transient + * unit creation call doesn't count as individual references. */ + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) + u->bus_track_add = b; + + return 1; } return 0; @@ -1421,3 +1506,71 @@ int bus_unit_check_load_state(Unit *u, sd_bus_error *error) { return sd_bus_error_set_errnof(error, u->load_error, "Unit %s is not loaded properly: %m.", u->id); } + +static int bus_track_handler(sd_bus_track *t, void *userdata) { + Unit *u = userdata; + + assert(t); + assert(u); + + u->bus_track = sd_bus_track_unref(u->bus_track); /* make sure we aren't called again */ + + unit_add_to_gc_queue(u); + return 0; +} + +static int allocate_bus_track(Unit *u) { + int r; + + assert(u); + + if (u->bus_track) + return 0; + + r = sd_bus_track_new(u->manager->api_bus, &u->bus_track, bus_track_handler, u); + if (r < 0) + return r; + + r = sd_bus_track_set_recursive(u->bus_track, true); + if (r < 0) { + u->bus_track = sd_bus_track_unref(u->bus_track); + return r; + } + + return 0; +} + +int bus_unit_track_add_name(Unit *u, const char *name) { + int r; + + assert(u); + + r = allocate_bus_track(u); + if (r < 0) + return r; + + return sd_bus_track_add_name(u->bus_track, name); +} + +int bus_unit_track_add_sender(Unit *u, sd_bus_message *m) { + int r; + + assert(u); + + r = allocate_bus_track(u); + if (r < 0) + return r; + + return sd_bus_track_add_sender(u->bus_track, m); +} + +int bus_unit_track_remove_sender(Unit *u, sd_bus_message *m) { + assert(u); + + /* If we haven't allocated the bus track object yet, then there's definitely no reference taken yet, return an + * error */ + if (!u->bus_track) + return -EUNATCH; + + return sd_bus_track_remove_sender(u->bus_track, m); +} diff --git a/src/core/dbus-unit.h b/src/core/dbus-unit.h index 4db88dbebc..b280de7a1d 100644 --- a/src/core/dbus-unit.h +++ b/src/core/dbus-unit.h @@ -33,9 +33,15 @@ int bus_unit_method_start_generic(sd_bus_message *message, Unit *u, JobType job_ int bus_unit_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_unit_method_reset_failed(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_unit_queue_job(sd_bus_message *message, Unit *u, JobType type, JobMode mode, bool reload_if_possible, sd_bus_error *error); int bus_unit_set_properties(Unit *u, sd_bus_message *message, UnitSetPropertiesMode mode, bool commit, sd_bus_error *error); int bus_unit_method_set_properties(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_unit_method_get_processes(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_unit_method_ref(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_unit_method_unref(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_unit_queue_job(sd_bus_message *message, Unit *u, JobType type, JobMode mode, bool reload_if_possible, sd_bus_error *error); int bus_unit_check_load_state(Unit *u, sd_bus_error *error); + +int bus_unit_track_add_name(Unit *u, const char *name); +int bus_unit_track_add_sender(Unit *u, sd_bus_message *m); +int bus_unit_track_remove_sender(Unit *u, sd_bus_message *m); diff --git a/src/core/dbus.c b/src/core/dbus.c index 3422a02d68..070974fe66 100644 --- a/src/core/dbus.c +++ b/src/core/dbus.c @@ -964,10 +964,6 @@ static int bus_init_private(Manager *m) { if (m->private_listen_fd >= 0) return 0; - /* We don't need the private socket if we have kdbus */ - if (m->kdbus_fd >= 0) - return 0; - if (MANAGER_IS_SYSTEM(m)) { /* We want the private bus only when running as init */ @@ -1168,60 +1164,57 @@ int bus_foreach_bus( return ret; } -void bus_track_serialize(sd_bus_track *t, FILE *f) { +void bus_track_serialize(sd_bus_track *t, FILE *f, const char *prefix) { const char *n; assert(f); + assert(prefix); - for (n = sd_bus_track_first(t); n; n = sd_bus_track_next(t)) - fprintf(f, "subscribed=%s\n", n); -} + for (n = sd_bus_track_first(t); n; n = sd_bus_track_next(t)) { + int c, j; -int bus_track_deserialize_item(char ***l, const char *line) { - const char *e; - int r; - - assert(l); - assert(line); - - e = startswith(line, "subscribed="); - if (!e) - return 0; + c = sd_bus_track_count_name(t, n); - r = strv_extend(l, e); - if (r < 0) - return r; - - return 1; + for (j = 0; j < c; j++) { + fputs(prefix, f); + fputc('=', f); + fputs(n, f); + fputc('\n', f); + } + } } -int bus_track_coldplug(Manager *m, sd_bus_track **t, char ***l) { +int bus_track_coldplug(Manager *m, sd_bus_track **t, bool recursive, char **l) { + char **i; int r = 0; assert(m); assert(t); - assert(l); - if (!strv_isempty(*l) && m->api_bus) { - char **i; - - if (!*t) { - r = sd_bus_track_new(m->api_bus, t, NULL, NULL); - if (r < 0) - return r; - } + if (strv_isempty(l)) + return 0; - r = 0; - STRV_FOREACH(i, *l) { - int k; + if (!m->api_bus) + return 0; - k = sd_bus_track_add_name(*t, *i); - if (k < 0) - r = k; - } + if (!*t) { + r = sd_bus_track_new(m->api_bus, t, NULL, NULL); + if (r < 0) + return r; } - *l = strv_free(*l); + r = sd_bus_track_set_recursive(*t, recursive); + if (r < 0) + return r; + + r = 0; + STRV_FOREACH(i, l) { + int k; + + k = sd_bus_track_add_name(*t, *i); + if (k < 0) + r = k; + } return r; } diff --git a/src/core/dbus.h b/src/core/dbus.h index 6baaffbd75..a092ed9d76 100644 --- a/src/core/dbus.h +++ b/src/core/dbus.h @@ -28,9 +28,8 @@ void bus_done(Manager *m); int bus_fdset_add_all(Manager *m, FDSet *fds); -void bus_track_serialize(sd_bus_track *t, FILE *f); -int bus_track_deserialize_item(char ***l, const char *line); -int bus_track_coldplug(Manager *m, sd_bus_track **t, char ***l); +void bus_track_serialize(sd_bus_track *t, FILE *f, const char *prefix); +int bus_track_coldplug(Manager *m, sd_bus_track **t, bool recursive, char **l); int manager_sync_bus_names(Manager *m, sd_bus *bus); diff --git a/src/core/device.c b/src/core/device.c index 16e56efcc3..4b9e84aeb6 100644 --- a/src/core/device.c +++ b/src/core/device.c @@ -331,11 +331,7 @@ static int device_setup_unit(Manager *m, struct udev_device *dev, const char *pa if (!u) { delete = true; - u = unit_new(m, sizeof(Device)); - if (!u) - return log_oom(); - - r = unit_add_name(u, e); + r = unit_new_for_name(m, sizeof(Device), e, &u); if (r < 0) goto fail; @@ -369,7 +365,7 @@ static int device_setup_unit(Manager *m, struct udev_device *dev, const char *pa fail: log_unit_warning_errno(u, r, "Failed to set up device unit: %m"); - if (delete) + if (delete && u) unit_free(u); return r; @@ -464,6 +460,10 @@ static void device_update_found_one(Device *d, bool add, DeviceFound found, bool if (!now) return; + /* Didn't exist before, but does now? if so, generate a new invocation ID for it */ + if (previous == DEVICE_NOT_FOUND && d->found != DEVICE_NOT_FOUND) + (void) unit_acquire_invocation_id(UNIT(d)); + if (d->found & DEVICE_FOUND_UDEV) /* When the device is known to udev we consider it * plugged. */ diff --git a/src/core/dynamic-user.c b/src/core/dynamic-user.c new file mode 100644 index 0000000000..e1846e1adb --- /dev/null +++ b/src/core/dynamic-user.c @@ -0,0 +1,794 @@ +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <grp.h> +#include <pwd.h> +#include <sys/file.h> + +#include "dynamic-user.h" +#include "fd-util.h" +#include "fs-util.h" +#include "parse-util.h" +#include "random-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "user-util.h" +#include "fileio.h" + +/* Takes a value generated randomly or by hashing and turns it into a UID in the right range */ +#define UID_CLAMP_INTO_RANGE(rnd) (((uid_t) (rnd) % (DYNAMIC_UID_MAX - DYNAMIC_UID_MIN + 1)) + DYNAMIC_UID_MIN) + +static DynamicUser* dynamic_user_free(DynamicUser *d) { + if (!d) + return NULL; + + if (d->manager) + (void) hashmap_remove(d->manager->dynamic_users, d->name); + + safe_close_pair(d->storage_socket); + return mfree(d); +} + +static int dynamic_user_add(Manager *m, const char *name, int storage_socket[2], DynamicUser **ret) { + DynamicUser *d = NULL; + int r; + + assert(m); + assert(name); + assert(storage_socket); + + r = hashmap_ensure_allocated(&m->dynamic_users, &string_hash_ops); + if (r < 0) + return r; + + d = malloc0(offsetof(DynamicUser, name) + strlen(name) + 1); + if (!d) + return -ENOMEM; + + strcpy(d->name, name); + + d->storage_socket[0] = storage_socket[0]; + d->storage_socket[1] = storage_socket[1]; + + r = hashmap_put(m->dynamic_users, d->name, d); + if (r < 0) { + free(d); + return r; + } + + d->manager = m; + + if (ret) + *ret = d; + + return 0; +} + +int dynamic_user_acquire(Manager *m, const char *name, DynamicUser** ret) { + _cleanup_close_pair_ int storage_socket[2] = { -1, -1 }; + DynamicUser *d; + int r; + + assert(m); + assert(name); + + /* Return the DynamicUser structure for a specific user name. Note that this won't actually allocate a UID for + * it, but just prepare the data structure for it. The UID is allocated only on demand, when it's really + * needed, and in the child process we fork off, since allocation involves NSS checks which are not OK to do + * from PID 1. To allow the children and PID 1 share information about allocated UIDs we use an anonymous + * AF_UNIX/SOCK_DGRAM socket (called the "storage socket") that contains at most one datagram with the + * allocated UID number, plus an fd referencing the lock file for the UID + * (i.e. /run/systemd/dynamic-uid/$UID). Why involve the socket pair? So that PID 1 and all its children can + * share the same storage for the UID and lock fd, simply by inheriting the storage socket fds. The socket pair + * may exist in three different states: + * + * a) no datagram stored. This is the initial state. In this case the dynamic user was never realized. + * + * b) a datagram containing a UID stored, but no lock fd attached to it. In this case there was already a + * statically assigned UID by the same name, which we are reusing. + * + * c) a datagram containing a UID stored, and a lock fd is attached to it. In this case we allocated a dynamic + * UID and locked it in the file system, using the lock fd. + * + * As PID 1 and various children might access the socket pair simultaneously, and pop the datagram or push it + * back in any time, we also maintain a lock on the socket pair. Note one peculiarity regarding locking here: + * the UID lock on disk is protected via a BSD file lock (i.e. an fd-bound lock), so that the lock is kept in + * place as long as there's a reference to the fd open. The lock on the storage socket pair however is a POSIX + * file lock (i.e. a process-bound lock), as all users share the same fd of this (after all it is anonymous, + * nobody else could get any access to it except via our own fd) and we want to synchronize access between all + * processes that have access to it. */ + + d = hashmap_get(m->dynamic_users, name); + if (d) { + /* We already have a structure for the dynamic user, let's increase the ref count and reuse it */ + d->n_ref++; + *ret = d; + return 0; + } + + if (!valid_user_group_name_or_id(name)) + return -EINVAL; + + if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, storage_socket) < 0) + return -errno; + + r = dynamic_user_add(m, name, storage_socket, &d); + if (r < 0) + return r; + + storage_socket[0] = storage_socket[1] = -1; + + if (ret) { + d->n_ref++; + *ret = d; + } + + return 1; +} + +static int make_uid_symlinks(uid_t uid, const char *name, bool b) { + + char path1[strlen("/run/systemd/dynamic-uid/direct:") + DECIMAL_STR_MAX(uid_t) + 1]; + const char *path2; + int r = 0, k; + + /* Add direct additional symlinks for direct lookups of dynamic UIDs and their names by userspace code. The + * only reason we have this is because dbus-daemon cannot use D-Bus for resolving users and groups (since it + * would be its own client then). We hence keep these world-readable symlinks in place, so that the + * unprivileged dbus user can read the mappings when it needs them via these symlinks instead of having to go + * via the bus. Ideally, we'd use the lock files we keep for this anyway, but we can't since we use BSD locks + * on them and as those may be taken by any user with read access we can't make them world-readable. */ + + xsprintf(path1, "/run/systemd/dynamic-uid/direct:" UID_FMT, uid); + if (unlink(path1) < 0 && errno != ENOENT) + r = -errno; + + if (b && symlink(name, path1) < 0) { + k = log_warning_errno(errno, "Failed to symlink \"%s\": %m", path1); + if (r == 0) + r = k; + } + + path2 = strjoina("/run/systemd/dynamic-uid/direct:", name); + if (unlink(path2) < 0 && errno != ENOENT) { + k = -errno; + if (r == 0) + r = k; + } + + if (b && symlink(path1 + strlen("/run/systemd/dynamic-uid/direct:"), path2) < 0) { + k = log_warning_errno(errno, "Failed to symlink \"%s\": %m", path2); + if (r == 0) + r = k; + } + + return r; +} + +static int pick_uid(const char *name, uid_t *ret_uid) { + + static const uint8_t hash_key[] = { + 0x37, 0x53, 0x7e, 0x31, 0xcf, 0xce, 0x48, 0xf5, + 0x8a, 0xbb, 0x39, 0x57, 0x8d, 0xd9, 0xec, 0x59 + }; + + unsigned n_tries = 100; + uid_t candidate; + int r; + + /* A static user by this name does not exist yet. Let's find a free ID then, and use that. We start with a UID + * generated as hash from the user name. */ + candidate = UID_CLAMP_INTO_RANGE(siphash24(name, strlen(name), hash_key)); + + (void) mkdir("/run/systemd/dynamic-uid", 0755); + + for (;;) { + char lock_path[strlen("/run/systemd/dynamic-uid/") + DECIMAL_STR_MAX(uid_t) + 1]; + _cleanup_close_ int lock_fd = -1; + ssize_t l; + + if (--n_tries <= 0) /* Give up retrying eventually */ + return -EBUSY; + + if (!uid_is_dynamic(candidate)) + goto next; + + xsprintf(lock_path, "/run/systemd/dynamic-uid/" UID_FMT, candidate); + + for (;;) { + struct stat st; + + lock_fd = open(lock_path, O_CREAT|O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NOCTTY, 0600); + if (lock_fd < 0) + return -errno; + + r = flock(lock_fd, LOCK_EX|LOCK_NB); /* Try to get a BSD file lock on the UID lock file */ + if (r < 0) { + if (errno == EBUSY || errno == EAGAIN) + goto next; /* already in use */ + + return -errno; + } + + if (fstat(lock_fd, &st) < 0) + return -errno; + if (st.st_nlink > 0) + break; + + /* Oh, bummer, we got the lock, but the file was unlinked between the time we opened it and + * got the lock. Close it, and try again. */ + lock_fd = safe_close(lock_fd); + } + + /* Some superficial check whether this UID/GID might already be taken by some static user */ + if (getpwuid(candidate) || getgrgid((gid_t) candidate)) { + (void) unlink(lock_path); + goto next; + } + + /* Let's store the user name in the lock file, so that we can use it for looking up the username for a UID */ + l = pwritev(lock_fd, + (struct iovec[2]) { + { .iov_base = (char*) name, .iov_len = strlen(name) }, + { .iov_base = (char[1]) { '\n' }, .iov_len = 1 } + }, 2, 0); + if (l < 0) { + (void) unlink(lock_path); + return -errno; + } + + (void) ftruncate(lock_fd, l); + (void) make_uid_symlinks(candidate, name, true); /* also add direct lookup symlinks */ + + *ret_uid = candidate; + r = lock_fd; + lock_fd = -1; + + return r; + + next: + /* Pick another random UID, and see if that works for us. */ + random_bytes(&candidate, sizeof(candidate)); + candidate = UID_CLAMP_INTO_RANGE(candidate); + } +} + +static int dynamic_user_pop(DynamicUser *d, uid_t *ret_uid, int *ret_lock_fd) { + uid_t uid = UID_INVALID; + struct iovec iov = { + .iov_base = &uid, + .iov_len = sizeof(uid), + }; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int))]; + } control = {}; + struct msghdr mh = { + .msg_control = &control, + .msg_controllen = sizeof(control), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + struct cmsghdr *cmsg; + + ssize_t k; + int lock_fd = -1; + + assert(d); + assert(ret_uid); + assert(ret_lock_fd); + + /* Read the UID and lock fd that is stored in the storage AF_UNIX socket. This should be called with the lock + * on the socket taken. */ + + k = recvmsg(d->storage_socket[0], &mh, MSG_DONTWAIT|MSG_NOSIGNAL|MSG_CMSG_CLOEXEC); + if (k < 0) + return -errno; + + cmsg = cmsg_find(&mh, SOL_SOCKET, SCM_RIGHTS, CMSG_LEN(sizeof(int))); + if (cmsg) + lock_fd = *(int*) CMSG_DATA(cmsg); + else + cmsg_close_all(&mh); /* just in case... */ + + *ret_uid = uid; + *ret_lock_fd = lock_fd; + + return 0; +} + +static int dynamic_user_push(DynamicUser *d, uid_t uid, int lock_fd) { + struct iovec iov = { + .iov_base = &uid, + .iov_len = sizeof(uid), + }; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int))]; + } control = {}; + struct msghdr mh = { + .msg_control = &control, + .msg_controllen = sizeof(control), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + ssize_t k; + + assert(d); + + /* Store the UID and lock_fd in the storage socket. This should be called with the socket pair lock taken. */ + + if (lock_fd >= 0) { + struct cmsghdr *cmsg; + + cmsg = CMSG_FIRSTHDR(&mh); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + memcpy(CMSG_DATA(cmsg), &lock_fd, sizeof(int)); + + mh.msg_controllen = CMSG_SPACE(sizeof(int)); + } else { + mh.msg_control = NULL; + mh.msg_controllen = 0; + } + + k = sendmsg(d->storage_socket[1], &mh, MSG_DONTWAIT|MSG_NOSIGNAL); + if (k < 0) + return -errno; + + return 0; +} + +static void unlink_uid_lock(int lock_fd, uid_t uid, const char *name) { + char lock_path[strlen("/run/systemd/dynamic-uid/") + DECIMAL_STR_MAX(uid_t) + 1]; + + if (lock_fd < 0) + return; + + xsprintf(lock_path, "/run/systemd/dynamic-uid/" UID_FMT, uid); + (void) unlink(lock_path); + + (void) make_uid_symlinks(uid, name, false); /* remove direct lookup symlinks */ +} + +int dynamic_user_realize(DynamicUser *d, uid_t *ret) { + + _cleanup_close_ int etc_passwd_lock_fd = -1, uid_lock_fd = -1; + uid_t uid = UID_INVALID; + int r; + + assert(d); + + /* Acquire a UID for the user name. This will allocate a UID for the user name if the user doesn't exist + * yet. If it already exists its existing UID/GID will be reused. */ + + if (lockf(d->storage_socket[0], F_LOCK, 0) < 0) + return -errno; + + r = dynamic_user_pop(d, &uid, &uid_lock_fd); + if (r < 0) { + int new_uid_lock_fd; + uid_t new_uid; + + if (r != -EAGAIN) + goto finish; + + /* OK, nothing stored yet, let's try to find something useful. While we are working on this release the + * lock however, so that nobody else blocks on our NSS lookups. */ + (void) lockf(d->storage_socket[0], F_ULOCK, 0); + + /* Let's see if a proper, static user or group by this name exists. Try to take the lock on + * /etc/passwd, if that fails with EROFS then /etc is read-only. In that case it's fine if we don't + * take the lock, given that users can't be added there anyway in this case. */ + etc_passwd_lock_fd = take_etc_passwd_lock(NULL); + if (etc_passwd_lock_fd < 0 && etc_passwd_lock_fd != -EROFS) + return etc_passwd_lock_fd; + + /* First, let's parse this as numeric UID */ + r = parse_uid(d->name, &uid); + if (r < 0) { + struct passwd *p; + struct group *g; + + /* OK, this is not a numeric UID. Let's see if there's a user by this name */ + p = getpwnam(d->name); + if (p) + uid = p->pw_uid; + + /* Let's see if there's a group by this name */ + g = getgrnam(d->name); + if (g) { + /* If the UID/GID of the user/group of the same don't match, refuse operation */ + if (uid != UID_INVALID && uid != (uid_t) g->gr_gid) + return -EILSEQ; + + uid = (uid_t) g->gr_gid; + } + } + + if (uid == UID_INVALID) { + /* No static UID assigned yet, excellent. Let's pick a new dynamic one, and lock it. */ + + uid_lock_fd = pick_uid(d->name, &uid); + if (uid_lock_fd < 0) + return uid_lock_fd; + } + + /* So, we found a working UID/lock combination. Let's see if we actually still need it. */ + if (lockf(d->storage_socket[0], F_LOCK, 0) < 0) { + unlink_uid_lock(uid_lock_fd, uid, d->name); + return -errno; + } + + r = dynamic_user_pop(d, &new_uid, &new_uid_lock_fd); + if (r < 0) { + if (r != -EAGAIN) { + /* OK, something bad happened, let's get rid of the bits we acquired. */ + unlink_uid_lock(uid_lock_fd, uid, d->name); + goto finish; + } + + /* Great! Nothing is stored here, still. Store our newly acquired data. */ + } else { + /* Hmm, so as it appears there's now something stored in the storage socket. Throw away what we + * acquired, and use what's stored now. */ + + unlink_uid_lock(uid_lock_fd, uid, d->name); + safe_close(uid_lock_fd); + + uid = new_uid; + uid_lock_fd = new_uid_lock_fd; + } + } + + /* If the UID/GID was already allocated dynamically, push the data we popped out back in. If it was already + * allocated statically, push the UID back too, but do not push the lock fd in. If we allocated the UID + * dynamically right here, push that in along with the lock fd for it. */ + r = dynamic_user_push(d, uid, uid_lock_fd); + if (r < 0) + goto finish; + + *ret = uid; + r = 0; + +finish: + (void) lockf(d->storage_socket[0], F_ULOCK, 0); + return r; +} + +int dynamic_user_current(DynamicUser *d, uid_t *ret) { + _cleanup_close_ int lock_fd = -1; + uid_t uid; + int r; + + assert(d); + assert(ret); + + /* Get the currently assigned UID for the user, if there's any. This simply pops the data from the storage socket, and pushes it back in right-away. */ + + if (lockf(d->storage_socket[0], F_LOCK, 0) < 0) + return -errno; + + r = dynamic_user_pop(d, &uid, &lock_fd); + if (r < 0) + goto finish; + + r = dynamic_user_push(d, uid, lock_fd); + if (r < 0) + goto finish; + + *ret = uid; + r = 0; + +finish: + (void) lockf(d->storage_socket[0], F_ULOCK, 0); + return r; +} + +DynamicUser* dynamic_user_ref(DynamicUser *d) { + if (!d) + return NULL; + + assert(d->n_ref > 0); + d->n_ref++; + + return d; +} + +DynamicUser* dynamic_user_unref(DynamicUser *d) { + if (!d) + return NULL; + + /* Note that this doesn't actually release any resources itself. If a dynamic user should be fully destroyed + * and its UID released, use dynamic_user_destroy() instead. NB: the dynamic user table may contain entries + * with no references, which is commonly the case right before a daemon reload. */ + + assert(d->n_ref > 0); + d->n_ref--; + + return NULL; +} + +static int dynamic_user_close(DynamicUser *d) { + _cleanup_close_ int lock_fd = -1; + uid_t uid; + int r; + + /* Release the user ID, by releasing the lock on it, and emptying the storage socket. After this the user is + * unrealized again, much like it was after it the DynamicUser object was first allocated. */ + + if (lockf(d->storage_socket[0], F_LOCK, 0) < 0) + return -errno; + + r = dynamic_user_pop(d, &uid, &lock_fd); + if (r == -EAGAIN) { + /* User wasn't realized yet, nothing to do. */ + r = 0; + goto finish; + } + if (r < 0) + goto finish; + + /* This dynamic user was realized and dynamically allocated. In this case, let's remove the lock file. */ + unlink_uid_lock(lock_fd, uid, d->name); + r = 1; + +finish: + (void) lockf(d->storage_socket[0], F_ULOCK, 0); + return r; +} + +DynamicUser* dynamic_user_destroy(DynamicUser *d) { + if (!d) + return NULL; + + /* Drop a reference to a DynamicUser object, and destroy the user completely if this was the last + * reference. This is called whenever a service is shut down and wants its dynamic UID gone. Note that + * dynamic_user_unref() is what is called whenever a service is simply freed, for example during a reload + * cycle, where the dynamic users should not be destroyed, but our datastructures should. */ + + dynamic_user_unref(d); + + if (d->n_ref > 0) + return NULL; + + (void) dynamic_user_close(d); + return dynamic_user_free(d); +} + +int dynamic_user_serialize(Manager *m, FILE *f, FDSet *fds) { + DynamicUser *d; + Iterator i; + + assert(m); + assert(f); + assert(fds); + + /* Dump the dynamic user database into the manager serialization, to deal with daemon reloads. */ + + HASHMAP_FOREACH(d, m->dynamic_users, i) { + int copy0, copy1; + + copy0 = fdset_put_dup(fds, d->storage_socket[0]); + if (copy0 < 0) + return copy0; + + copy1 = fdset_put_dup(fds, d->storage_socket[1]); + if (copy1 < 0) + return copy1; + + fprintf(f, "dynamic-user=%s %i %i\n", d->name, copy0, copy1); + } + + return 0; +} + +void dynamic_user_deserialize_one(Manager *m, const char *value, FDSet *fds) { + _cleanup_free_ char *name = NULL, *s0 = NULL, *s1 = NULL; + int r, fd0, fd1; + + assert(m); + assert(value); + assert(fds); + + /* Parse the serialization again, after a daemon reload */ + + r = extract_many_words(&value, NULL, 0, &name, &s0, &s1, NULL); + if (r != 3 || !isempty(value)) { + log_debug("Unable to parse dynamic user line."); + return; + } + + if (safe_atoi(s0, &fd0) < 0 || !fdset_contains(fds, fd0)) { + log_debug("Unable to process dynamic user fd specification."); + return; + } + + if (safe_atoi(s1, &fd1) < 0 || !fdset_contains(fds, fd1)) { + log_debug("Unable to process dynamic user fd specification."); + return; + } + + r = dynamic_user_add(m, name, (int[]) { fd0, fd1 }, NULL); + if (r < 0) { + log_debug_errno(r, "Failed to add dynamic user: %m"); + return; + } + + (void) fdset_remove(fds, fd0); + (void) fdset_remove(fds, fd1); +} + +void dynamic_user_vacuum(Manager *m, bool close_user) { + DynamicUser *d; + Iterator i; + + assert(m); + + /* Empty the dynamic user database, optionally cleaning up orphaned dynamic users, i.e. destroy and free users + * to which no reference exist. This is called after a daemon reload finished, in order to destroy users which + * might not be referenced anymore. */ + + HASHMAP_FOREACH(d, m->dynamic_users, i) { + if (d->n_ref > 0) + continue; + + if (close_user) { + log_debug("Removing orphaned dynamic user %s", d->name); + (void) dynamic_user_close(d); + } + + dynamic_user_free(d); + } +} + +int dynamic_user_lookup_uid(Manager *m, uid_t uid, char **ret) { + char lock_path[strlen("/run/systemd/dynamic-uid/") + DECIMAL_STR_MAX(uid_t) + 1]; + _cleanup_free_ char *user = NULL; + uid_t check_uid; + int r; + + assert(m); + assert(ret); + + /* A friendly way to translate a dynamic user's UID into a name. */ + if (!uid_is_dynamic(uid)) + return -ESRCH; + + xsprintf(lock_path, "/run/systemd/dynamic-uid/" UID_FMT, uid); + r = read_one_line_file(lock_path, &user); + if (r == -ENOENT) + return -ESRCH; + if (r < 0) + return r; + + /* The lock file might be stale, hence let's verify the data before we return it */ + r = dynamic_user_lookup_name(m, user, &check_uid); + if (r < 0) + return r; + if (check_uid != uid) /* lock file doesn't match our own idea */ + return -ESRCH; + + *ret = user; + user = NULL; + + return 0; +} + +int dynamic_user_lookup_name(Manager *m, const char *name, uid_t *ret) { + DynamicUser *d; + int r; + + assert(m); + assert(name); + assert(ret); + + /* A friendly call for translating a dynamic user's name into its UID */ + + d = hashmap_get(m->dynamic_users, name); + if (!d) + return -ESRCH; + + r = dynamic_user_current(d, ret); + if (r == -EAGAIN) /* not realized yet? */ + return -ESRCH; + + return r; +} + +int dynamic_creds_acquire(DynamicCreds *creds, Manager *m, const char *user, const char *group) { + bool acquired = false; + int r; + + assert(creds); + assert(m); + + /* A DynamicUser object encapsulates an allocation of both a UID and a GID for a specific name. However, some + * services use different user and groups. For cases like that there's DynamicCreds containing a pair of user + * and group. This call allocates a pair. */ + + if (!creds->user && user) { + r = dynamic_user_acquire(m, user, &creds->user); + if (r < 0) + return r; + + acquired = true; + } + + if (!creds->group) { + + if (creds->user && (!group || streq_ptr(user, group))) + creds->group = dynamic_user_ref(creds->user); + else { + r = dynamic_user_acquire(m, group, &creds->group); + if (r < 0) { + if (acquired) + creds->user = dynamic_user_unref(creds->user); + return r; + } + } + } + + return 0; +} + +int dynamic_creds_realize(DynamicCreds *creds, uid_t *uid, gid_t *gid) { + uid_t u = UID_INVALID; + gid_t g = GID_INVALID; + int r; + + assert(creds); + assert(uid); + assert(gid); + + /* Realize both the referenced user and group */ + + if (creds->user) { + r = dynamic_user_realize(creds->user, &u); + if (r < 0) + return r; + } + + if (creds->group && creds->group != creds->user) { + r = dynamic_user_realize(creds->group, &g); + if (r < 0) + return r; + } else + g = u; + + *uid = u; + *gid = g; + + return 0; +} + +void dynamic_creds_unref(DynamicCreds *creds) { + assert(creds); + + creds->user = dynamic_user_unref(creds->user); + creds->group = dynamic_user_unref(creds->group); +} + +void dynamic_creds_destroy(DynamicCreds *creds) { + assert(creds); + + creds->user = dynamic_user_destroy(creds->user); + creds->group = dynamic_user_destroy(creds->group); +} diff --git a/src/core/dynamic-user.h b/src/core/dynamic-user.h new file mode 100644 index 0000000000..0b8bce1a72 --- /dev/null +++ b/src/core/dynamic-user.h @@ -0,0 +1,66 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +typedef struct DynamicUser DynamicUser; + +typedef struct DynamicCreds { + /* A combination of a dynamic user and group */ + DynamicUser *user; + DynamicUser *group; +} DynamicCreds; + +#include "manager.h" + +/* Note that this object always allocates a pair of user and group under the same name, even if one of them isn't + * used. This means, if you want to allocate a group and user pair, and they might have two different names, then you + * need to allocated two of these objects. DynamicCreds below makes that easy. */ +struct DynamicUser { + int n_ref; + Manager *manager; + + /* An AF_UNIX socket pair that contains a datagram containing both the numeric ID assigned, as well as a lock + * file fd locking the user ID we picked. */ + int storage_socket[2]; + + char name[]; +}; + +int dynamic_user_acquire(Manager *m, const char *name, DynamicUser **ret); + +int dynamic_user_realize(DynamicUser *d, uid_t *ret); +int dynamic_user_current(DynamicUser *d, uid_t *ret); + +DynamicUser* dynamic_user_ref(DynamicUser *d); +DynamicUser* dynamic_user_unref(DynamicUser *d); +DynamicUser* dynamic_user_destroy(DynamicUser *d); + +int dynamic_user_serialize(Manager *m, FILE *f, FDSet *fds); +void dynamic_user_deserialize_one(Manager *m, const char *value, FDSet *fds); +void dynamic_user_vacuum(Manager *m, bool close_user); + +int dynamic_user_lookup_uid(Manager *m, uid_t uid, char **ret); +int dynamic_user_lookup_name(Manager *m, const char *name, uid_t *ret); + +int dynamic_creds_acquire(DynamicCreds *creds, Manager *m, const char *user, const char *group); +int dynamic_creds_realize(DynamicCreds *creds, uid_t *uid, gid_t *gid); + +void dynamic_creds_unref(DynamicCreds *creds); +void dynamic_creds_destroy(DynamicCreds *creds); diff --git a/src/core/failure-action.c b/src/core/emergency-action.c index ddae46190f..90232bc57a 100644 --- a/src/core/failure-action.c +++ b/src/core/emergency-action.c @@ -23,59 +23,60 @@ #include "bus-error.h" #include "bus-util.h" -#include "failure-action.h" +#include "emergency-action.h" #include "special.h" #include "string-table.h" #include "terminal-util.h" -static void log_and_status(Manager *m, const char *message) { - log_warning("%s", message); +static void log_and_status(Manager *m, const char *message, const char *reason) { + log_warning("%s: %s", message, reason); manager_status_printf(m, STATUS_TYPE_EMERGENCY, ANSI_HIGHLIGHT_RED " !! " ANSI_NORMAL, - "%s", message); + "%s: %s", message, reason); } -int failure_action( +int emergency_action( Manager *m, - FailureAction action, - const char *reboot_arg) { + EmergencyAction action, + const char *reboot_arg, + const char *reason) { assert(m); assert(action >= 0); - assert(action < _FAILURE_ACTION_MAX); + assert(action < _EMERGENCY_ACTION_MAX); - if (action == FAILURE_ACTION_NONE) + if (action == EMERGENCY_ACTION_NONE) return -ECANCELED; if (!MANAGER_IS_SYSTEM(m)) { /* Downgrade all options to simply exiting if we run * in user mode */ - log_warning("Exiting as result of failure."); + log_warning("Exiting: %s", reason); m->exit_code = MANAGER_EXIT; return -ECANCELED; } switch (action) { - case FAILURE_ACTION_REBOOT: - log_and_status(m, "Rebooting as result of failure."); + case EMERGENCY_ACTION_REBOOT: + log_and_status(m, "Rebooting", reason); (void) update_reboot_parameter_and_warn(reboot_arg); (void) manager_add_job_by_name_and_warn(m, JOB_START, SPECIAL_REBOOT_TARGET, JOB_REPLACE_IRREVERSIBLY, NULL); break; - case FAILURE_ACTION_REBOOT_FORCE: - log_and_status(m, "Forcibly rebooting as result of failure."); + case EMERGENCY_ACTION_REBOOT_FORCE: + log_and_status(m, "Forcibly rebooting", reason); (void) update_reboot_parameter_and_warn(reboot_arg); m->exit_code = MANAGER_REBOOT; break; - case FAILURE_ACTION_REBOOT_IMMEDIATE: - log_and_status(m, "Rebooting immediately as result of failure."); + case EMERGENCY_ACTION_REBOOT_IMMEDIATE: + log_and_status(m, "Rebooting immediately", reason); sync(); @@ -89,18 +90,18 @@ int failure_action( reboot(RB_AUTOBOOT); break; - case FAILURE_ACTION_POWEROFF: - log_and_status(m, "Powering off as result of failure."); + case EMERGENCY_ACTION_POWEROFF: + log_and_status(m, "Powering off", reason); (void) manager_add_job_by_name_and_warn(m, JOB_START, SPECIAL_POWEROFF_TARGET, JOB_REPLACE_IRREVERSIBLY, NULL); break; - case FAILURE_ACTION_POWEROFF_FORCE: - log_and_status(m, "Forcibly powering off as result of failure."); + case EMERGENCY_ACTION_POWEROFF_FORCE: + log_and_status(m, "Forcibly powering off", reason); m->exit_code = MANAGER_POWEROFF; break; - case FAILURE_ACTION_POWEROFF_IMMEDIATE: - log_and_status(m, "Powering off immediately as result of failure."); + case EMERGENCY_ACTION_POWEROFF_IMMEDIATE: + log_and_status(m, "Powering off immediately", reason); sync(); @@ -109,19 +110,19 @@ int failure_action( break; default: - assert_not_reached("Unknown failure action"); + assert_not_reached("Unknown emergency action"); } return -ECANCELED; } -static const char* const failure_action_table[_FAILURE_ACTION_MAX] = { - [FAILURE_ACTION_NONE] = "none", - [FAILURE_ACTION_REBOOT] = "reboot", - [FAILURE_ACTION_REBOOT_FORCE] = "reboot-force", - [FAILURE_ACTION_REBOOT_IMMEDIATE] = "reboot-immediate", - [FAILURE_ACTION_POWEROFF] = "poweroff", - [FAILURE_ACTION_POWEROFF_FORCE] = "poweroff-force", - [FAILURE_ACTION_POWEROFF_IMMEDIATE] = "poweroff-immediate" +static const char* const emergency_action_table[_EMERGENCY_ACTION_MAX] = { + [EMERGENCY_ACTION_NONE] = "none", + [EMERGENCY_ACTION_REBOOT] = "reboot", + [EMERGENCY_ACTION_REBOOT_FORCE] = "reboot-force", + [EMERGENCY_ACTION_REBOOT_IMMEDIATE] = "reboot-immediate", + [EMERGENCY_ACTION_POWEROFF] = "poweroff", + [EMERGENCY_ACTION_POWEROFF_FORCE] = "poweroff-force", + [EMERGENCY_ACTION_POWEROFF_IMMEDIATE] = "poweroff-immediate" }; -DEFINE_STRING_TABLE_LOOKUP(failure_action, FailureAction); +DEFINE_STRING_TABLE_LOOKUP(emergency_action, EmergencyAction); diff --git a/src/core/failure-action.h b/src/core/emergency-action.h index 1adac4ad5c..8804b59752 100644 --- a/src/core/failure-action.h +++ b/src/core/emergency-action.h @@ -20,22 +20,22 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -typedef enum FailureAction { - FAILURE_ACTION_NONE, - FAILURE_ACTION_REBOOT, - FAILURE_ACTION_REBOOT_FORCE, - FAILURE_ACTION_REBOOT_IMMEDIATE, - FAILURE_ACTION_POWEROFF, - FAILURE_ACTION_POWEROFF_FORCE, - FAILURE_ACTION_POWEROFF_IMMEDIATE, - _FAILURE_ACTION_MAX, - _FAILURE_ACTION_INVALID = -1 -} FailureAction; +typedef enum EmergencyAction { + EMERGENCY_ACTION_NONE, + EMERGENCY_ACTION_REBOOT, + EMERGENCY_ACTION_REBOOT_FORCE, + EMERGENCY_ACTION_REBOOT_IMMEDIATE, + EMERGENCY_ACTION_POWEROFF, + EMERGENCY_ACTION_POWEROFF_FORCE, + EMERGENCY_ACTION_POWEROFF_IMMEDIATE, + _EMERGENCY_ACTION_MAX, + _EMERGENCY_ACTION_INVALID = -1 +} EmergencyAction; #include "macro.h" #include "manager.h" -int failure_action(Manager *m, FailureAction action, const char *reboot_arg); +int emergency_action(Manager *m, EmergencyAction action, const char *reboot_arg, const char *reason); -const char* failure_action_to_string(FailureAction i) _const_; -FailureAction failure_action_from_string(const char *s) _pure_; +const char* emergency_action_to_string(EmergencyAction i) _const_; +EmergencyAction emergency_action_from_string(const char *s) _pure_; diff --git a/src/core/execute.c b/src/core/execute.c index 7c178b97c3..85ee82c3e1 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -25,11 +25,14 @@ #include <signal.h> #include <string.h> #include <sys/capability.h> +#include <sys/eventfd.h> #include <sys/mman.h> #include <sys/personality.h> #include <sys/prctl.h> +#include <sys/shm.h> #include <sys/socket.h> #include <sys/stat.h> +#include <sys/types.h> #include <sys/un.h> #include <unistd.h> #include <utmpx.h> @@ -90,6 +93,7 @@ #include "selinux-util.h" #include "signal-util.h" #include "smack-util.h" +#include "special.h" #include "string-table.h" #include "string-util.h" #include "strv.h" @@ -219,12 +223,36 @@ static void exec_context_tty_reset(const ExecContext *context, const ExecParamet (void) vt_disallocate(path); } +static bool is_terminal_input(ExecInput i) { + return IN_SET(i, + EXEC_INPUT_TTY, + EXEC_INPUT_TTY_FORCE, + EXEC_INPUT_TTY_FAIL); +} + static bool is_terminal_output(ExecOutput o) { - return - o == EXEC_OUTPUT_TTY || - o == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || - o == EXEC_OUTPUT_KMSG_AND_CONSOLE || - o == EXEC_OUTPUT_JOURNAL_AND_CONSOLE; + return IN_SET(o, + EXEC_OUTPUT_TTY, + EXEC_OUTPUT_SYSLOG_AND_CONSOLE, + EXEC_OUTPUT_KMSG_AND_CONSOLE, + EXEC_OUTPUT_JOURNAL_AND_CONSOLE); +} + +static bool exec_context_needs_term(const ExecContext *c) { + assert(c); + + /* Return true if the execution context suggests we should set $TERM to something useful. */ + + if (is_terminal_input(c->std_input)) + return true; + + if (is_terminal_output(c->std_output)) + return true; + + if (is_terminal_output(c->std_error)) + return true; + + return !!c->tty_path; } static int open_null_as(int flags, int nfd) { @@ -363,13 +391,6 @@ static int open_terminal_as(const char *path, mode_t mode, int nfd) { return r; } -static bool is_terminal_input(ExecInput i) { - return - i == EXEC_INPUT_TTY || - i == EXEC_INPUT_TTY_FORCE || - i == EXEC_INPUT_TTY_FAIL; -} - static int fixup_input(ExecInput std_input, int socket_fd, bool apply_tty_stdin) { if (is_terminal_input(std_input) && !apply_tty_stdin) @@ -392,7 +413,8 @@ static int fixup_output(ExecOutput std_output, int socket_fd) { static int setup_input( const ExecContext *context, const ExecParameters *params, - int socket_fd) { + int socket_fd, + int named_iofds[3]) { ExecInput i; @@ -410,7 +432,7 @@ static int setup_input( return STDIN_FILENO; } - i = fixup_input(context->std_input, socket_fd, params->apply_tty_stdin); + i = fixup_input(context->std_input, socket_fd, params->flags & EXEC_APPLY_TTY_STDIN); switch (i) { @@ -442,6 +464,10 @@ static int setup_input( case EXEC_INPUT_SOCKET: return dup2(socket_fd, STDIN_FILENO) < 0 ? -errno : STDIN_FILENO; + case EXEC_INPUT_NAMED_FD: + (void) fd_nonblock(named_iofds[STDIN_FILENO], false); + return dup2(named_iofds[STDIN_FILENO], STDIN_FILENO) < 0 ? -errno : STDIN_FILENO; + default: assert_not_reached("Unknown input type"); } @@ -453,6 +479,7 @@ static int setup_output( const ExecParameters *params, int fileno, int socket_fd, + int named_iofds[3], const char *ident, uid_t uid, gid_t gid, @@ -485,7 +512,7 @@ static int setup_output( return STDERR_FILENO; } - i = fixup_input(context->std_input, socket_fd, params->apply_tty_stdin); + i = fixup_input(context->std_input, socket_fd, params->flags & EXEC_APPLY_TTY_STDIN); o = fixup_output(context->std_output, socket_fd); if (fileno == STDERR_FILENO) { @@ -504,7 +531,7 @@ static int setup_output( return fileno; /* Duplicate from stdout if possible */ - if (e == o || e == EXEC_OUTPUT_INHERIT) + if ((e == o && e != EXEC_OUTPUT_NAMED_FD) || e == EXEC_OUTPUT_INHERIT) return dup2(STDOUT_FILENO, fileno) < 0 ? -errno : fileno; o = e; @@ -566,6 +593,10 @@ static int setup_output( assert(socket_fd >= 0); return dup2(socket_fd, fileno) < 0 ? -errno : fileno; + case EXEC_OUTPUT_NAMED_FD: + (void) fd_nonblock(named_iofds[fileno], false); + return dup2(named_iofds[fileno], fileno) < 0 ? -errno : fileno; + default: assert_not_reached("Unknown error type"); } @@ -701,73 +732,157 @@ static int ask_for_confirmation(char *response, char **argv) { return r; } -static int enforce_groups(const ExecContext *context, const char *username, gid_t gid) { - bool keep_groups = false; +static int get_fixed_user(const ExecContext *c, const char **user, + uid_t *uid, gid_t *gid, + const char **home, const char **shell) { int r; + const char *name; - assert(context); + assert(c); + + if (!c->user) + return 0; + + /* Note that we don't set $HOME or $SHELL if they are not particularly enlightening anyway + * (i.e. are "/" or "/bin/nologin"). */ + + name = c->user; + r = get_user_creds_clean(&name, uid, gid, home, shell); + if (r < 0) + return r; - /* Lookup and set GID and supplementary group list. Here too - * we avoid NSS lookups for gid=0. */ + *user = name; + return 0; +} - if (context->group || username) { +static int get_fixed_group(const ExecContext *c, const char **group, gid_t *gid) { + int r; + const char *name; + + assert(c); + + if (!c->group) + return 0; + + name = c->group; + r = get_group_creds(&name, gid); + if (r < 0) + return r; + + *group = name; + return 0; +} + +static int get_supplementary_groups(const ExecContext *c, const char *user, + const char *group, gid_t gid, + gid_t **supplementary_gids, int *ngids) { + char **i; + int r, k = 0; + int ngroups_max; + bool keep_groups = false; + gid_t *groups = NULL; + _cleanup_free_ gid_t *l_gids = NULL; + + assert(c); + + /* + * If user is given, then lookup GID and supplementary groups list. + * We avoid NSS lookups for gid=0. Also we have to initialize groups + * here and as early as possible so we keep the list of supplementary + * groups of the caller. + */ + if (user && gid_is_valid(gid) && gid != 0) { /* First step, initialize groups from /etc/groups */ - if (username && gid != 0) { - if (initgroups(username, gid) < 0) - return -errno; + if (initgroups(user, gid) < 0) + return -errno; - keep_groups = true; - } + keep_groups = true; + } - /* Second step, set our gids */ - if (setresgid(gid, gid, gid) < 0) + if (!c->supplementary_groups) + return 0; + + /* + * If SupplementaryGroups= was passed then NGROUPS_MAX has to + * be positive, otherwise fail. + */ + errno = 0; + ngroups_max = (int) sysconf(_SC_NGROUPS_MAX); + if (ngroups_max <= 0) { + if (errno > 0) return -errno; + else + return -EOPNOTSUPP; /* For all other values */ } - if (context->supplementary_groups) { - int ngroups_max, k; - gid_t *gids; - char **i; + l_gids = new(gid_t, ngroups_max); + if (!l_gids) + return -ENOMEM; - /* Final step, initialize any manually set supplementary groups */ - assert_se((ngroups_max = (int) sysconf(_SC_NGROUPS_MAX)) > 0); + if (keep_groups) { + /* + * Lookup the list of groups that the user belongs to, we + * avoid NSS lookups here too for gid=0. + */ + k = ngroups_max; + if (getgrouplist(user, gid, l_gids, &k) < 0) + return -EINVAL; + } else + k = 0; - if (!(gids = new(gid_t, ngroups_max))) - return -ENOMEM; + STRV_FOREACH(i, c->supplementary_groups) { + const char *g; - if (keep_groups) { - k = getgroups(ngroups_max, gids); - if (k < 0) { - free(gids); - return -errno; - } - } else - k = 0; + if (k >= ngroups_max) + return -E2BIG; - STRV_FOREACH(i, context->supplementary_groups) { - const char *g; + g = *i; + r = get_group_creds(&g, l_gids+k); + if (r < 0) + return r; - if (k >= ngroups_max) { - free(gids); - return -E2BIG; - } + k++; + } - g = *i; - r = get_group_creds(&g, gids+k); - if (r < 0) { - free(gids); - return r; - } + /* + * Sets ngids to zero to drop all supplementary groups, happens + * when we are under root and SupplementaryGroups= is empty. + */ + if (k == 0) { + *ngids = 0; + return 0; + } - k++; - } + /* Otherwise get the final list of supplementary groups */ + groups = memdup(l_gids, sizeof(gid_t) * k); + if (!groups) + return -ENOMEM; - if (setgroups(k, gids) < 0) { - free(gids); - return -errno; - } + *supplementary_gids = groups; + *ngids = k; + + groups = NULL; + + return 0; +} + +static int enforce_groups(const ExecContext *context, gid_t gid, + gid_t *supplementary_gids, int ngids) { + int r; + + assert(context); + + /* Handle SupplementaryGroups= even if it is empty */ + if (context->supplementary_groups) { + r = maybe_setgroups(ngids, supplementary_gids); + if (r < 0) + return r; + } - free(gids); + if (gid_is_valid(gid)) { + /* Then set our gids */ + if (setresgid(gid, gid, gid) < 0) + return -errno; } return 0; @@ -776,6 +891,9 @@ static int enforce_groups(const ExecContext *context, const char *username, gid_ static int enforce_user(const ExecContext *context, uid_t uid) { assert(context); + if (!uid_is_valid(uid)) + return 0; + /* Sets (but doesn't look up) the uid and make sure we keep the * capabilities while doing so. */ @@ -818,14 +936,19 @@ static int null_conv( return PAM_CONV_ERR; } +#endif + static int setup_pam( const char *name, const char *user, uid_t uid, + gid_t gid, const char *tty, char ***env, int fds[], unsigned n_fds) { +#ifdef HAVE_PAM + static const struct pam_conv conv = { .conv = null_conv, .appdata_ptr = NULL @@ -925,8 +1048,14 @@ static int setup_pam( * and this will make PR_SET_PDEATHSIG work in most cases. * If this fails, ignore the error - but expect sd-pam threads * to fail to exit normally */ + + r = maybe_setgroups(0, NULL); + if (r < 0) + log_warning_errno(r, "Failed to setgroups() in sd-pam: %m"); + if (setresgid(gid, gid, gid) < 0) + log_warning_errno(errno, "Failed to setresgid() in sd-pam: %m"); if (setresuid(uid, uid, uid) < 0) - log_error_errno(r, "Error: Failed to setresuid() in sd-pam: %m"); + log_warning_errno(errno, "Failed to setresuid() in sd-pam: %m"); (void) ignore_signals(SIGPIPE, -1); @@ -1019,8 +1148,10 @@ fail: closelog(); return r; -} +#else + return 0; #endif +} static void rename_process_from_path(const char *path) { char process_name[11]; @@ -1055,15 +1186,29 @@ static void rename_process_from_path(const char *path) { #ifdef HAVE_SECCOMP -static int apply_seccomp(const ExecContext *c) { +static bool skip_seccomp_unavailable(const Unit* u, const char* msg) { + + if (is_seccomp_available()) + return false; + + log_open(); + log_unit_debug(u, "SECCOMP features not detected in the kernel, skipping %s", msg); + log_close(); + return true; +} + +static int apply_seccomp(const Unit* u, const ExecContext *c) { uint32_t negative_action, action; - scmp_filter_ctx *seccomp; + scmp_filter_ctx seccomp; Iterator i; void *id; int r; assert(c); + if (skip_seccomp_unavailable(u, "syscall filtering")) + return 0; + negative_action = c->syscall_errno == 0 ? SCMP_ACT_KILL : SCMP_ACT_ERRNO(c->syscall_errno); seccomp = seccomp_init(c->syscall_whitelist ? negative_action : SCMP_ACT_ALLOW); @@ -1104,20 +1249,23 @@ finish: return r; } -static int apply_address_families(const ExecContext *c) { - scmp_filter_ctx *seccomp; +static int apply_address_families(const Unit* u, const ExecContext *c) { + scmp_filter_ctx seccomp; Iterator i; int r; +#if defined(__i386__) + return 0; +#endif + assert(c); - seccomp = seccomp_init(SCMP_ACT_ALLOW); - if (!seccomp) - return -ENOMEM; + if (skip_seccomp_unavailable(u, "RestrictAddressFamilies=")) + return 0; - r = seccomp_add_secondary_archs(seccomp); + r = seccomp_init_conservative(&seccomp, SCMP_ACT_ALLOW); if (r < 0) - goto finish; + return r; if (c->address_families_whitelist) { int af, first = 0, last = 0; @@ -1214,10 +1362,6 @@ static int apply_address_families(const ExecContext *c) { } } - r = seccomp_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0); - if (r < 0) - goto finish; - r = seccomp_load(seccomp); finish: @@ -1225,15 +1369,18 @@ finish: return r; } -static int apply_memory_deny_write_execute(const ExecContext *c) { - scmp_filter_ctx *seccomp; +static int apply_memory_deny_write_execute(const Unit* u, const ExecContext *c) { + scmp_filter_ctx seccomp; int r; assert(c); - seccomp = seccomp_init(SCMP_ACT_ALLOW); - if (!seccomp) - return -ENOMEM; + if (skip_seccomp_unavailable(u, "MemoryDenyWriteExecute=")) + return 0; + + r = seccomp_init_conservative(&seccomp, SCMP_ACT_ALLOW); + if (r < 0) + return r; r = seccomp_rule_add( seccomp, @@ -1253,7 +1400,12 @@ static int apply_memory_deny_write_execute(const ExecContext *c) { if (r < 0) goto finish; - r = seccomp_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0); + r = seccomp_rule_add( + seccomp, + SCMP_ACT_ERRNO(EPERM), + SCMP_SYS(shmat), + 1, + SCMP_A2(SCMP_CMP_MASKED_EQ, SHM_EXEC, SHM_EXEC)); if (r < 0) goto finish; @@ -1264,22 +1416,25 @@ finish: return r; } -static int apply_restrict_realtime(const ExecContext *c) { +static int apply_restrict_realtime(const Unit* u, const ExecContext *c) { static const int permitted_policies[] = { SCHED_OTHER, SCHED_BATCH, SCHED_IDLE, }; - scmp_filter_ctx *seccomp; + scmp_filter_ctx seccomp; unsigned i; int r, p, max_policy = 0; assert(c); - seccomp = seccomp_init(SCMP_ACT_ALLOW); - if (!seccomp) - return -ENOMEM; + if (skip_seccomp_unavailable(u, "RestrictRealtime=")) + return 0; + + r = seccomp_init_conservative(&seccomp, SCMP_ACT_ALLOW); + if (r < 0) + return r; /* Determine the highest policy constant we want to allow */ for (i = 0; i < ELEMENTSOF(permitted_policies); i++) @@ -1323,7 +1478,34 @@ static int apply_restrict_realtime(const ExecContext *c) { if (r < 0) goto finish; - r = seccomp_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0); + r = seccomp_load(seccomp); + +finish: + seccomp_release(seccomp); + return r; +} + +static int apply_protect_sysctl(const Unit *u, const ExecContext *c) { + scmp_filter_ctx seccomp; + int r; + + assert(c); + + /* Turn off the legacy sysctl() system call. Many distributions turn this off while building the kernel, but + * let's protect even those systems where this is left on in the kernel. */ + + if (skip_seccomp_unavailable(u, "ProtectKernelTunables=")) + return 0; + + r = seccomp_init_conservative(&seccomp, SCMP_ACT_ALLOW); + if (r < 0) + return r; + + r = seccomp_rule_add( + seccomp, + SCMP_ACT_ERRNO(EPERM), + SCMP_SYS(_sysctl), + 0); if (r < 0) goto finish; @@ -1334,12 +1516,33 @@ finish: return r; } +static int apply_protect_kernel_modules(const Unit *u, const ExecContext *c) { + assert(c); + + /* Turn off module syscalls on ProtectKernelModules=yes */ + + if (skip_seccomp_unavailable(u, "ProtectKernelModules=")) + return 0; + + return seccomp_load_filter_set(SCMP_ACT_ALLOW, syscall_filter_sets + SYSCALL_FILTER_SET_MODULE, SCMP_ACT_ERRNO(EPERM)); +} + +static int apply_private_devices(const Unit *u, const ExecContext *c) { + assert(c); + + /* If PrivateDevices= is set, also turn off iopl and all @raw-io syscalls. */ + + if (skip_seccomp_unavailable(u, "PrivateDevices=")) + return 0; + + return seccomp_load_filter_set(SCMP_ACT_ALLOW, syscall_filter_sets + SYSCALL_FILTER_SET_RAW_IO, SCMP_ACT_ERRNO(EPERM)); +} + #endif static void do_idle_pipe_dance(int idle_pipe[4]) { assert(idle_pipe); - idle_pipe[1] = safe_close(idle_pipe[1]); idle_pipe[2] = safe_close(idle_pipe[2]); @@ -1366,6 +1569,7 @@ static void do_idle_pipe_dance(int idle_pipe[4]) { } static int build_environment( + Unit *u, const ExecContext *c, const ExecParameters *p, unsigned n_fds, @@ -1380,10 +1584,11 @@ static int build_environment( unsigned n_env = 0; char *x; + assert(u); assert(c); assert(ret); - our_env = new0(char*, 12); + our_env = new0(char*, 14); if (!our_env) return -ENOMEM; @@ -1408,7 +1613,7 @@ static int build_environment( our_env[n_env++] = x; } - if (p->watchdog_usec > 0) { + if ((p->flags & EXEC_SET_WATCHDOG) && p->watchdog_usec > 0) { if (asprintf(&x, "WATCHDOG_PID="PID_FMT, getpid()) < 0) return -ENOMEM; our_env[n_env++] = x; @@ -1418,6 +1623,16 @@ static int build_environment( our_env[n_env++] = x; } + /* If this is D-Bus, tell the nss-systemd module, since it relies on being able to use D-Bus look up dynamic + * users via PID 1, possibly dead-locking the dbus daemon. This way it will not use D-Bus to resolve names, but + * check the database directly. */ + if (unit_has_name(u, SPECIAL_DBUS_SERVICE)) { + x = strdup("SYSTEMD_NSS_BYPASS_BUS=1"); + if (!x) + return -ENOMEM; + our_env[n_env++] = x; + } + if (home) { x = strappend("HOME=", home); if (!x) @@ -1444,12 +1659,28 @@ static int build_environment( our_env[n_env++] = x; } - if (is_terminal_input(c->std_input) || - c->std_output == EXEC_OUTPUT_TTY || - c->std_error == EXEC_OUTPUT_TTY || - c->tty_path) { + if (!sd_id128_is_null(u->invocation_id)) { + if (asprintf(&x, "INVOCATION_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(u->invocation_id)) < 0) + return -ENOMEM; + + our_env[n_env++] = x; + } + + if (exec_context_needs_term(c)) { + const char *tty_path, *term = NULL; + + tty_path = exec_context_tty_path(c); + + /* If we are forked off PID 1 and we are supposed to operate on /dev/console, then let's try to inherit + * the $TERM set for PID 1. This is useful for containers so that the $TERM the container manager + * passes to PID 1 ends up all the way in the console login shown. */ - x = strdup(default_term_for_tty(exec_context_tty_path(c))); + if (path_equal(tty_path, "/dev/console") && getppid() == 1) + term = getenv("TERM"); + if (!term) + term = default_term_for_tty(tty_path); + + x = strappend("TERM=", term); if (!x) return -ENOMEM; our_env[n_env++] = x; @@ -1520,20 +1751,382 @@ static bool exec_needs_mount_namespace( if (context->private_devices || context->protect_system != PROTECT_SYSTEM_NO || - context->protect_home != PROTECT_HOME_NO) + context->protect_home != PROTECT_HOME_NO || + context->protect_kernel_tunables || + context->protect_kernel_modules || + context->protect_control_groups) return true; return false; } +static int setup_private_users(uid_t uid, gid_t gid) { + _cleanup_free_ char *uid_map = NULL, *gid_map = NULL; + _cleanup_close_pair_ int errno_pipe[2] = { -1, -1 }; + _cleanup_close_ int unshare_ready_fd = -1; + _cleanup_(sigkill_waitp) pid_t pid = 0; + uint64_t c = 1; + siginfo_t si; + ssize_t n; + int r; + + /* Set up a user namespace and map root to root, the selected UID/GID to itself, and everything else to + * nobody. In order to be able to write this mapping we need CAP_SETUID in the original user namespace, which + * we however lack after opening the user namespace. To work around this we fork() a temporary child process, + * which waits for the parent to create the new user namespace while staying in the original namespace. The + * child then writes the UID mapping, under full privileges. The parent waits for the child to finish and + * continues execution normally. */ + + if (uid != 0 && uid_is_valid(uid)) + asprintf(&uid_map, + "0 0 1\n" /* Map root → root */ + UID_FMT " " UID_FMT " 1\n", /* Map $UID → $UID */ + uid, uid); + else + uid_map = strdup("0 0 1\n"); /* The case where the above is the same */ + if (!uid_map) + return -ENOMEM; + + if (gid != 0 && gid_is_valid(gid)) + asprintf(&gid_map, + "0 0 1\n" /* Map root → root */ + GID_FMT " " GID_FMT " 1\n", /* Map $GID → $GID */ + gid, gid); + else + gid_map = strdup("0 0 1\n"); /* The case where the above is the same */ + if (!gid_map) + return -ENOMEM; + + /* Create a communication channel so that the parent can tell the child when it finished creating the user + * namespace. */ + unshare_ready_fd = eventfd(0, EFD_CLOEXEC); + if (unshare_ready_fd < 0) + return -errno; + + /* Create a communication channel so that the child can tell the parent a proper error code in case it + * failed. */ + if (pipe2(errno_pipe, O_CLOEXEC) < 0) + return -errno; + + pid = fork(); + if (pid < 0) + return -errno; + + if (pid == 0) { + _cleanup_close_ int fd = -1; + const char *a; + pid_t ppid; + + /* Child process, running in the original user namespace. Let's update the parent's UID/GID map from + * here, after the parent opened its own user namespace. */ + + ppid = getppid(); + errno_pipe[0] = safe_close(errno_pipe[0]); + + /* Wait until the parent unshared the user namespace */ + if (read(unshare_ready_fd, &c, sizeof(c)) < 0) { + r = -errno; + goto child_fail; + } + + /* Disable the setgroups() system call in the child user namespace, for good. */ + a = procfs_file_alloca(ppid, "setgroups"); + fd = open(a, O_WRONLY|O_CLOEXEC); + if (fd < 0) { + if (errno != ENOENT) { + r = -errno; + goto child_fail; + } + + /* If the file is missing the kernel is too old, let's continue anyway. */ + } else { + if (write(fd, "deny\n", 5) < 0) { + r = -errno; + goto child_fail; + } + + fd = safe_close(fd); + } + + /* First write the GID map */ + a = procfs_file_alloca(ppid, "gid_map"); + fd = open(a, O_WRONLY|O_CLOEXEC); + if (fd < 0) { + r = -errno; + goto child_fail; + } + if (write(fd, gid_map, strlen(gid_map)) < 0) { + r = -errno; + goto child_fail; + } + fd = safe_close(fd); + + /* The write the UID map */ + a = procfs_file_alloca(ppid, "uid_map"); + fd = open(a, O_WRONLY|O_CLOEXEC); + if (fd < 0) { + r = -errno; + goto child_fail; + } + if (write(fd, uid_map, strlen(uid_map)) < 0) { + r = -errno; + goto child_fail; + } + + _exit(EXIT_SUCCESS); + + child_fail: + (void) write(errno_pipe[1], &r, sizeof(r)); + _exit(EXIT_FAILURE); + } + + errno_pipe[1] = safe_close(errno_pipe[1]); + + if (unshare(CLONE_NEWUSER) < 0) + return -errno; + + /* Let the child know that the namespace is ready now */ + if (write(unshare_ready_fd, &c, sizeof(c)) < 0) + return -errno; + + /* Try to read an error code from the child */ + n = read(errno_pipe[0], &r, sizeof(r)); + if (n < 0) + return -errno; + if (n == sizeof(r)) { /* an error code was sent to us */ + if (r < 0) + return r; + return -EIO; + } + if (n != 0) /* on success we should have read 0 bytes */ + return -EIO; + + r = wait_for_terminate(pid, &si); + if (r < 0) + return r; + pid = 0; + + /* If something strange happened with the child, let's consider this fatal, too */ + if (si.si_code != CLD_EXITED || si.si_status != 0) + return -EIO; + + return 0; +} + +static int setup_runtime_directory( + const ExecContext *context, + const ExecParameters *params, + uid_t uid, + gid_t gid) { + + char **rt; + int r; + + assert(context); + assert(params); + + STRV_FOREACH(rt, context->runtime_directory) { + _cleanup_free_ char *p; + + p = strjoin(params->runtime_prefix, "/", *rt, NULL); + if (!p) + return -ENOMEM; + + r = mkdir_p_label(p, context->runtime_directory_mode); + if (r < 0) + return r; + + r = chmod_and_chown(p, context->runtime_directory_mode, uid, gid); + if (r < 0) + return r; + } + + return 0; +} + +static int setup_smack( + const ExecContext *context, + const ExecCommand *command) { + +#ifdef HAVE_SMACK + int r; + + assert(context); + assert(command); + + if (!mac_smack_use()) + return 0; + + if (context->smack_process_label) { + r = mac_smack_apply_pid(0, context->smack_process_label); + if (r < 0) + return r; + } +#ifdef SMACK_DEFAULT_PROCESS_LABEL + else { + _cleanup_free_ char *exec_label = NULL; + + r = mac_smack_read(command->path, SMACK_ATTR_EXEC, &exec_label); + if (r < 0 && r != -ENODATA && r != -EOPNOTSUPP) + return r; + + r = mac_smack_apply_pid(0, exec_label ? : SMACK_DEFAULT_PROCESS_LABEL); + if (r < 0) + return r; + } +#endif +#endif + + return 0; +} + +static int compile_read_write_paths( + const ExecContext *context, + const ExecParameters *params, + char ***ret) { + + _cleanup_strv_free_ char **l = NULL; + char **rt; + + /* Compile the list of writable paths. This is the combination of the explicitly configured paths, plus all + * runtime directories. */ + + if (strv_isempty(context->read_write_paths) && + strv_isempty(context->runtime_directory)) { + *ret = NULL; /* NOP if neither is set */ + return 0; + } + + l = strv_copy(context->read_write_paths); + if (!l) + return -ENOMEM; + + STRV_FOREACH(rt, context->runtime_directory) { + char *s; + + s = strjoin(params->runtime_prefix, "/", *rt, NULL); + if (!s) + return -ENOMEM; + + if (strv_consume(&l, s) < 0) + return -ENOMEM; + } + + *ret = l; + l = NULL; + + return 0; +} + +static int apply_mount_namespace(Unit *u, const ExecContext *context, + const ExecParameters *params, + ExecRuntime *runtime) { + int r; + _cleanup_free_ char **rw = NULL; + char *tmp = NULL, *var = NULL; + const char *root_dir = NULL; + NameSpaceInfo ns_info = { + .private_dev = context->private_devices, + .protect_control_groups = context->protect_control_groups, + .protect_kernel_tunables = context->protect_kernel_tunables, + .protect_kernel_modules = context->protect_kernel_modules, + }; + + assert(context); + + /* The runtime struct only contains the parent of the private /tmp, + * which is non-accessible to world users. Inside of it there's a /tmp + * that is sticky, and that's the one we want to use here. */ + + if (context->private_tmp && runtime) { + if (runtime->tmp_dir) + tmp = strjoina(runtime->tmp_dir, "/tmp"); + if (runtime->var_tmp_dir) + var = strjoina(runtime->var_tmp_dir, "/tmp"); + } + + r = compile_read_write_paths(context, params, &rw); + if (r < 0) + return r; + + if (params->flags & EXEC_APPLY_CHROOT) + root_dir = context->root_directory; + + r = setup_namespace(root_dir, &ns_info, rw, + context->read_only_paths, + context->inaccessible_paths, + tmp, + var, + context->protect_home, + context->protect_system, + context->mount_flags); + + /* If we couldn't set up the namespace this is probably due to a + * missing capability. In this case, silently proceeed. */ + if (IN_SET(r, -EPERM, -EACCES)) { + log_open(); + log_unit_debug_errno(u, r, "Failed to set up namespace, assuming containerized execution, ignoring: %m"); + log_close(); + r = 0; + } + + return r; +} + +static int apply_working_directory(const ExecContext *context, + const ExecParameters *params, + const char *home, + const bool needs_mount_ns) { + const char *d; + const char *wd; + + assert(context); + + if (context->working_directory_home) + wd = home; + else if (context->working_directory) + wd = context->working_directory; + else + wd = "/"; + + if (params->flags & EXEC_APPLY_CHROOT) { + if (!needs_mount_ns && context->root_directory) + if (chroot(context->root_directory) < 0) + return -errno; + + d = wd; + } else + d = strjoina(strempty(context->root_directory), "/", strempty(wd)); + + if (chdir(d) < 0 && !context->working_directory_missing_ok) + return -errno; + + return 0; +} + +static void append_socket_pair(int *array, unsigned *n, int pair[2]) { + assert(array); + assert(n); + + if (!pair) + return; + + if (pair[0] >= 0) + array[(*n)++] = pair[0]; + if (pair[1] >= 0) + array[(*n)++] = pair[1]; +} + static int close_remaining_fds( const ExecParameters *params, ExecRuntime *runtime, + DynamicCreds *dcreds, + int user_lookup_fd, int socket_fd, int *fds, unsigned n_fds) { unsigned n_dont_close = 0; - int dont_close[n_fds + 7]; + int dont_close[n_fds + 12]; assert(params); @@ -1551,37 +2144,109 @@ static int close_remaining_fds( n_dont_close += n_fds; } - if (runtime) { - if (runtime->netns_storage_socket[0] >= 0) - dont_close[n_dont_close++] = runtime->netns_storage_socket[0]; - if (runtime->netns_storage_socket[1] >= 0) - dont_close[n_dont_close++] = runtime->netns_storage_socket[1]; + if (runtime) + append_socket_pair(dont_close, &n_dont_close, runtime->netns_storage_socket); + + if (dcreds) { + if (dcreds->user) + append_socket_pair(dont_close, &n_dont_close, dcreds->user->storage_socket); + if (dcreds->group) + append_socket_pair(dont_close, &n_dont_close, dcreds->group->storage_socket); } + if (user_lookup_fd >= 0) + dont_close[n_dont_close++] = user_lookup_fd; + return close_all_fds(dont_close, n_dont_close); } +static bool context_has_address_families(const ExecContext *c) { + assert(c); + + return c->address_families_whitelist || + !set_isempty(c->address_families); +} + +static bool context_has_syscall_filters(const ExecContext *c) { + assert(c); + + return c->syscall_whitelist || + !set_isempty(c->syscall_filter) || + !set_isempty(c->syscall_archs); +} + +static bool context_has_no_new_privileges(const ExecContext *c) { + assert(c); + + if (c->no_new_privileges) + return true; + + if (have_effective_cap(CAP_SYS_ADMIN)) /* if we are privileged, we don't need NNP */ + return false; + + return context_has_address_families(c) || /* we need NNP if we have any form of seccomp and are unprivileged */ + c->memory_deny_write_execute || + c->restrict_realtime || + c->protect_kernel_tunables || + c->protect_kernel_modules || + c->private_devices || + context_has_syscall_filters(c); +} + +static int send_user_lookup( + Unit *unit, + int user_lookup_fd, + uid_t uid, + gid_t gid) { + + assert(unit); + + /* Send the resolved UID/GID to PID 1 after we learnt it. We send a single datagram, containing the UID/GID + * data as well as the unit name. Note that we suppress sending this if no user/group to resolve was + * specified. */ + + if (user_lookup_fd < 0) + return 0; + + if (!uid_is_valid(uid) && !gid_is_valid(gid)) + return 0; + + if (writev(user_lookup_fd, + (struct iovec[]) { + { .iov_base = &uid, .iov_len = sizeof(uid) }, + { .iov_base = &gid, .iov_len = sizeof(gid) }, + { .iov_base = unit->id, .iov_len = strlen(unit->id) }}, 3) < 0) + return -errno; + + return 0; +} + static int exec_child( Unit *unit, ExecCommand *command, const ExecContext *context, const ExecParameters *params, ExecRuntime *runtime, + DynamicCreds *dcreds, char **argv, int socket_fd, + int named_iofds[3], int *fds, unsigned n_fds, char **files_env, + int user_lookup_fd, int *exit_status) { _cleanup_strv_free_ char **our_env = NULL, **pass_env = NULL, **accum_env = NULL, **final_argv = NULL; _cleanup_free_ char *mac_selinux_context_net = NULL; - const char *username = NULL, *home = NULL, *shell = NULL, *wd; + _cleanup_free_ gid_t *supplementary_gids = NULL; + const char *username = NULL, *groupname = NULL; + const char *home = NULL, *shell = NULL; dev_t journal_stream_dev = 0; ino_t journal_stream_ino = 0; bool needs_mount_namespace; uid_t uid = UID_INVALID; gid_t gid = GID_INVALID; - int i, r; + int i, r, ngids = 0; assert(unit); assert(command); @@ -1617,7 +2282,7 @@ static int exec_child( log_forget_fds(); - r = close_remaining_fds(params, runtime, socket_fd, fds, n_fds); + r = close_remaining_fds(params, runtime, dcreds, user_lookup_fd, socket_fd, fds, n_fds); if (r < 0) { *exit_status = EXIT_FDS; return r; @@ -1631,7 +2296,7 @@ static int exec_child( exec_context_tty_reset(context, params); - if (params->confirm_spawn) { + if (params->flags & EXEC_CONFIRM_SPAWN) { char response; r = ask_for_confirmation(&response, argv); @@ -1650,44 +2315,76 @@ static int exec_child( } } - if (context->user) { - username = context->user; - r = get_user_creds(&username, &uid, &gid, &home, &shell); + if (context->dynamic_user && dcreds) { + + /* Make sure we bypass our own NSS module for any NSS checks */ + if (putenv((char*) "SYSTEMD_NSS_DYNAMIC_BYPASS=1") != 0) { + *exit_status = EXIT_USER; + return -errno; + } + + r = dynamic_creds_realize(dcreds, &uid, &gid); if (r < 0) { *exit_status = EXIT_USER; return r; } - } - if (context->group) { - const char *g = context->group; + if (!uid_is_valid(uid) || !gid_is_valid(gid)) { + *exit_status = EXIT_USER; + return -ESRCH; + } + + if (dcreds->user) + username = dcreds->user->name; + + } else { + r = get_fixed_user(context, &username, &uid, &gid, &home, &shell); + if (r < 0) { + *exit_status = EXIT_USER; + return r; + } - r = get_group_creds(&g, &gid); + r = get_fixed_group(context, &groupname, &gid); if (r < 0) { *exit_status = EXIT_GROUP; return r; } } + /* Initialize user supplementary groups and get SupplementaryGroups= ones */ + r = get_supplementary_groups(context, username, groupname, gid, + &supplementary_gids, &ngids); + if (r < 0) { + *exit_status = EXIT_GROUP; + return r; + } + + r = send_user_lookup(unit, user_lookup_fd, uid, gid); + if (r < 0) { + *exit_status = EXIT_USER; + return r; + } + + user_lookup_fd = safe_close(user_lookup_fd); /* If a socket is connected to STDIN/STDOUT/STDERR, we * must sure to drop O_NONBLOCK */ if (socket_fd >= 0) (void) fd_nonblock(socket_fd, false); - r = setup_input(context, params, socket_fd); + r = setup_input(context, params, socket_fd, named_iofds); if (r < 0) { *exit_status = EXIT_STDIN; return r; } - r = setup_output(unit, context, params, STDOUT_FILENO, socket_fd, basename(command->path), uid, gid, &journal_stream_dev, &journal_stream_ino); + r = setup_output(unit, context, params, STDOUT_FILENO, socket_fd, named_iofds, basename(command->path), uid, gid, &journal_stream_dev, &journal_stream_ino); if (r < 0) { *exit_status = EXIT_STDOUT; return r; } - r = setup_output(unit, context, params, STDERR_FILENO, socket_fd, basename(command->path), uid, gid, &journal_stream_dev, &journal_stream_ino); + r = setup_output(unit, context, params, STDERR_FILENO, socket_fd, named_iofds, basename(command->path), uid, gid, &journal_stream_dev, &journal_stream_ino); if (r < 0) { *exit_status = EXIT_STDERR; return r; @@ -1774,7 +2471,7 @@ static int exec_child( USER_PROCESS, username ? "root" : context->user); - if (context->user && is_terminal_input(context->std_input)) { + if (context->user) { r = chown_terminal(STDIN_FILENO, uid); if (r < 0) { *exit_status = EXIT_STDIN; @@ -1801,32 +2498,15 @@ static int exec_child( } if (!strv_isempty(context->runtime_directory) && params->runtime_prefix) { - char **rt; - - STRV_FOREACH(rt, context->runtime_directory) { - _cleanup_free_ char *p; - - p = strjoin(params->runtime_prefix, "/", *rt, NULL); - if (!p) { - *exit_status = EXIT_RUNTIME_DIRECTORY; - return -ENOMEM; - } - - r = mkdir_p_label(p, context->runtime_directory_mode); - if (r < 0) { - *exit_status = EXIT_RUNTIME_DIRECTORY; - return r; - } - - r = chmod_and_chown(p, context->runtime_directory_mode, uid, gid); - if (r < 0) { - *exit_status = EXIT_RUNTIME_DIRECTORY; - return r; - } + r = setup_runtime_directory(context, params, uid, gid); + if (r < 0) { + *exit_status = EXIT_RUNTIME_DIRECTORY; + return r; } } r = build_environment( + unit, context, params, n_fds, @@ -1860,49 +2540,16 @@ static int exec_child( } accum_env = strv_env_clean(accum_env); - umask(context->umask); + (void) umask(context->umask); - if (params->apply_permissions && !command->privileged) { - r = enforce_groups(context, username, gid); - if (r < 0) { - *exit_status = EXIT_GROUP; - return r; - } -#ifdef HAVE_SMACK - if (context->smack_process_label) { - r = mac_smack_apply_pid(0, context->smack_process_label); - if (r < 0) { - *exit_status = EXIT_SMACK_PROCESS_LABEL; - return r; - } - } -#ifdef SMACK_DEFAULT_PROCESS_LABEL - else { - _cleanup_free_ char *exec_label = NULL; - - r = mac_smack_read(command->path, SMACK_ATTR_EXEC, &exec_label); - if (r < 0 && r != -ENODATA && r != -EOPNOTSUPP) { - *exit_status = EXIT_SMACK_PROCESS_LABEL; - return r; - } - - r = mac_smack_apply_pid(0, exec_label ? : SMACK_DEFAULT_PROCESS_LABEL); - if (r < 0) { - *exit_status = EXIT_SMACK_PROCESS_LABEL; - return r; - } - } -#endif -#endif -#ifdef HAVE_PAM + if ((params->flags & EXEC_APPLY_PERMISSIONS) && !command->privileged) { if (context->pam_name && username) { - r = setup_pam(context->pam_name, username, uid, context->tty_path, &accum_env, fds, n_fds); + r = setup_pam(context->pam_name, username, uid, gid, context->tty_path, &accum_env, fds, n_fds); if (r < 0) { *exit_status = EXIT_PAM; return r; } } -#endif } if (context->private_network && runtime && runtime->netns_storage_socket[0] >= 0) { @@ -1914,80 +2561,37 @@ static int exec_child( } needs_mount_namespace = exec_needs_mount_namespace(context, params, runtime); - if (needs_mount_namespace) { - char *tmp = NULL, *var = NULL; - - /* The runtime struct only contains the parent - * of the private /tmp, which is - * non-accessible to world users. Inside of it - * there's a /tmp that is sticky, and that's - * the one we want to use here. */ - - if (context->private_tmp && runtime) { - if (runtime->tmp_dir) - tmp = strjoina(runtime->tmp_dir, "/tmp"); - if (runtime->var_tmp_dir) - var = strjoina(runtime->var_tmp_dir, "/tmp"); - } - - r = setup_namespace( - params->apply_chroot ? context->root_directory : NULL, - context->read_write_paths, - context->read_only_paths, - context->inaccessible_paths, - tmp, - var, - context->private_devices, - context->protect_home, - context->protect_system, - context->mount_flags); - - /* If we couldn't set up the namespace this is - * probably due to a missing capability. In this case, - * silently proceeed. */ - if (r == -EPERM || r == -EACCES) { - log_open(); - log_unit_debug_errno(unit, r, "Failed to set up namespace, assuming containerized execution, ignoring: %m"); - log_close(); - } else if (r < 0) { + r = apply_mount_namespace(unit, context, params, runtime); + if (r < 0) { *exit_status = EXIT_NAMESPACE; return r; } } - if (context->working_directory_home) - wd = home; - else if (context->working_directory) - wd = context->working_directory; - else - wd = "/"; - - if (params->apply_chroot) { - if (!needs_mount_namespace && context->root_directory) - if (chroot(context->root_directory) < 0) { - *exit_status = EXIT_CHROOT; - return -errno; - } - - if (chdir(wd) < 0 && - !context->working_directory_missing_ok) { - *exit_status = EXIT_CHDIR; - return -errno; - } - } else { - const char *d; + /* Apply just after mount namespace setup */ + r = apply_working_directory(context, params, home, needs_mount_namespace); + if (r < 0) { + *exit_status = EXIT_CHROOT; + return r; + } - d = strjoina(strempty(context->root_directory), "/", strempty(wd)); - if (chdir(d) < 0 && - !context->working_directory_missing_ok) { - *exit_status = EXIT_CHDIR; - return -errno; + /* Drop groups as early as possbile */ + if ((params->flags & EXEC_APPLY_PERMISSIONS) && !command->privileged) { + r = enforce_groups(context, gid, supplementary_gids, ngids); + if (r < 0) { + *exit_status = EXIT_GROUP; + return r; } } #ifdef HAVE_SELINUX - if (params->apply_permissions && mac_selinux_use() && params->selinux_context_net && socket_fd >= 0 && !command->privileged) { + if ((params->flags & EXEC_APPLY_PERMISSIONS) && + mac_selinux_use() && + params->selinux_context_net && + socket_fd >= 0 && + !command->privileged) { + r = mac_selinux_get_child_mls_label(socket_fd, command->path, context->selinux_context, &mac_selinux_context_net); if (r < 0) { *exit_status = EXIT_SELINUX_CONTEXT; @@ -1996,6 +2600,14 @@ static int exec_child( } #endif + if ((params->flags & EXEC_APPLY_PERMISSIONS) && context->private_users) { + r = setup_private_users(uid, gid); + if (r < 0) { + *exit_status = EXIT_USER; + return r; + } + } + /* We repeat the fd closing here, to make sure that * nothing is leaked from the PAM modules. Note that * we are more aggressive this time since socket_fd @@ -2012,13 +2624,8 @@ static int exec_child( return r; } - if (params->apply_permissions && !command->privileged) { + if ((params->flags & EXEC_APPLY_PERMISSIONS) && !command->privileged) { - bool use_address_families = context->address_families_whitelist || - !set_isempty(context->address_families); - bool use_syscall_filter = context->syscall_whitelist || - !set_isempty(context->syscall_filter) || - !set_isempty(context->syscall_archs); int secure_bits = context->secure_bits; for (i = 0; i < _RLIMIT_MAX; i++) { @@ -2085,6 +2692,41 @@ static int exec_child( } } + /* Apply the MAC contexts late, but before seccomp syscall filtering, as those should really be last to + * influence our own codepaths as little as possible. Moreover, applying MAC contexts usually requires + * syscalls that are subject to seccomp filtering, hence should probably be applied before the syscalls + * are restricted. */ + +#ifdef HAVE_SELINUX + if (mac_selinux_use()) { + char *exec_context = mac_selinux_context_net ?: context->selinux_context; + + if (exec_context) { + r = setexeccon(exec_context); + if (r < 0) { + *exit_status = EXIT_SELINUX_CONTEXT; + return r; + } + } + } +#endif + + r = setup_smack(context, command); + if (r < 0) { + *exit_status = EXIT_SMACK_PROCESS_LABEL; + return r; + } + +#ifdef HAVE_APPARMOR + if (context->apparmor_profile && mac_apparmor_use()) { + r = aa_change_onexec(context->apparmor_profile); + if (r < 0 && !context->apparmor_profile_ignore) { + *exit_status = EXIT_APPARMOR_PROFILE; + return -errno; + } + } +#endif + /* PR_GET_SECUREBITS is not privileged, while * PR_SET_SECUREBITS is. So to suppress * potential EPERMs we'll try not to call @@ -2095,16 +2737,15 @@ static int exec_child( return -errno; } - if (context->no_new_privileges || - (!have_effective_cap(CAP_SYS_ADMIN) && (use_address_families || context->memory_deny_write_execute || context->restrict_realtime || use_syscall_filter))) + if (context_has_no_new_privileges(context)) if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) { *exit_status = EXIT_NO_NEW_PRIVILEGES; return -errno; } #ifdef HAVE_SECCOMP - if (use_address_families) { - r = apply_address_families(context); + if (context_has_address_families(context)) { + r = apply_address_families(unit, context); if (r < 0) { *exit_status = EXIT_ADDRESS_FAMILIES; return r; @@ -2112,7 +2753,7 @@ static int exec_child( } if (context->memory_deny_write_execute) { - r = apply_memory_deny_write_execute(context); + r = apply_memory_deny_write_execute(unit, context); if (r < 0) { *exit_status = EXIT_SECCOMP; return r; @@ -2120,42 +2761,44 @@ static int exec_child( } if (context->restrict_realtime) { - r = apply_restrict_realtime(context); + r = apply_restrict_realtime(unit, context); if (r < 0) { *exit_status = EXIT_SECCOMP; return r; } } - if (use_syscall_filter) { - r = apply_seccomp(context); + if (context->protect_kernel_tunables) { + r = apply_protect_sysctl(unit, context); if (r < 0) { *exit_status = EXIT_SECCOMP; return r; } } -#endif -#ifdef HAVE_SELINUX - if (mac_selinux_use()) { - char *exec_context = mac_selinux_context_net ?: context->selinux_context; + if (context->protect_kernel_modules) { + r = apply_protect_kernel_modules(unit, context); + if (r < 0) { + *exit_status = EXIT_SECCOMP; + return r; + } + } - if (exec_context) { - r = setexeccon(exec_context); - if (r < 0) { - *exit_status = EXIT_SELINUX_CONTEXT; - return r; - } + if (context->private_devices) { + r = apply_private_devices(unit, context); + if (r < 0) { + *exit_status = EXIT_SECCOMP; + return r; } } -#endif -#ifdef HAVE_APPARMOR - if (context->apparmor_profile && mac_apparmor_use()) { - r = aa_change_onexec(context->apparmor_profile); - if (r < 0 && !context->apparmor_profile_ignore) { - *exit_status = EXIT_APPARMOR_PROFILE; - return -errno; + /* This really should remain the last step before the execve(), to make sure our own code is unaffected + * by the filter as little as possible. */ + if (context_has_syscall_filters(context)) { + r = apply_seccomp(unit, context); + if (r < 0) { + *exit_status = EXIT_SECCOMP; + return r; } } #endif @@ -2192,12 +2835,14 @@ int exec_spawn(Unit *unit, const ExecContext *context, const ExecParameters *params, ExecRuntime *runtime, + DynamicCreds *dcreds, pid_t *ret) { _cleanup_strv_free_ char **files_env = NULL; int *fds = NULL; unsigned n_fds = 0; _cleanup_free_ char *line = NULL; int socket_fd, r; + int named_iofds[3] = { -1, -1, -1 }; char **argv; pid_t pid; @@ -2224,6 +2869,10 @@ int exec_spawn(Unit *unit, n_fds = params->n_fds; } + r = exec_context_named_iofds(unit, context, params, named_iofds); + if (r < 0) + return log_unit_error_errno(unit, r, "Failed to load a named file descriptor: %m"); + r = exec_context_load_environment(unit, context, &files_env); if (r < 0) return log_unit_error_errno(unit, r, "Failed to load environment files: %m"); @@ -2250,10 +2899,13 @@ int exec_spawn(Unit *unit, context, params, runtime, + dcreds, argv, socket_fd, + named_iofds, fds, n_fds, files_env, + unit->manager->user_lookup_fds[1], &exit_status); if (r < 0) { log_open(); @@ -2313,6 +2965,9 @@ void exec_context_done(ExecContext *c) { for (l = 0; l < ELEMENTSOF(c->rlimit); l++) c->rlimit[l] = mfree(c->rlimit[l]); + for (l = 0; l < 3; l++) + c->stdio_fdname[l] = mfree(c->stdio_fdname[l]); + c->working_directory = mfree(c->working_directory); c->root_directory = mfree(c->root_directory); c->tty_path = mfree(c->tty_path); @@ -2411,6 +3066,56 @@ static void invalid_env(const char *p, void *userdata) { log_unit_error(info->unit, "Ignoring invalid environment assignment '%s': %s", p, info->path); } +const char* exec_context_fdname(const ExecContext *c, int fd_index) { + assert(c); + + switch (fd_index) { + case STDIN_FILENO: + if (c->std_input != EXEC_INPUT_NAMED_FD) + return NULL; + return c->stdio_fdname[STDIN_FILENO] ?: "stdin"; + case STDOUT_FILENO: + if (c->std_output != EXEC_OUTPUT_NAMED_FD) + return NULL; + return c->stdio_fdname[STDOUT_FILENO] ?: "stdout"; + case STDERR_FILENO: + if (c->std_error != EXEC_OUTPUT_NAMED_FD) + return NULL; + return c->stdio_fdname[STDERR_FILENO] ?: "stderr"; + default: + return NULL; + } +} + +int exec_context_named_iofds(Unit *unit, const ExecContext *c, const ExecParameters *p, int named_iofds[3]) { + unsigned i, targets; + const char *stdio_fdname[3]; + + assert(c); + assert(p); + + targets = (c->std_input == EXEC_INPUT_NAMED_FD) + + (c->std_output == EXEC_OUTPUT_NAMED_FD) + + (c->std_error == EXEC_OUTPUT_NAMED_FD); + + for (i = 0; i < 3; i++) + stdio_fdname[i] = exec_context_fdname(c, i); + + for (i = 0; i < p->n_fds && targets > 0; i++) + if (named_iofds[STDIN_FILENO] < 0 && c->std_input == EXEC_INPUT_NAMED_FD && stdio_fdname[STDIN_FILENO] && streq(p->fd_names[i], stdio_fdname[STDIN_FILENO])) { + named_iofds[STDIN_FILENO] = p->fds[i]; + targets--; + } else if (named_iofds[STDOUT_FILENO] < 0 && c->std_output == EXEC_OUTPUT_NAMED_FD && stdio_fdname[STDOUT_FILENO] && streq(p->fd_names[i], stdio_fdname[STDOUT_FILENO])) { + named_iofds[STDOUT_FILENO] = p->fds[i]; + targets--; + } else if (named_iofds[STDERR_FILENO] < 0 && c->std_error == EXEC_OUTPUT_NAMED_FD && stdio_fdname[STDERR_FILENO] && streq(p->fd_names[i], stdio_fdname[STDERR_FILENO])) { + named_iofds[STDERR_FILENO] = p->fds[i]; + targets--; + } + + return (targets == 0 ? 0 : -ENOENT); +} + int exec_context_load_environment(Unit *unit, const ExecContext *c, char ***l) { char **i, **r = NULL; @@ -2555,8 +3260,12 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { "%sRootDirectory: %s\n" "%sNonBlocking: %s\n" "%sPrivateTmp: %s\n" - "%sPrivateNetwork: %s\n" "%sPrivateDevices: %s\n" + "%sProtectKernelTunables: %s\n" + "%sProtectKernelModules: %s\n" + "%sProtectControlGroups: %s\n" + "%sPrivateNetwork: %s\n" + "%sPrivateUsers: %s\n" "%sProtectHome: %s\n" "%sProtectSystem: %s\n" "%sIgnoreSIGPIPE: %s\n" @@ -2567,8 +3276,12 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { prefix, c->root_directory ? c->root_directory : "/", prefix, yes_no(c->non_blocking), prefix, yes_no(c->private_tmp), - prefix, yes_no(c->private_network), prefix, yes_no(c->private_devices), + prefix, yes_no(c->protect_kernel_tunables), + prefix, yes_no(c->protect_kernel_modules), + prefix, yes_no(c->protect_control_groups), + prefix, yes_no(c->private_network), + prefix, yes_no(c->private_users), prefix, protect_home_to_string(c->protect_home), prefix, protect_system_to_string(c->protect_system), prefix, yes_no(c->ignore_sigpipe), @@ -2723,6 +3436,8 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { if (c->group) fprintf(f, "%sGroup: %s\n", prefix, c->group); + fprintf(f, "%sDynamicUser: %s\n", prefix, yes_no(c->dynamic_user)); + if (strv_length(c->supplementary_groups) > 0) { fprintf(f, "%sSupplementaryGroups:", prefix); strv_fprintf(f, c->supplementary_groups); @@ -2882,12 +3597,12 @@ void exec_status_dump(ExecStatus *s, FILE *f, const char *prefix) { "%sPID: "PID_FMT"\n", prefix, s->pid); - if (s->start_timestamp.realtime > 0) + if (dual_timestamp_is_set(&s->start_timestamp)) fprintf(f, "%sStart Timestamp: %s\n", prefix, format_timestamp(buf, sizeof(buf), s->start_timestamp.realtime)); - if (s->exit_timestamp.realtime > 0) + if (dual_timestamp_is_set(&s->exit_timestamp)) fprintf(f, "%sExit Timestamp: %s\n" "%sExit Code: %s\n" @@ -2908,7 +3623,8 @@ char *exec_command_line(char **argv) { STRV_FOREACH(a, argv) k += strlen(*a)+3; - if (!(n = new(char, k))) + n = new(char, k); + if (!n) return NULL; p = n; @@ -3097,9 +3813,7 @@ ExecRuntime *exec_runtime_unref(ExecRuntime *r) { free(r->tmp_dir); free(r->var_tmp_dir); safe_close_pair(r->netns_storage_socket); - free(r); - - return NULL; + return mfree(r); } int exec_runtime_serialize(Unit *u, ExecRuntime *rt, FILE *f, FDSet *fds) { @@ -3255,7 +3969,8 @@ static const char* const exec_input_table[_EXEC_INPUT_MAX] = { [EXEC_INPUT_TTY] = "tty", [EXEC_INPUT_TTY_FORCE] = "tty-force", [EXEC_INPUT_TTY_FAIL] = "tty-fail", - [EXEC_INPUT_SOCKET] = "socket" + [EXEC_INPUT_SOCKET] = "socket", + [EXEC_INPUT_NAMED_FD] = "fd", }; DEFINE_STRING_TABLE_LOOKUP(exec_input, ExecInput); @@ -3270,7 +3985,8 @@ static const char* const exec_output_table[_EXEC_OUTPUT_MAX] = { [EXEC_OUTPUT_KMSG_AND_CONSOLE] = "kmsg+console", [EXEC_OUTPUT_JOURNAL] = "journal", [EXEC_OUTPUT_JOURNAL_AND_CONSOLE] = "journal+console", - [EXEC_OUTPUT_SOCKET] = "socket" + [EXEC_OUTPUT_SOCKET] = "socket", + [EXEC_OUTPUT_NAMED_FD] = "fd", }; DEFINE_STRING_TABLE_LOOKUP(exec_output, ExecOutput); diff --git a/src/core/execute.h b/src/core/execute.h index 189c4d0999..c7d0f7761e 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -50,6 +50,7 @@ typedef enum ExecInput { EXEC_INPUT_TTY_FORCE, EXEC_INPUT_TTY_FAIL, EXEC_INPUT_SOCKET, + EXEC_INPUT_NAMED_FD, _EXEC_INPUT_MAX, _EXEC_INPUT_INVALID = -1 } ExecInput; @@ -65,6 +66,7 @@ typedef enum ExecOutput { EXEC_OUTPUT_JOURNAL, EXEC_OUTPUT_JOURNAL_AND_CONSOLE, EXEC_OUTPUT_SOCKET, + EXEC_OUTPUT_NAMED_FD, _EXEC_OUTPUT_MAX, _EXEC_OUTPUT_INVALID = -1 } ExecOutput; @@ -92,6 +94,8 @@ struct ExecRuntime { char *tmp_dir; char *var_tmp_dir; + /* An AF_UNIX socket pair, that contains a datagram containing a file descriptor referring to the network + * namespace. */ int netns_storage_socket[2]; }; @@ -118,6 +122,7 @@ struct ExecContext { ExecInput std_input; ExecOutput std_output; ExecOutput std_error; + char *stdio_fdname[3]; nsec_t timer_slack_nsec; @@ -169,11 +174,18 @@ struct ExecContext { bool private_tmp; bool private_network; bool private_devices; + bool private_users; ProtectSystem protect_system; ProtectHome protect_home; + bool protect_kernel_tunables; + bool protect_kernel_modules; + bool protect_control_groups; bool no_new_privileges; + bool dynamic_user; + bool remove_ipc; + /* This is not exposed to the user but available * internally. We need it to make sure that whenever we spawn * /usr/bin/mount it is run in the same process group as us so @@ -204,6 +216,19 @@ struct ExecContext { bool no_new_privileges_set:1; }; +typedef enum ExecFlags { + EXEC_CONFIRM_SPAWN = 1U << 0, + EXEC_APPLY_PERMISSIONS = 1U << 1, + EXEC_APPLY_CHROOT = 1U << 2, + EXEC_APPLY_TTY_STDIN = 1U << 3, + + /* The following are not used by execute.c, but by consumers internally */ + EXEC_PASS_FDS = 1U << 4, + EXEC_IS_CONTROL = 1U << 5, + EXEC_SETENV_RESULT = 1U << 6, + EXEC_SET_WATCHDOG = 1U << 7, +} ExecFlags; + struct ExecParameters { char **argv; char **environment; @@ -212,11 +237,7 @@ struct ExecParameters { char **fd_names; unsigned n_fds; - bool apply_permissions:1; - bool apply_chroot:1; - bool apply_tty_stdin:1; - - bool confirm_spawn:1; + ExecFlags flags; bool selinux_context_net:1; bool cgroup_delegate:1; @@ -235,12 +256,14 @@ struct ExecParameters { }; #include "unit.h" +#include "dynamic-user.h" int exec_spawn(Unit *unit, ExecCommand *command, const ExecContext *context, const ExecParameters *exec_params, ExecRuntime *runtime, + DynamicCreds *dynamic_creds, pid_t *ret); void exec_command_done(ExecCommand *c); @@ -264,6 +287,8 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix); int exec_context_destroy_runtime_directory(ExecContext *c, const char *runtime_root); int exec_context_load_environment(Unit *unit, const ExecContext *c, char ***l); +int exec_context_named_iofds(Unit *unit, const ExecContext *c, const ExecParameters *p, int named_iofds[3]); +const char* exec_context_fdname(const ExecContext *c, int fd_index); bool exec_context_may_touch_console(ExecContext *c); bool exec_context_maintains_privileges(ExecContext *c); diff --git a/src/core/job.c b/src/core/job.c index 7557874d4d..ac6910a906 100644 --- a/src/core/job.c +++ b/src/core/job.c @@ -690,16 +690,16 @@ _pure_ static const char *job_get_status_message_format(Unit *u, JobType t, JobR } static void job_print_status_message(Unit *u, JobType t, JobResult result) { - static struct { + static const struct { const char *color, *word; } const statuses[_JOB_RESULT_MAX] = { - [JOB_DONE] = {ANSI_GREEN, " OK "}, - [JOB_TIMEOUT] = {ANSI_HIGHLIGHT_RED, " TIME "}, - [JOB_FAILED] = {ANSI_HIGHLIGHT_RED, "FAILED"}, - [JOB_DEPENDENCY] = {ANSI_HIGHLIGHT_YELLOW, "DEPEND"}, - [JOB_SKIPPED] = {ANSI_HIGHLIGHT, " INFO "}, - [JOB_ASSERT] = {ANSI_HIGHLIGHT_YELLOW, "ASSERT"}, - [JOB_UNSUPPORTED] = {ANSI_HIGHLIGHT_YELLOW, "UNSUPP"}, + [JOB_DONE] = { ANSI_GREEN, " OK " }, + [JOB_TIMEOUT] = { ANSI_HIGHLIGHT_RED, " TIME " }, + [JOB_FAILED] = { ANSI_HIGHLIGHT_RED, "FAILED" }, + [JOB_DEPENDENCY] = { ANSI_HIGHLIGHT_YELLOW, "DEPEND" }, + [JOB_SKIPPED] = { ANSI_HIGHLIGHT, " INFO " }, + [JOB_ASSERT] = { ANSI_HIGHLIGHT_YELLOW, "ASSERT" }, + [JOB_UNSUPPORTED] = { ANSI_HIGHLIGHT_YELLOW, "UNSUPP" }, }; const char *format; @@ -767,8 +767,9 @@ static void job_log_status_message(Unit *u, JobType t, JobResult result) { if (!format) return; + /* The description might be longer than the buffer, but that's OK, we'll just truncate it here */ DISABLE_WARNING_FORMAT_NONLITERAL; - xsprintf(buf, format, unit_description(u)); + snprintf(buf, sizeof(buf), format, unit_description(u)); REENABLE_WARNING; switch (t) { @@ -927,7 +928,7 @@ static int job_dispatch_timer(sd_event_source *s, uint64_t monotonic, void *user u = j->unit; job_finish_and_invalidate(j, JOB_TIMEOUT, true, false); - failure_action(u->manager, u->job_timeout_action, u->job_timeout_reboot_arg); + emergency_action(u->manager, u->job_timeout_action, u->job_timeout_reboot_arg, "job timed out"); return 0; } @@ -997,7 +998,10 @@ char *job_dbus_path(Job *j) { return p; } -int job_serialize(Job *j, FILE *f, FDSet *fds) { +int job_serialize(Job *j, FILE *f) { + assert(j); + assert(f); + fprintf(f, "job-id=%u\n", j->id); fprintf(f, "job-type=%s\n", job_type_to_string(j->type)); fprintf(f, "job-state=%s\n", job_state_to_string(j->state)); @@ -1008,15 +1012,16 @@ int job_serialize(Job *j, FILE *f, FDSet *fds) { if (j->begin_usec > 0) fprintf(f, "job-begin="USEC_FMT"\n", j->begin_usec); - bus_track_serialize(j->clients, f); + bus_track_serialize(j->clients, f, "subscribed"); /* End marker */ fputc('\n', f); return 0; } -int job_deserialize(Job *j, FILE *f, FDSet *fds) { +int job_deserialize(Job *j, FILE *f) { assert(j); + assert(f); for (;;) { char line[LINE_MAX], *l, *v; @@ -1106,7 +1111,7 @@ int job_deserialize(Job *j, FILE *f, FDSet *fds) { } else if (streq(l, "subscribed")) { if (strv_extend(&j->deserialized_clients, v) < 0) - return log_oom(); + log_oom(); } } } @@ -1118,9 +1123,8 @@ int job_coldplug(Job *j) { /* After deserialization is complete and the bus connection * set up again, let's start watching our subscribers again */ - r = bus_track_coldplug(j->manager, &j->clients, &j->deserialized_clients); - if (r < 0) - return r; + (void) bus_track_coldplug(j->manager, &j->clients, false, j->deserialized_clients); + j->deserialized_clients = strv_free(j->deserialized_clients); if (j->state == JOB_WAITING) job_add_to_run_queue(j); diff --git a/src/core/job.h b/src/core/job.h index d359e8bb3e..85368f0d30 100644 --- a/src/core/job.h +++ b/src/core/job.h @@ -177,8 +177,8 @@ Job* job_install(Job *j); int job_install_deserialized(Job *j); void job_uninstall(Job *j); void job_dump(Job *j, FILE*f, const char *prefix); -int job_serialize(Job *j, FILE *f, FDSet *fds); -int job_deserialize(Job *j, FILE *f, FDSet *fds); +int job_serialize(Job *j, FILE *f); +int job_deserialize(Job *j, FILE *f); int job_coldplug(Job *j); JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts); diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index 6a5c16a000..af2f9d960b 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -19,9 +19,9 @@ m4_dnl Define the context options only once m4_define(`EXEC_CONTEXT_CONFIG_ITEMS', `$1.WorkingDirectory, config_parse_working_directory, 0, offsetof($1, exec_context) $1.RootDirectory, config_parse_unit_path_printf, 0, offsetof($1, exec_context.root_directory) -$1.User, config_parse_unit_string_printf, 0, offsetof($1, exec_context.user) -$1.Group, config_parse_unit_string_printf, 0, offsetof($1, exec_context.group) -$1.SupplementaryGroups, config_parse_strv, 0, offsetof($1, exec_context.supplementary_groups) +$1.User, config_parse_user_group, 0, offsetof($1, exec_context.user) +$1.Group, config_parse_user_group, 0, offsetof($1, exec_context.group) +$1.SupplementaryGroups, config_parse_user_group_strv, 0, offsetof($1, exec_context.supplementary_groups) $1.Nice, config_parse_exec_nice, 0, offsetof($1, exec_context) $1.OOMScoreAdjust, config_parse_exec_oom_score_adjust, 0, offsetof($1, exec_context) $1.IOSchedulingClass, config_parse_exec_io_class, 0, offsetof($1, exec_context) @@ -34,9 +34,10 @@ $1.UMask, config_parse_mode, 0, $1.Environment, config_parse_environ, 0, offsetof($1, exec_context.environment) $1.EnvironmentFile, config_parse_unit_env_file, 0, offsetof($1, exec_context.environment_files) $1.PassEnvironment, config_parse_pass_environ, 0, offsetof($1, exec_context.pass_environment) -$1.StandardInput, config_parse_input, 0, offsetof($1, exec_context.std_input) -$1.StandardOutput, config_parse_output, 0, offsetof($1, exec_context.std_output) -$1.StandardError, config_parse_output, 0, offsetof($1, exec_context.std_error) +$1.DynamicUser, config_parse_bool, 0, offsetof($1, exec_context.dynamic_user) +$1.StandardInput, config_parse_exec_input, 0, offsetof($1, exec_context) +$1.StandardOutput, config_parse_exec_output, 0, offsetof($1, exec_context) +$1.StandardError, config_parse_exec_output, 0, offsetof($1, exec_context) $1.TTYPath, config_parse_unit_path_printf, 0, offsetof($1, exec_context.tty_path) $1.TTYReset, config_parse_bool, 0, offsetof($1, exec_context.tty_reset) $1.TTYVHangup, config_parse_bool, 0, offsetof($1, exec_context.tty_vhangup) @@ -87,8 +88,12 @@ $1.ReadWritePaths, config_parse_namespace_path_strv, 0, $1.ReadOnlyPaths, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.read_only_paths) $1.InaccessiblePaths, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.inaccessible_paths) $1.PrivateTmp, config_parse_bool, 0, offsetof($1, exec_context.private_tmp) -$1.PrivateNetwork, config_parse_bool, 0, offsetof($1, exec_context.private_network) $1.PrivateDevices, config_parse_bool, 0, offsetof($1, exec_context.private_devices) +$1.ProtectKernelTunables, config_parse_bool, 0, offsetof($1, exec_context.protect_kernel_tunables) +$1.ProtectKernelModules, config_parse_bool, 0, offsetof($1, exec_context.protect_kernel_modules) +$1.ProtectControlGroups, config_parse_bool, 0, offsetof($1, exec_context.protect_control_groups) +$1.PrivateNetwork, config_parse_bool, 0, offsetof($1, exec_context.private_network) +$1.PrivateUsers, config_parse_bool, 0, offsetof($1, exec_context.private_users) $1.ProtectSystem, config_parse_protect_system, 0, offsetof($1, exec_context) $1.ProtectHome, config_parse_protect_home, 0, offsetof($1, exec_context) $1.MountFlags, config_parse_exec_mount_flags, 0, offsetof($1, exec_context) @@ -120,6 +125,8 @@ $1.KillSignal, config_parse_signal, 0, m4_define(`CGROUP_CONTEXT_CONFIG_ITEMS', `$1.Slice, config_parse_unit_slice, 0, 0 $1.CPUAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.cpu_accounting) +$1.CPUWeight, config_parse_cpu_weight, 0, offsetof($1, cgroup_context.cpu_weight) +$1.StartupCPUWeight, config_parse_cpu_weight, 0, offsetof($1, cgroup_context.startup_cpu_weight) $1.CPUShares, config_parse_cpu_shares, 0, offsetof($1, cgroup_context.cpu_shares) $1.StartupCPUShares, config_parse_cpu_shares, 0, offsetof($1, cgroup_context.startup_cpu_shares) $1.CPUQuota, config_parse_cpu_quota, 0, offsetof($1, cgroup_context) @@ -127,6 +134,7 @@ $1.MemoryAccounting, config_parse_bool, 0, $1.MemoryLow, config_parse_memory_limit, 0, offsetof($1, cgroup_context) $1.MemoryHigh, config_parse_memory_limit, 0, offsetof($1, cgroup_context) $1.MemoryMax, config_parse_memory_limit, 0, offsetof($1, cgroup_context) +$1.MemorySwapMax, config_parse_memory_limit, 0, offsetof($1, cgroup_context) $1.MemoryLimit, config_parse_memory_limit, 0, offsetof($1, cgroup_context) $1.DeviceAllow, config_parse_device_allow, 0, offsetof($1, cgroup_context) $1.DevicePolicy, config_parse_device_policy, 0, offsetof($1, cgroup_context.device_policy) @@ -180,13 +188,13 @@ Unit.OnFailureIsolate, config_parse_job_mode_isolate, 0, Unit.IgnoreOnIsolate, config_parse_bool, 0, offsetof(Unit, ignore_on_isolate) Unit.IgnoreOnSnapshot, config_parse_warn_compat, DISABLED_LEGACY, 0 Unit.JobTimeoutSec, config_parse_sec_fix_0, 0, offsetof(Unit, job_timeout) -Unit.JobTimeoutAction, config_parse_failure_action, 0, offsetof(Unit, job_timeout_action) +Unit.JobTimeoutAction, config_parse_emergency_action, 0, offsetof(Unit, job_timeout_action) Unit.JobTimeoutRebootArgument, config_parse_string, 0, offsetof(Unit, job_timeout_reboot_arg) Unit.StartLimitIntervalSec, config_parse_sec, 0, offsetof(Unit, start_limit.interval) m4_dnl The following is a legacy alias name for compatibility Unit.StartLimitInterval, config_parse_sec, 0, offsetof(Unit, start_limit.interval) Unit.StartLimitBurst, config_parse_unsigned, 0, offsetof(Unit, start_limit.burst) -Unit.StartLimitAction, config_parse_failure_action, 0, offsetof(Unit, start_limit_action) +Unit.StartLimitAction, config_parse_emergency_action, 0, offsetof(Unit, start_limit_action) Unit.RebootArgument, config_parse_string, 0, offsetof(Unit, reboot_arg) Unit.ConditionPathExists, config_parse_unit_condition_path, CONDITION_PATH_EXISTS, offsetof(Unit, conditions) Unit.ConditionPathExistsGlob, config_parse_unit_condition_path, CONDITION_PATH_EXISTS_GLOB, offsetof(Unit, conditions) @@ -243,9 +251,9 @@ Service.WatchdogSec, config_parse_sec, 0, m4_dnl The following three only exist for compatibility, they moved into Unit, see above Service.StartLimitInterval, config_parse_sec, 0, offsetof(Unit, start_limit.interval) Service.StartLimitBurst, config_parse_unsigned, 0, offsetof(Unit, start_limit.burst) -Service.StartLimitAction, config_parse_failure_action, 0, offsetof(Unit, start_limit_action) +Service.StartLimitAction, config_parse_emergency_action, 0, offsetof(Unit, start_limit_action) Service.RebootArgument, config_parse_string, 0, offsetof(Unit, reboot_arg) -Service.FailureAction, config_parse_failure_action, 0, offsetof(Service, failure_action) +Service.FailureAction, config_parse_emergency_action, 0, offsetof(Service, emergency_action) Service.Type, config_parse_service_type, 0, offsetof(Service, type) Service.Restart, config_parse_service_restart, 0, offsetof(Service, restart) Service.PermissionsStartOnly, config_parse_bool, 0, offsetof(Service, permissions_start_only) @@ -285,13 +293,14 @@ Socket.ExecStartPost, config_parse_exec, SOCKET_EXEC Socket.ExecStopPre, config_parse_exec, SOCKET_EXEC_STOP_PRE, offsetof(Socket, exec_command) Socket.ExecStopPost, config_parse_exec, SOCKET_EXEC_STOP_POST, offsetof(Socket, exec_command) Socket.TimeoutSec, config_parse_sec, 0, offsetof(Socket, timeout_usec) -Socket.SocketUser, config_parse_unit_string_printf, 0, offsetof(Socket, user) -Socket.SocketGroup, config_parse_unit_string_printf, 0, offsetof(Socket, group) +Socket.SocketUser, config_parse_user_group, 0, offsetof(Socket, user) +Socket.SocketGroup, config_parse_user_group, 0, offsetof(Socket, group) Socket.SocketMode, config_parse_mode, 0, offsetof(Socket, socket_mode) Socket.DirectoryMode, config_parse_mode, 0, offsetof(Socket, directory_mode) Socket.Accept, config_parse_bool, 0, offsetof(Socket, accept) Socket.Writable, config_parse_bool, 0, offsetof(Socket, writable) Socket.MaxConnections, config_parse_unsigned, 0, offsetof(Socket, max_connections) +Socket.MaxConnectionsPerSource, config_parse_unsigned, 0, offsetof(Socket, max_connections_per_source) Socket.KeepAlive, config_parse_bool, 0, offsetof(Socket, keep_alive) Socket.KeepAliveTimeSec, config_parse_sec, 0, offsetof(Socket, keep_alive_time) Socket.KeepAliveIntervalSec, config_parse_sec, 0, offsetof(Socket, keep_alive_interval) @@ -350,6 +359,8 @@ Mount.Type, config_parse_string, 0, Mount.TimeoutSec, config_parse_sec, 0, offsetof(Mount, timeout_usec) Mount.DirectoryMode, config_parse_mode, 0, offsetof(Mount, directory_mode) Mount.SloppyOptions, config_parse_bool, 0, offsetof(Mount, sloppy_options) +Mount.LazyUnmount, config_parse_bool, 0, offsetof(Mount, lazy_unmount) +Mount.ForceUnmount, config_parse_bool, 0, offsetof(Mount, force_unmount) EXEC_CONTEXT_CONFIG_ITEMS(Mount)m4_dnl CGROUP_CONTEXT_CONFIG_ITEMS(Mount)m4_dnl KILL_CONTEXT_CONFIG_ITEMS(Mount)m4_dnl diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index a36953f766..cbc826809e 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -64,6 +64,7 @@ #include "unit-name.h" #include "unit-printf.h" #include "unit.h" +#include "user-util.h" #include "utf8.h" #include "web-util.h" @@ -490,16 +491,17 @@ int config_parse_socket_bind(const char *unit, return 0; } -int config_parse_exec_nice(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) { +int config_parse_exec_nice( + 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) { ExecContext *c = data; int priority, r; @@ -509,14 +511,13 @@ int config_parse_exec_nice(const char *unit, assert(rvalue); assert(data); - r = safe_atoi(rvalue, &priority); + r = parse_nice(rvalue, &priority); if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse nice priority, ignoring: %s", rvalue); - return 0; - } + if (r == -ERANGE) + log_syntax(unit, LOG_ERR, filename, line, r, "Nice priority out of range, ignoring: %s", rvalue); + else + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse nice priority, ignoring: %s", rvalue); - if (priority < PRIO_MIN || priority >= PRIO_MAX) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Nice priority out of range, ignoring: %s", rvalue); return 0; } @@ -775,8 +776,104 @@ int config_parse_socket_bindtodevice( return 0; } -DEFINE_CONFIG_PARSE_ENUM(config_parse_output, exec_output, ExecOutput, "Failed to parse output specifier"); -DEFINE_CONFIG_PARSE_ENUM(config_parse_input, exec_input, ExecInput, "Failed to parse input specifier"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_input, exec_input, ExecInput, "Failed to parse input literal specifier"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_output, exec_output, ExecOutput, "Failed to parse output literal specifier"); + +int config_parse_exec_input(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) { + ExecContext *c = data; + const char *name; + int r; + + assert(data); + assert(filename); + assert(line); + assert(rvalue); + + name = startswith(rvalue, "fd:"); + if (name) { + /* Strip prefix and validate fd name */ + if (!fdname_is_valid(name)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid file descriptor name, ignoring: %s", name); + return 0; + } + c->std_input = EXEC_INPUT_NAMED_FD; + r = free_and_strdup(&c->stdio_fdname[STDIN_FILENO], name); + if (r < 0) + log_oom(); + return r; + } else { + ExecInput ei = exec_input_from_string(rvalue); + if (ei == _EXEC_INPUT_INVALID) + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse input specifier, ignoring: %s", rvalue); + else + c->std_input = ei; + return 0; + } +} + +int config_parse_exec_output(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) { + ExecContext *c = data; + ExecOutput eo; + const char *name; + int r; + + assert(data); + assert(filename); + assert(line); + assert(lvalue); + assert(rvalue); + + name = startswith(rvalue, "fd:"); + if (name) { + /* Strip prefix and validate fd name */ + if (!fdname_is_valid(name)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid file descriptor name, ignoring: %s", name); + return 0; + } + eo = EXEC_OUTPUT_NAMED_FD; + } else { + eo = exec_output_from_string(rvalue); + if (eo == _EXEC_OUTPUT_INVALID) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse output specifier, ignoring: %s", rvalue); + return 0; + } + } + + if (streq(lvalue, "StandardOutput")) { + c->std_output = eo; + r = free_and_strdup(&c->stdio_fdname[STDOUT_FILENO], name); + if (r < 0) + log_oom(); + return r; + } else if (streq(lvalue, "StandardError")) { + c->std_error = eo; + r = free_and_strdup(&c->stdio_fdname[STDERR_FILENO], name); + if (r < 0) + log_oom(); + return r; + } else { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse output property, ignoring: %s", lvalue); + return 0; + } +} int config_parse_exec_io_class(const char *unit, const char *filename, @@ -1337,10 +1434,13 @@ int config_parse_timer(const char *unit, void *userdata) { Timer *t = data; - usec_t u = 0; + usec_t usec = 0; TimerValue *v; TimerBase b; CalendarSpec *c = NULL; + Unit *u = userdata; + _cleanup_free_ char *k = NULL; + int r; assert(filename); assert(lvalue); @@ -1359,14 +1459,20 @@ int config_parse_timer(const char *unit, return 0; } + r = unit_full_printf(u, rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers in %s, ignoring: %m", rvalue); + return 0; + } + if (b == TIMER_CALENDAR) { - if (calendar_spec_from_string(rvalue, &c) < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse calendar specification, ignoring: %s", rvalue); + if (calendar_spec_from_string(k, &c) < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse calendar specification, ignoring: %s", k); return 0; } } else { - if (parse_sec(rvalue, &u) < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse timer value, ignoring: %s", rvalue); + if (parse_sec(k, &usec) < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse timer value, ignoring: %s", k); return 0; } } @@ -1378,7 +1484,7 @@ int config_parse_timer(const char *unit, } v->base = b; - v->value = u; + v->value = usec; v->calendar_spec = c; LIST_PREPEND(value, t->values, v); @@ -1581,11 +1687,7 @@ int config_parse_fdname( return 0; } - free(s->fdname); - s->fdname = p; - p = NULL; - - return 0; + return free_and_replace(s->fdname, p); } int config_parse_service_sockets( @@ -1763,6 +1865,123 @@ int config_parse_sec_fix_0( return 0; } +int config_parse_user_group( + 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) { + + char **user = data, *n; + Unit *u = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + if (isempty(rvalue)) + n = NULL; + else { + _cleanup_free_ char *k = NULL; + + r = unit_full_printf(u, rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers in %s, ignoring: %m", rvalue); + return 0; + } + + if (!valid_user_group_name_or_id(k)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid user/group name or numeric ID, ignoring: %s", k); + return 0; + } + + n = k; + k = NULL; + } + + free(*user); + *user = n; + + return 0; +} + +int config_parse_user_group_strv( + 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) { + + char ***users = data; + Unit *u = userdata; + const char *p; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + if (isempty(rvalue)) { + char **empty; + + empty = new0(char*, 1); + if (!empty) + return log_oom(); + + strv_free(*users); + *users = empty; + + return 0; + } + + p = rvalue; + for (;;) { + _cleanup_free_ char *word = NULL, *k = NULL; + + r = extract_first_word(&p, &word, WHITESPACE, 0); + if (r == 0) + break; + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Invalid syntax, ignoring: %s", rvalue); + break; + } + + r = unit_full_printf(u, word, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers in %s, ignoring: %m", word); + continue; + } + + if (!valid_user_group_name_or_id(k)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid user/group name or numeric ID, ignoring: %s", k); + continue; + } + + r = strv_push(users, k); + if (r < 0) + return log_oom(); + + k = NULL; + } + + return 0; +} + int config_parse_busname_service( const char *unit, const char *filename, @@ -1925,9 +2144,7 @@ int config_parse_working_directory( return 0; } - free(c->working_directory); - c->working_directory = k; - k = NULL; + free_and_replace(c->working_directory, k); c->working_directory_home = false; } @@ -2306,7 +2523,7 @@ int config_parse_unit_condition_null( } DEFINE_CONFIG_PARSE_ENUM(config_parse_notify_access, notify_access, NotifyAccess, "Failed to parse notify access specifier"); -DEFINE_CONFIG_PARSE_ENUM(config_parse_failure_action, failure_action, FailureAction, "Failed to parse failure action specifier"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_emergency_action, emergency_action, EmergencyAction, "Failed to parse failure action specifier"); int config_parse_unit_requires_mounts_for( const char *unit, @@ -2401,6 +2618,7 @@ int config_parse_documentation(const char *unit, } #ifdef HAVE_SECCOMP + static int syscall_filter_parse_one( const char *unit, const char *filename, @@ -2411,27 +2629,29 @@ static int syscall_filter_parse_one( bool warn) { int r; - if (*t == '@') { - const SystemCallFilterSet *set; + if (t[0] == '@') { + const SyscallFilterSet *set; + const char *i; - for (set = syscall_filter_sets; set->set_name; set++) - if (streq(set->set_name, t)) { - const char *sys; + set = syscall_filter_set_find(t); + if (!set) { + if (warn) + log_syntax(unit, LOG_WARNING, filename, line, 0, "Don't know system call group, ignoring: %s", t); + return 0; + } - NULSTR_FOREACH(sys, set->value) { - r = syscall_filter_parse_one(unit, filename, line, c, invert, sys, false); - if (r < 0) - return r; - } - break; - } + NULSTR_FOREACH(i, set->value) { + r = syscall_filter_parse_one(unit, filename, line, c, invert, i, false); + if (r < 0) + return r; + } } else { int id; id = seccomp_syscall_resolve_name(t); if (id == __NR_SCMP_ERROR) { if (warn) - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse system call, ignoring: %s", t); + log_syntax(unit, LOG_WARNING, filename, line, 0, "Failed to parse system call, ignoring: %s", t); return 0; } @@ -2445,8 +2665,9 @@ static int syscall_filter_parse_one( if (r < 0) return log_oom(); } else - set_remove(c->syscall_filter, INT_TO_PTR(id + 1)); + (void) set_remove(c->syscall_filter, INT_TO_PTR(id + 1)); } + return 0; } @@ -2465,8 +2686,7 @@ int config_parse_syscall_filter( ExecContext *c = data; Unit *u = userdata; bool invert = false; - const char *word, *state; - size_t l; + const char *p; int r; assert(filename); @@ -2505,24 +2725,24 @@ int config_parse_syscall_filter( } } - FOREACH_WORD_QUOTED(word, l, rvalue, state) { - _cleanup_free_ char *t = NULL; + p = rvalue; + for (;;) { + _cleanup_free_ char *word = NULL; - t = strndup(word, l); - if (!t) + r = extract_first_word(&p, &word, NULL, 0); + if (r == 0) + break; + if (r == -ENOMEM) return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid syntax, ignoring: %s", rvalue); + break; + } - r = syscall_filter_parse_one(unit, filename, line, c, invert, t, true); + r = syscall_filter_parse_one(unit, filename, line, c, invert, word, true); if (r < 0) return r; } - if (!isempty(state)) - log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); - - /* Turn on NNP, but only if it wasn't configured explicitly - * before, and only if we are in user mode. */ - if (!c->no_new_privileges_set && MANAGER_IS_USER(u->manager)) - c->no_new_privileges = true; return 0; } @@ -2733,6 +2953,34 @@ int config_parse_unit_slice( DEFINE_CONFIG_PARSE_ENUM(config_parse_device_policy, cgroup_device_policy, CGroupDevicePolicy, "Failed to parse device policy"); +int config_parse_cpu_weight( + 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) { + + uint64_t *weight = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = cg_weight_parse(rvalue, weight); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "CPU weight '%s' invalid. Ignoring.", rvalue); + return 0; + } + + return 0; +} + int config_parse_cpu_shares( const char *unit, const char *filename, @@ -2785,7 +3033,7 @@ int config_parse_cpu_quota( return 0; } - r = parse_percent(rvalue); + r = parse_percent_unbounded(rvalue); if (r <= 0) { log_syntax(unit, LOG_ERR, filename, line, r, "CPU quota '%s' invalid. Ignoring.", rvalue); return 0; @@ -2835,8 +3083,12 @@ int config_parse_memory_limit( c->memory_high = bytes; else if (streq(lvalue, "MemoryMax")) c->memory_max = bytes; - else + else if (streq(lvalue, "MemorySwapMax")) + c->memory_swap_max = bytes; + else if (streq(lvalue, "MemoryLimit")) c->memory_limit = bytes; + else + return -EINVAL; return 0; } @@ -2853,30 +3105,36 @@ int config_parse_tasks_max( void *data, void *userdata) { - uint64_t *tasks_max = data, u; + uint64_t *tasks_max = data, v; + Unit *u = userdata; int r; - if (isempty(rvalue) || streq(rvalue, "infinity")) { - *tasks_max = (uint64_t) -1; + if (isempty(rvalue)) { + *tasks_max = u->manager->default_tasks_max; + return 0; + } + + if (streq(rvalue, "infinity")) { + *tasks_max = CGROUP_LIMIT_MAX; return 0; } r = parse_percent(rvalue); if (r < 0) { - r = safe_atou64(rvalue, &u); + r = safe_atou64(rvalue, &v); if (r < 0) { log_syntax(unit, LOG_ERR, filename, line, r, "Maximum tasks value '%s' invalid. Ignoring.", rvalue); return 0; } } else - u = system_tasks_max_scale(r, 100U); + v = system_tasks_max_scale(r, 100U); - if (u <= 0 || u >= UINT64_MAX) { + if (v <= 0 || v >= UINT64_MAX) { log_syntax(unit, LOG_ERR, filename, line, 0, "Maximum tasks value '%s' out of range. Ignoring.", rvalue); return 0; } - *tasks_max = u; + *tasks_max = v; return 0; } @@ -2919,9 +3177,7 @@ int config_parse_device_allow( if (!path) return log_oom(); - if (!startswith(path, "/dev/") && - !startswith(path, "block-") && - !startswith(path, "char-")) { + if (!is_deviceallow_pattern(path)) { log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid device node path '%s'. Ignoring.", path); return 0; } @@ -3576,7 +3832,7 @@ int config_parse_no_new_privileges( return 0; } - c->no_new_privileges = !!k; + c->no_new_privileges = k; c->no_new_privileges_set = true; return 0; @@ -4026,8 +4282,8 @@ void unit_dump_config_items(FILE *f) { { config_parse_exec_cpu_affinity, "CPUAFFINITY" }, { config_parse_mode, "MODE" }, { config_parse_unit_env_file, "FILE" }, - { config_parse_output, "OUTPUT" }, - { config_parse_input, "INPUT" }, + { config_parse_exec_output, "OUTPUT" }, + { config_parse_exec_input, "INPUT" }, { config_parse_log_facility, "FACILITY" }, { config_parse_log_level, "LEVEL" }, { config_parse_exec_secure_bits, "SECUREBITS" }, @@ -4062,7 +4318,7 @@ void unit_dump_config_items(FILE *f) { { config_parse_unit_slice, "SLICE" }, { config_parse_documentation, "URL" }, { config_parse_service_timeout, "SECONDS" }, - { config_parse_failure_action, "ACTION" }, + { config_parse_emergency_action, "ACTION" }, { config_parse_set_status, "STATUS" }, { config_parse_service_sockets, "SOCKETS" }, { config_parse_environ, "ENVIRON" }, @@ -4073,6 +4329,7 @@ void unit_dump_config_items(FILE *f) { { config_parse_address_families, "FAMILIES" }, #endif { config_parse_cpu_shares, "SHARES" }, + { config_parse_cpu_weight, "WEIGHT" }, { config_parse_memory_limit, "LIMIT" }, { config_parse_device_allow, "DEVICE" }, { config_parse_device_policy, "POLICY" }, diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index b36a2e3a02..c05f205c37 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -45,7 +45,9 @@ int config_parse_service_timeout(const char *unit, const char *filename, unsigne int config_parse_service_type(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); int config_parse_service_restart(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); int config_parse_socket_bindtodevice(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); +int config_parse_exec_output(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); int config_parse_output(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); +int config_parse_exec_input(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); int config_parse_input(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); int config_parse_exec_io_class(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); int config_parse_exec_io_priority(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); @@ -73,7 +75,7 @@ int config_parse_unit_condition_string(const char *unit, const char *filename, u int config_parse_unit_condition_null(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); int config_parse_kill_mode(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); int config_parse_notify_access(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); -int config_parse_failure_action(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); +int config_parse_emergency_action(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); int config_parse_unit_requires_mounts_for(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); int config_parse_syscall_filter(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); int config_parse_syscall_archs(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); @@ -81,6 +83,7 @@ int config_parse_syscall_errno(const char *unit, const char *filename, unsigned int config_parse_environ(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); int config_parse_pass_environ(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); int config_parse_unit_slice(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); +int config_parse_cpu_weight(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); int config_parse_cpu_shares(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); int config_parse_memory_limit(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); int config_parse_tasks_max(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); @@ -111,6 +114,8 @@ int config_parse_exec_utmp_mode(const char *unit, const char *filename, unsigned int config_parse_working_directory(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); int config_parse_fdname(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); int config_parse_sec_fix_0(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); +int config_parse_user_group(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); +int config_parse_user_group_strv(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); /* gperf prototypes */ const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, unsigned length); diff --git a/src/core/main.c b/src/core/main.c index 33e22e37dc..f07ed71b31 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -72,6 +72,9 @@ #include "process-util.h" #include "raw-clone.h" #include "rlimit-util.h" +#ifdef HAVE_SECCOMP +#include "seccomp-util.h" +#endif #include "selinux-setup.h" #include "selinux-util.h" #include "signal-util.h" @@ -86,14 +89,14 @@ #include "user-util.h" #include "virt.h" #include "watchdog.h" +#include "emergency-action.h" static enum { ACTION_RUN, ACTION_HELP, ACTION_VERSION, ACTION_TEST, - ACTION_DUMP_CONFIGURATION_ITEMS, - ACTION_DONE + ACTION_DUMP_CONFIGURATION_ITEMS } arg_action = ACTION_RUN; static char *arg_default_unit = NULL; static bool arg_system = false; @@ -129,6 +132,7 @@ static bool arg_default_memory_accounting = false; static bool arg_default_tasks_accounting = true; static uint64_t arg_default_tasks_max = UINT64_MAX; static sd_id128_t arg_machine_id = {}; +static EmergencyAction arg_cad_burst_action = EMERGENCY_ACTION_REBOOT_FORCE; noreturn static void freeze_or_reboot(void) { @@ -200,7 +204,7 @@ noreturn static void crash(int sig) { pid, sigchld_code_to_string(status.si_code), status.si_status, strna(status.si_code == CLD_EXITED - ? exit_status_to_string(status.si_status, EXIT_STATUS_FULL) + ? exit_status_to_string(status.si_status, EXIT_STATUS_MINIMAL) : signal_to_string(status.si_status))); else log_emergency("Caught <%s>, dumped core as pid "PID_FMT".", signal_to_string(sig), pid); @@ -304,7 +308,7 @@ static int set_machine_id(const char *m) { return 0; } -static int parse_proc_cmdline_item(const char *key, const char *value) { +static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { int r; @@ -700,6 +704,7 @@ static int parse_config_file(void) { { "Manager", "DefaultMemoryAccounting", config_parse_bool, 0, &arg_default_memory_accounting }, { "Manager", "DefaultTasksAccounting", config_parse_bool, 0, &arg_default_tasks_accounting }, { "Manager", "DefaultTasksMax", config_parse_tasks_max, 0, &arg_default_tasks_max }, + { "Manager", "CtrlAltDelBurstAction", config_parse_emergency_action, 0, &arg_cad_burst_action }, {} }; @@ -713,7 +718,7 @@ static int parse_config_file(void) { CONF_PATHS_NULSTR("systemd/system.conf.d") : CONF_PATHS_NULSTR("systemd/user.conf.d"); - config_parse_many(fn, conf_dirs_nulstr, "Manager\0", config_item_table_lookup, items, false, NULL); + config_parse_many_nulstr(fn, conf_dirs_nulstr, "Manager\0", config_item_table_lookup, items, false, NULL); /* Traditionally "0" was used to turn off the default unit timeouts. Fix this up so that we used USEC_INFINITY * like everywhere else. */ @@ -994,10 +999,8 @@ static int parse_argv(int argc, char *argv[]) { case ARG_MACHINE_ID: r = set_machine_id(optarg); - if (r < 0) { - log_error("MachineID '%s' is not valid.", optarg); - return r; - } + if (r < 0) + return log_error_errno(r, "MachineID '%s' is not valid.", optarg); break; case 'h': @@ -1120,7 +1123,7 @@ static int bump_rlimit_nofile(struct rlimit *saved_rlimit) { * later when transitioning from the initrd to the main * systemd or suchlike. */ if (getrlimit(RLIMIT_NOFILE, saved_rlimit) < 0) - return log_error_errno(errno, "Reading RLIMIT_NOFILE failed: %m"); + return log_warning_errno(errno, "Reading RLIMIT_NOFILE failed, ignoring: %m"); /* Make sure forked processes get the default kernel setting */ if (!arg_default_rlimit[RLIMIT_NOFILE]) { @@ -1137,7 +1140,7 @@ static int bump_rlimit_nofile(struct rlimit *saved_rlimit) { nl.rlim_cur = nl.rlim_max = 64*1024; r = setrlimit_closest(RLIMIT_NOFILE, &nl); if (r < 0) - return log_error_errno(r, "Setting RLIMIT_NOFILE failed: %m"); + return log_warning_errno(r, "Setting RLIMIT_NOFILE failed, ignoring: %m"); return 0; } @@ -1187,6 +1190,9 @@ static int enforce_syscall_archs(Set *archs) { void *id; int r; + if (!is_seccomp_available()) + return 0; + seccomp = seccomp_init(SCMP_ACT_ALLOW); if (!seccomp) return log_oom(); @@ -1319,7 +1325,7 @@ static int fixup_environment(void) { return r; if (r == 0) { - term = strdup(default_term_for_tty("/dev/console") + 5); + term = strdup(default_term_for_tty("/dev/console")); if (!term) return -ENOMEM; } @@ -1415,12 +1421,12 @@ int main(int argc, char *argv[]) { if (mac_selinux_setup(&loaded_policy) < 0) { error_message = "Failed to load SELinux policy"; goto finish; - } else if (ima_setup() < 0) { - error_message = "Failed to load IMA policy"; - goto finish; } else if (mac_smack_setup(&loaded_policy) < 0) { error_message = "Failed to load SMACK policy"; goto finish; + } else if (ima_setup() < 0) { + error_message = "Failed to load IMA policy"; + goto finish; } dual_timestamp_get(&security_finish_timestamp); } @@ -1506,7 +1512,8 @@ int main(int argc, char *argv[]) { if (getpid() == 1) { /* Don't limit the core dump size, so that coredump handlers such as systemd-coredump (which honour the limit) * will process core dumps for system services by default. */ - (void) setrlimit(RLIMIT_CORE, &RLIMIT_MAKE_CONST(RLIM_INFINITY)); + if (setrlimit(RLIMIT_CORE, &RLIMIT_MAKE_CONST(RLIM_INFINITY)) < 0) + log_warning_errno(errno, "Failed to set RLIMIT_CORE: %m"); /* But at the same time, turn off the core_pattern logic by default, so that no coredumps are stored * until the systemd-coredump tool is enabled via sysctl. */ @@ -1524,15 +1531,9 @@ int main(int argc, char *argv[]) { * need to do that for user instances since they never log * into the console. */ log_show_color(colors_enabled()); - make_null_stdio(); - } - - /* Initialize default unit */ - r = free_and_strdup(&arg_default_unit, SPECIAL_DEFAULT_TARGET); - if (r < 0) { - log_emergency_errno(r, "Failed to set default unit %s: %m", SPECIAL_DEFAULT_TARGET); - error_message = "Failed to set default unit"; - goto finish; + r = make_null_stdio(); + if (r < 0) + log_warning_errno(r, "Failed to redirect standard streams to /dev/null: %m"); } r = initialize_join_controllers(); @@ -1560,7 +1561,7 @@ int main(int argc, char *argv[]) { (void) reset_all_signal_handlers(); (void) ignore_signals(SIGNALS_IGNORE, -1); - arg_default_tasks_max = system_tasks_max_scale(15U, 100U); /* 15% the system PIDs equals 4915 by default. */ + arg_default_tasks_max = system_tasks_max_scale(DEFAULT_TASKS_MAX_PERCENTAGE, 100U); if (parse_config_file() < 0) { error_message = "Failed to parse config file"; @@ -1568,7 +1569,7 @@ int main(int argc, char *argv[]) { } if (arg_system) { - r = parse_proc_cmdline(parse_proc_cmdline_item); + r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, false); if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); } @@ -1582,6 +1583,16 @@ int main(int argc, char *argv[]) { goto finish; } + /* Initialize default unit */ + if (!arg_default_unit) { + arg_default_unit = strdup(SPECIAL_DEFAULT_TARGET); + if (!arg_default_unit) { + r = log_oom(); + error_message = "Failed to set default unit"; + goto finish; + } + } + if (arg_action == ACTION_TEST && geteuid() == 0) { log_error("Don't run test mode as root."); @@ -1602,11 +1613,10 @@ int main(int argc, char *argv[]) { goto finish; } - if (arg_action == ACTION_TEST) - skip_setup = true; - - if (arg_action == ACTION_TEST || arg_action == ACTION_HELP) + if (arg_action == ACTION_TEST || arg_action == ACTION_HELP) { pager_open(arg_no_pager, false); + skip_setup = true; + } if (arg_action == ACTION_HELP) { retval = help(); @@ -1615,12 +1625,10 @@ int main(int argc, char *argv[]) { retval = version(); goto finish; } else if (arg_action == ACTION_DUMP_CONFIGURATION_ITEMS) { + pager_open(arg_no_pager, false); unit_dump_config_items(stdout); retval = EXIT_SUCCESS; goto finish; - } else if (arg_action == ACTION_DONE) { - retval = EXIT_SUCCESS; - goto finish; } if (!arg_system && @@ -1766,10 +1774,10 @@ int main(int argc, char *argv[]) { log_warning_errno(errno, "Failed to make us a subreaper: %m"); if (arg_system) { - bump_rlimit_nofile(&saved_rlimit_nofile); + (void) bump_rlimit_nofile(&saved_rlimit_nofile); if (empty_etc) { - r = unit_file_preset_all(UNIT_FILE_SYSTEM, false, NULL, UNIT_FILE_PRESET_ENABLE_ONLY, false, NULL, 0); + r = unit_file_preset_all(UNIT_FILE_SYSTEM, 0, NULL, UNIT_FILE_PRESET_ENABLE_ONLY, NULL, 0); if (r < 0) log_full_errno(r == -EEXIST ? LOG_NOTICE : LOG_WARNING, r, "Failed to populate /etc with preset unit settings, ignoring: %m"); else @@ -1792,6 +1800,7 @@ int main(int argc, char *argv[]) { m->initrd_timestamp = initrd_timestamp; m->security_start_timestamp = security_start_timestamp; m->security_finish_timestamp = security_finish_timestamp; + m->cad_burst_action = arg_cad_burst_action; manager_set_defaults(m); manager_set_show_status(m, arg_show_status); diff --git a/src/core/manager.c b/src/core/manager.c index 85bf858992..ffccfdcd5e 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -45,6 +45,7 @@ #include "bus-error.h" #include "bus-kernel.h" #include "bus-util.h" +#include "clean-ipc.h" #include "dbus-job.h" #include "dbus-manager.h" #include "dbus-unit.h" @@ -81,6 +82,7 @@ #include "transaction.h" #include "umask-util.h" #include "unit-name.h" +#include "user-util.h" #include "util.h" #include "virt.h" #include "watchdog.h" @@ -98,6 +100,7 @@ static int manager_dispatch_cgroups_agent_fd(sd_event_source *source, int fd, ui static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata); static int manager_dispatch_time_change_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata); static int manager_dispatch_idle_pipe_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata); +static int manager_dispatch_user_lookup_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata); static int manager_dispatch_jobs_in_progress(sd_event_source *source, usec_t usec, void *userdata); static int manager_dispatch_run_queue(sd_event_source *source, void *userdata); static int manager_run_generators(Manager *m); @@ -519,6 +522,7 @@ static void manager_clean_environment(Manager *m) { "LISTEN_FDNAMES", "WATCHDOG_PID", "WATCHDOG_USEC", + "INVOCATION_ID", NULL); } @@ -553,7 +557,6 @@ static int manager_default_environment(Manager *m) { return 0; } - int manager_new(UnitFileScope scope, bool test_run, Manager **_m) { Manager *m; int r; @@ -580,17 +583,25 @@ int manager_new(UnitFileScope scope, bool test_run, Manager **_m) { if (MANAGER_IS_SYSTEM(m)) { m->unit_log_field = "UNIT="; m->unit_log_format_string = "UNIT=%s"; + + m->invocation_log_field = "INVOCATION_ID="; + m->invocation_log_format_string = "INVOCATION_ID=" SD_ID128_FORMAT_STR; } else { m->unit_log_field = "USER_UNIT="; m->unit_log_format_string = "USER_UNIT=%s"; + + m->invocation_log_field = "USER_INVOCATION_ID="; + m->invocation_log_format_string = "USER_INVOCATION_ID=" SD_ID128_FORMAT_STR; } m->idle_pipe[0] = m->idle_pipe[1] = m->idle_pipe[2] = m->idle_pipe[3] = -1; m->pin_cgroupfs_fd = m->notify_fd = m->cgroups_agent_fd = m->signal_fd = m->time_change_fd = - m->dev_autofs_fd = m->private_listen_fd = m->kdbus_fd = m->cgroup_inotify_fd = + m->dev_autofs_fd = m->private_listen_fd = m->cgroup_inotify_fd = m->ask_password_inotify_fd = -1; + m->user_lookup_fds[0] = m->user_lookup_fds[1] = -1; + m->current_job_id = 1; /* start as id #1, so that we can leave #0 around as "null-like" value */ m->have_ask_password = -EINVAL; /* we don't know */ @@ -657,9 +668,8 @@ int manager_new(UnitFileScope scope, bool test_run, Manager **_m) { goto fail; } - /* Note that we set up neither kdbus, nor the notify fd - * here. We do that after deserialization, since they might - * have gotten serialized across the reexec. */ + /* Note that we do not set up the notify fd here. We do that after deserialization, + * since they might have gotten serialized across the reexec. */ m->taint_usr = dir_is_empty("/usr") > 0; @@ -767,7 +777,7 @@ static int manager_setup_cgroups_agent(Manager *m) { if (!MANAGER_IS_SYSTEM(m)) return 0; - if (cg_unified() > 0) /* We don't need this anymore on the unified hierarchy */ + if (cg_unified(SYSTEMD_CGROUP_CONTROLLER) > 0) /* We don't need this anymore on the unified hierarchy */ return 0; if (m->cgroups_agent_fd < 0) { @@ -813,6 +823,59 @@ static int manager_setup_cgroups_agent(Manager *m) { return 0; } +static int manager_setup_user_lookup_fd(Manager *m) { + int r; + + assert(m); + + /* Set up the socket pair used for passing UID/GID resolution results from forked off processes to PID + * 1. Background: we can't do name lookups (NSS) from PID 1, since it might involve IPC and thus activation, + * and we might hence deadlock on ourselves. Hence we do all user/group lookups asynchronously from the forked + * off processes right before executing the binaries to start. In order to be able to clean up any IPC objects + * created by a unit (see RemoveIPC=) we need to know in PID 1 the used UID/GID of the executed processes, + * hence we establish this communication channel so that forked off processes can pass their UID/GID + * information back to PID 1. The forked off processes send their resolved UID/GID to PID 1 in a simple + * datagram, along with their unit name, so that we can share one communication socket pair among all units for + * this purpose. + * + * You might wonder why we need a communication channel for this that is independent of the usual notification + * socket scheme (i.e. $NOTIFY_SOCKET). The primary difference is about trust: data sent via the $NOTIFY_SOCKET + * channel is only accepted if it originates from the right unit and if reception was enabled for it. The user + * lookup socket OTOH is only accessible by PID 1 and its children until they exec(), and always available. + * + * Note that this function is called under two circumstances: when we first initialize (in which case we + * allocate both the socket pair and the event source to listen on it), and when we deserialize after a reload + * (in which case the socket pair already exists but we still need to allocate the event source for it). */ + + if (m->user_lookup_fds[0] < 0) { + + /* Free all secondary fields */ + safe_close_pair(m->user_lookup_fds); + m->user_lookup_event_source = sd_event_source_unref(m->user_lookup_event_source); + + if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, m->user_lookup_fds) < 0) + return log_error_errno(errno, "Failed to allocate user lookup socket: %m"); + + (void) fd_inc_rcvbuf(m->user_lookup_fds[0], NOTIFY_RCVBUF_SIZE); + } + + if (!m->user_lookup_event_source) { + r = sd_event_add_io(m->event, &m->user_lookup_event_source, m->user_lookup_fds[0], EPOLLIN, manager_dispatch_user_lookup_fd, m); + if (r < 0) + return log_error_errno(errno, "Failed to allocate user lookup event source: %m"); + + /* Process even earlier than the notify event source, so that we always know first about valid UID/GID + * resolutions */ + r = sd_event_source_set_priority(m->user_lookup_event_source, SD_EVENT_PRIORITY_NORMAL-8); + if (r < 0) + return log_error_errno(errno, "Failed to set priority ot user lookup event source: %m"); + + (void) sd_event_source_set_description(m->user_lookup_event_source, "user-lookup"); + } + + return 0; +} + static int manager_connect_bus(Manager *m, bool reexecuting) { bool try_bus_connect; @@ -822,7 +885,6 @@ static int manager_connect_bus(Manager *m, bool reexecuting) { return 0; try_bus_connect = - m->kdbus_fd >= 0 || reexecuting || (MANAGER_IS_USER(m) && getenv("DBUS_SESSION_BUS_ADDRESS")); @@ -854,8 +916,7 @@ enum { _GC_OFFSET_MAX }; -static void unit_gc_mark_good(Unit *u, unsigned gc_marker) -{ +static void unit_gc_mark_good(Unit *u, unsigned gc_marker) { Iterator i; Unit *other; @@ -1004,7 +1065,11 @@ Manager* manager_free(Manager *m) { bus_done(m); + dynamic_user_vacuum(m, false); + hashmap_free(m->dynamic_users); + hashmap_free(m->units); + hashmap_free(m->units_by_invocation_id); hashmap_free(m->jobs); hashmap_free(m->watch_pids1); hashmap_free(m->watch_pids2); @@ -1019,12 +1084,13 @@ Manager* manager_free(Manager *m) { sd_event_source_unref(m->time_change_event_source); sd_event_source_unref(m->jobs_in_progress_event_source); sd_event_source_unref(m->run_queue_event_source); + sd_event_source_unref(m->user_lookup_event_source); safe_close(m->signal_fd); safe_close(m->notify_fd); safe_close(m->cgroups_agent_fd); safe_close(m->time_change_fd); - safe_close(m->kdbus_fd); + safe_close_pair(m->user_lookup_fds); manager_close_ask_password(m); @@ -1050,8 +1116,10 @@ Manager* manager_free(Manager *m) { assert(hashmap_isempty(m->units_requiring_mounts_for)); hashmap_free(m->units_requiring_mounts_for); - free(m); - return NULL; + hashmap_free(m->uid_refs); + hashmap_free(m->gid_refs); + + return mfree(m); } void manager_enumerate(Manager *m) { @@ -1175,9 +1243,11 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds) { return r; /* Make sure the transient directory always exists, so that it remains in the search path */ - r = mkdir_p_label(m->lookup_paths.transient, 0755); - if (r < 0) - return r; + if (!m->test_run) { + r = mkdir_p_label(m->lookup_paths.transient, 0755); + if (r < 0) + return r; + } dual_timestamp_get(&m->generators_start_timestamp); r = manager_run_generators(m); @@ -1219,14 +1289,26 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds) { if (q < 0 && r == 0) r = q; - /* We might have deserialized the kdbus control fd, but if we - * didn't, then let's create the bus now. */ - manager_connect_bus(m, !!serialization); - bus_track_coldplug(m, &m->subscribed, &m->deserialized_subscribed); + q = manager_setup_user_lookup_fd(m); + if (q < 0 && r == 0) + r = q; + + /* Let's connect to the bus now. */ + (void) manager_connect_bus(m, !!serialization); + + (void) bus_track_coldplug(m, &m->subscribed, false, m->deserialized_subscribed); + m->deserialized_subscribed = strv_free(m->deserialized_subscribed); /* Third, fire things up! */ manager_coldplug(m); + /* Release any dynamic users no longer referenced */ + dynamic_user_vacuum(m, true); + + /* Release any references to UIDs/GIDs no longer referenced, and destroy any IPC owned by them */ + manager_vacuum_uid_refs(m); + manager_vacuum_gid_refs(m); + if (serialization) { assert(m->n_reloading > 0); m->n_reloading--; @@ -1599,8 +1681,14 @@ static void manager_invoke_notify_message(Manager *m, Unit *u, pid_t pid, const if (UNIT_VTABLE(u)->notify_message) UNIT_VTABLE(u)->notify_message(u, pid, tags, fds); - else - log_unit_debug(u, "Got notification message for unit. Ignoring."); + else if (_unlikely_(log_get_max_level() >= LOG_DEBUG)) { + _cleanup_free_ char *x = NULL, *y = NULL; + + x = cescape(buf); + if (x) + y = ellipsize(x, 20, 90); + log_unit_debug(u, "Got notification message \"%s\", ignoring.", strnull(y)); + } } static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { @@ -1626,7 +1714,6 @@ static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t struct cmsghdr *cmsg; struct ucred *ucred = NULL; - bool found = false; Unit *u1, *u2, *u3; int r, *fd_array = NULL; unsigned n_fds = 0; @@ -1640,16 +1727,15 @@ static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t return 0; } - n = recvmsg(m->notify_fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); + n = recvmsg(m->notify_fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC|MSG_TRUNC); if (n < 0) { - if (!IN_SET(errno, EAGAIN, EINTR)) - log_error("Failed to receive notification message: %m"); + if (IN_SET(errno, EAGAIN, EINTR)) + return 0; /* Spurious wakeup, try again */ - /* It's not an option to return an error here since it - * would disable the notification handler entirely. Services - * wouldn't be able to send the WATCHDOG message for - * example... */ - return 0; + /* If this is any other, real error, then let's stop processing this socket. This of course means we + * won't take notification messages anymore, but that's still better than busy looping around this: + * being woken up over and over again but being unable to actually read the message off the socket. */ + return log_error_errno(errno, "Failed to receive notification message: %m"); } CMSG_FOREACH(cmsg, &msghdr) { @@ -1682,40 +1768,40 @@ static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t return 0; } - if ((size_t) n >= sizeof(buf)) { + if ((size_t) n >= sizeof(buf) || (msghdr.msg_flags & MSG_TRUNC)) { log_warning("Received notify message exceeded maximum size. Ignoring."); return 0; } - /* The message should be a string. Here we make sure it's NUL-terminated, - * but only the part until first NUL will be used anyway. */ + /* As extra safety check, let's make sure the string we get doesn't contain embedded NUL bytes. We permit one + * trailing NUL byte in the message, but don't expect it. */ + if (n > 1 && memchr(buf, 0, n-1)) { + log_warning("Received notify message with embedded NUL bytes. Ignoring."); + return 0; + } + + /* Make sure it's NUL-terminated. */ buf[n] = 0; /* Notify every unit that might be interested, but try * to avoid notifying the same one multiple times. */ u1 = manager_get_unit_by_pid_cgroup(m, ucred->pid); - if (u1) { + if (u1) manager_invoke_notify_message(m, u1, ucred->pid, buf, fds); - found = true; - } u2 = hashmap_get(m->watch_pids1, PID_TO_PTR(ucred->pid)); - if (u2 && u2 != u1) { + if (u2 && u2 != u1) manager_invoke_notify_message(m, u2, ucred->pid, buf, fds); - found = true; - } u3 = hashmap_get(m->watch_pids2, PID_TO_PTR(ucred->pid)); - if (u3 && u3 != u2 && u3 != u1) { + if (u3 && u3 != u2 && u3 != u1) manager_invoke_notify_message(m, u3, ucred->pid, buf, fds); - found = true; - } - if (!found) + if (!u1 && !u2 && !u3) log_warning("Cannot find unit for notify message of PID "PID_FMT".", ucred->pid); if (fdset_size(fds) > 0) - log_warning("Got auxiliary fds with notification message, closing all."); + log_warning("Got extra auxiliary fds with notification message, closing them."); return 0; } @@ -1820,6 +1906,18 @@ static int manager_start_target(Manager *m, const char *name, JobMode mode) { return r; } +static void manager_handle_ctrl_alt_del(Manager *m) { + /* If the user presses C-A-D more than + * 7 times within 2s, we reboot/shutdown immediately, + * unless it was disabled in system.conf */ + + if (ratelimit_test(&m->ctrl_alt_del_ratelimit) || m->cad_burst_action == EMERGENCY_ACTION_NONE) + manager_start_target(m, SPECIAL_CTRL_ALT_DEL_TARGET, JOB_REPLACE_IRREVERSIBLY); + else + emergency_action(m, m->cad_burst_action, NULL, + "Ctrl-Alt-Del was pressed more than 7 times within 2s"); +} + static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { Manager *m = userdata; ssize_t n; @@ -1838,14 +1936,17 @@ static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t for (;;) { n = read(m->signal_fd, &sfsi, sizeof(sfsi)); if (n != sizeof(sfsi)) { + if (n >= 0) { + log_warning("Truncated read from signal fd (%zu bytes)!", n); + return 0; + } - if (n >= 0) - return -EIO; - - if (errno == EINTR || errno == EAGAIN) + if (IN_SET(errno, EINTR, EAGAIN)) break; - return -errno; + /* We return an error here, which will kill this handler, + * to avoid a busy loop on read error. */ + return log_error_errno(errno, "Reading from signal fd failed: %m"); } log_received_signal(sfsi.ssi_signo == SIGCHLD || @@ -1871,19 +1972,7 @@ static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t case SIGINT: if (MANAGER_IS_SYSTEM(m)) { - - /* If the user presses C-A-D more than - * 7 times within 2s, we reboot - * immediately. */ - - if (ratelimit_test(&m->ctrl_alt_del_ratelimit)) - manager_start_target(m, SPECIAL_CTRL_ALT_DEL_TARGET, JOB_REPLACE_IRREVERSIBLY); - else { - log_notice("Ctrl-Alt-Del was pressed more than 7 times within 2s, rebooting immediately."); - status_printf(NULL, true, false, "Ctrl-Alt-Del was pressed more than 7 times within 2s, rebooting immediately."); - m->exit_code = MANAGER_REBOOT; - } - + manager_handle_ctrl_alt_del(m); break; } @@ -2169,6 +2258,7 @@ int manager_loop(Manager *m) { int manager_load_unit_from_dbus_path(Manager *m, const char *s, sd_bus_error *e, Unit **_u) { _cleanup_free_ char *n = NULL; + sd_id128_t invocation_id; Unit *u; int r; @@ -2180,12 +2270,25 @@ int manager_load_unit_from_dbus_path(Manager *m, const char *s, sd_bus_error *e, if (r < 0) return r; + /* Permit addressing units by invocation ID: if the passed bus path is suffixed by a 128bit ID then we use it + * as invocation ID. */ + r = sd_id128_from_string(n, &invocation_id); + if (r >= 0) { + u = hashmap_get(m->units_by_invocation_id, &invocation_id); + if (u) { + *_u = u; + return 0; + } + + return sd_bus_error_setf(e, BUS_ERROR_NO_UNIT_FOR_INVOCATION_ID, "No unit with the specified invocation ID " SD_ID128_FORMAT_STR " known.", SD_ID128_FORMAT_VAL(invocation_id)); + } + + /* If this didn't work, we use the suffix as unit name. */ r = manager_load_unit(m, n, NULL, e, &u); if (r < 0) return r; *_u = u; - return 0; } @@ -2397,17 +2500,28 @@ int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool switching_root) { fprintf(f, "cgroups-agent-fd=%i\n", copy); } - if (m->kdbus_fd >= 0) { - int copy; + if (m->user_lookup_fds[0] >= 0) { + int copy0, copy1; - copy = fdset_put_dup(fds, m->kdbus_fd); - if (copy < 0) - return copy; + copy0 = fdset_put_dup(fds, m->user_lookup_fds[0]); + if (copy0 < 0) + return copy0; + + copy1 = fdset_put_dup(fds, m->user_lookup_fds[1]); + if (copy1 < 0) + return copy1; - fprintf(f, "kdbus-fd=%i\n", copy); + fprintf(f, "user-lookup=%i %i\n", copy0, copy1); } - bus_track_serialize(m->subscribed, f); + bus_track_serialize(m->subscribed, f, "subscribed"); + + r = dynamic_user_serialize(m, f, fds); + if (r < 0) + return r; + + manager_serialize_uid_refs(m, f); + manager_serialize_gid_refs(m, f); fputc('\n', f); @@ -2575,25 +2689,31 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) { m->cgroups_agent_fd = fdset_remove(fds, fd); } - } else if (startswith(l, "kdbus-fd=")) { - int fd; + } else if (startswith(l, "user-lookup=")) { + int fd0, fd1; - if (safe_atoi(l + 9, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) - log_debug("Failed to parse kdbus fd: %s", l + 9); + if (sscanf(l + 12, "%i %i", &fd0, &fd1) != 2 || fd0 < 0 || fd1 < 0 || fd0 == fd1 || !fdset_contains(fds, fd0) || !fdset_contains(fds, fd1)) + log_debug("Failed to parse user lookup fd: %s", l + 12); else { - safe_close(m->kdbus_fd); - m->kdbus_fd = fdset_remove(fds, fd); + m->user_lookup_event_source = sd_event_source_unref(m->user_lookup_event_source); + safe_close_pair(m->user_lookup_fds); + m->user_lookup_fds[0] = fdset_remove(fds, fd0); + m->user_lookup_fds[1] = fdset_remove(fds, fd1); } - } else { - int k; + } else if (startswith(l, "dynamic-user=")) + dynamic_user_deserialize_one(m, l + 13, fds); + else if (startswith(l, "destroy-ipc-uid=")) + manager_deserialize_uid_refs_one(m, l + 16); + else if (startswith(l, "destroy-ipc-gid=")) + manager_deserialize_gid_refs_one(m, l + 16); + else if (startswith(l, "subscribed=")) { - k = bus_track_deserialize_item(&m->deserialized_subscribed, l); - if (k < 0) - log_debug_errno(k, "Failed to deserialize bus tracker object: %m"); - else if (k == 0) - log_debug("Unknown serialization item '%s'", l); - } + if (strv_extend(&m->deserialized_subscribed, l+11) < 0) + log_oom(); + + } else if (!startswith(l, "kdbus-fd=")) /* ignore this one */ + log_debug("Unknown serialization item '%s'", l); } for (;;) { @@ -2666,6 +2786,9 @@ int manager_reload(Manager *m) { manager_clear_jobs_and_units(m); lookup_paths_flush_generator(&m->lookup_paths); lookup_paths_free(&m->lookup_paths); + dynamic_user_vacuum(m, false); + m->uid_refs = hashmap_free(m->uid_refs); + m->gid_refs = hashmap_free(m->gid_refs); q = lookup_paths_init(&m->lookup_paths, m->unit_file_scope, 0, NULL); if (q < 0 && r >= 0) @@ -2699,9 +2822,20 @@ int manager_reload(Manager *m) { if (q < 0 && r >= 0) r = q; + q = manager_setup_user_lookup_fd(m); + if (q < 0 && r >= 0) + r = q; + /* Third, fire things up! */ manager_coldplug(m); + /* Release any dynamic users no longer referenced */ + dynamic_user_vacuum(m, true); + + /* Release any references to UIDs/GIDs no longer referenced, and destroy any IPC owned by them */ + manager_vacuum_uid_refs(m); + manager_vacuum_gid_refs(m); + /* Sync current state of bus names with our set of listening units */ if (m->api_bus) manager_sync_bus_names(m, m->api_bus); @@ -2947,7 +3081,7 @@ int manager_set_default_rlimits(Manager *m, struct rlimit **default_rlimit) { m->rlimit[i] = newdup(struct rlimit, default_rlimit[i], 1); if (!m->rlimit[i]) - return -ENOMEM; + return log_oom(); } return 0; @@ -3135,6 +3269,300 @@ ManagerState manager_state(Manager *m) { return MANAGER_RUNNING; } +#define DESTROY_IPC_FLAG (UINT32_C(1) << 31) + +static void manager_unref_uid_internal( + Manager *m, + Hashmap **uid_refs, + uid_t uid, + bool destroy_now, + int (*_clean_ipc)(uid_t uid)) { + + uint32_t c, n; + + assert(m); + assert(uid_refs); + assert(uid_is_valid(uid)); + assert(_clean_ipc); + + /* A generic implementation, covering both manager_unref_uid() and manager_unref_gid(), under the assumption + * that uid_t and gid_t are actually defined the same way, with the same validity rules. + * + * We store a hashmap where the UID/GID is they key and the value is a 32bit reference counter, whose highest + * bit is used as flag for marking UIDs/GIDs whose IPC objects to remove when the last reference to the UID/GID + * is dropped. The flag is set to on, once at least one reference from a unit where RemoveIPC= is set is added + * on a UID/GID. It is reset when the UID's/GID's reference counter drops to 0 again. */ + + assert_cc(sizeof(uid_t) == sizeof(gid_t)); + assert_cc(UID_INVALID == (uid_t) GID_INVALID); + + if (uid == 0) /* We don't keep track of root, and will never destroy it */ + return; + + c = PTR_TO_UINT32(hashmap_get(*uid_refs, UID_TO_PTR(uid))); + + n = c & ~DESTROY_IPC_FLAG; + assert(n > 0); + n--; + + if (destroy_now && n == 0) { + hashmap_remove(*uid_refs, UID_TO_PTR(uid)); + + if (c & DESTROY_IPC_FLAG) { + log_debug("%s " UID_FMT " is no longer referenced, cleaning up its IPC.", + _clean_ipc == clean_ipc_by_uid ? "UID" : "GID", + uid); + (void) _clean_ipc(uid); + } + } else { + c = n | (c & DESTROY_IPC_FLAG); + assert_se(hashmap_update(*uid_refs, UID_TO_PTR(uid), UINT32_TO_PTR(c)) >= 0); + } +} + +void manager_unref_uid(Manager *m, uid_t uid, bool destroy_now) { + manager_unref_uid_internal(m, &m->uid_refs, uid, destroy_now, clean_ipc_by_uid); +} + +void manager_unref_gid(Manager *m, gid_t gid, bool destroy_now) { + manager_unref_uid_internal(m, &m->gid_refs, (uid_t) gid, destroy_now, clean_ipc_by_gid); +} + +static int manager_ref_uid_internal( + Manager *m, + Hashmap **uid_refs, + uid_t uid, + bool clean_ipc) { + + uint32_t c, n; + int r; + + assert(m); + assert(uid_refs); + assert(uid_is_valid(uid)); + + /* A generic implementation, covering both manager_ref_uid() and manager_ref_gid(), under the assumption + * that uid_t and gid_t are actually defined the same way, with the same validity rules. */ + + assert_cc(sizeof(uid_t) == sizeof(gid_t)); + assert_cc(UID_INVALID == (uid_t) GID_INVALID); + + if (uid == 0) /* We don't keep track of root, and will never destroy it */ + return 0; + + r = hashmap_ensure_allocated(uid_refs, &trivial_hash_ops); + if (r < 0) + return r; + + c = PTR_TO_UINT32(hashmap_get(*uid_refs, UID_TO_PTR(uid))); + + n = c & ~DESTROY_IPC_FLAG; + n++; + + if (n & DESTROY_IPC_FLAG) /* check for overflow */ + return -EOVERFLOW; + + c = n | (c & DESTROY_IPC_FLAG) | (clean_ipc ? DESTROY_IPC_FLAG : 0); + + return hashmap_replace(*uid_refs, UID_TO_PTR(uid), UINT32_TO_PTR(c)); +} + +int manager_ref_uid(Manager *m, uid_t uid, bool clean_ipc) { + return manager_ref_uid_internal(m, &m->uid_refs, uid, clean_ipc); +} + +int manager_ref_gid(Manager *m, gid_t gid, bool clean_ipc) { + return manager_ref_uid_internal(m, &m->gid_refs, (uid_t) gid, clean_ipc); +} + +static void manager_vacuum_uid_refs_internal( + Manager *m, + Hashmap **uid_refs, + int (*_clean_ipc)(uid_t uid)) { + + Iterator i; + void *p, *k; + + assert(m); + assert(uid_refs); + assert(_clean_ipc); + + HASHMAP_FOREACH_KEY(p, k, *uid_refs, i) { + uint32_t c, n; + uid_t uid; + + uid = PTR_TO_UID(k); + c = PTR_TO_UINT32(p); + + n = c & ~DESTROY_IPC_FLAG; + if (n > 0) + continue; + + if (c & DESTROY_IPC_FLAG) { + log_debug("Found unreferenced %s " UID_FMT " after reload/reexec. Cleaning up.", + _clean_ipc == clean_ipc_by_uid ? "UID" : "GID", + uid); + (void) _clean_ipc(uid); + } + + assert_se(hashmap_remove(*uid_refs, k) == p); + } +} + +void manager_vacuum_uid_refs(Manager *m) { + manager_vacuum_uid_refs_internal(m, &m->uid_refs, clean_ipc_by_uid); +} + +void manager_vacuum_gid_refs(Manager *m) { + manager_vacuum_uid_refs_internal(m, &m->gid_refs, clean_ipc_by_gid); +} + +static void manager_serialize_uid_refs_internal( + Manager *m, + FILE *f, + Hashmap **uid_refs, + const char *field_name) { + + Iterator i; + void *p, *k; + + assert(m); + assert(f); + assert(uid_refs); + assert(field_name); + + /* Serialize the UID reference table. Or actually, just the IPC destruction flag of it, as the actual counter + * of it is better rebuild after a reload/reexec. */ + + HASHMAP_FOREACH_KEY(p, k, *uid_refs, i) { + uint32_t c; + uid_t uid; + + uid = PTR_TO_UID(k); + c = PTR_TO_UINT32(p); + + if (!(c & DESTROY_IPC_FLAG)) + continue; + + fprintf(f, "%s=" UID_FMT "\n", field_name, uid); + } +} + +void manager_serialize_uid_refs(Manager *m, FILE *f) { + manager_serialize_uid_refs_internal(m, f, &m->uid_refs, "destroy-ipc-uid"); +} + +void manager_serialize_gid_refs(Manager *m, FILE *f) { + manager_serialize_uid_refs_internal(m, f, &m->gid_refs, "destroy-ipc-gid"); +} + +static void manager_deserialize_uid_refs_one_internal( + Manager *m, + Hashmap** uid_refs, + const char *value) { + + uid_t uid; + uint32_t c; + int r; + + assert(m); + assert(uid_refs); + assert(value); + + r = parse_uid(value, &uid); + if (r < 0 || uid == 0) { + log_debug("Unable to parse UID reference serialization"); + return; + } + + r = hashmap_ensure_allocated(uid_refs, &trivial_hash_ops); + if (r < 0) { + log_oom(); + return; + } + + c = PTR_TO_UINT32(hashmap_get(*uid_refs, UID_TO_PTR(uid))); + if (c & DESTROY_IPC_FLAG) + return; + + c |= DESTROY_IPC_FLAG; + + r = hashmap_replace(*uid_refs, UID_TO_PTR(uid), UINT32_TO_PTR(c)); + if (r < 0) { + log_debug("Failed to add UID reference entry"); + return; + } +} + +void manager_deserialize_uid_refs_one(Manager *m, const char *value) { + manager_deserialize_uid_refs_one_internal(m, &m->uid_refs, value); +} + +void manager_deserialize_gid_refs_one(Manager *m, const char *value) { + manager_deserialize_uid_refs_one_internal(m, &m->gid_refs, value); +} + +int manager_dispatch_user_lookup_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + struct buffer { + uid_t uid; + gid_t gid; + char unit_name[UNIT_NAME_MAX+1]; + } _packed_ buffer; + + Manager *m = userdata; + ssize_t l; + size_t n; + Unit *u; + + assert_se(source); + assert_se(m); + + /* Invoked whenever a child process succeeded resolving its user/group to use and sent us the resulting UID/GID + * in a datagram. We parse the datagram here and pass it off to the unit, so that it can add a reference to the + * UID/GID so that it can destroy the UID/GID's IPC objects when the reference counter drops to 0. */ + + l = recv(fd, &buffer, sizeof(buffer), MSG_DONTWAIT); + if (l < 0) { + if (errno == EINTR || errno == EAGAIN) + return 0; + + return log_error_errno(errno, "Failed to read from user lookup fd: %m"); + } + + if ((size_t) l <= offsetof(struct buffer, unit_name)) { + log_warning("Received too short user lookup message, ignoring."); + return 0; + } + + if ((size_t) l > offsetof(struct buffer, unit_name) + UNIT_NAME_MAX) { + log_warning("Received too long user lookup message, ignoring."); + return 0; + } + + if (!uid_is_valid(buffer.uid) && !gid_is_valid(buffer.gid)) { + log_warning("Got user lookup message with invalid UID/GID pair, ignoring."); + return 0; + } + + n = (size_t) l - offsetof(struct buffer, unit_name); + if (memchr(buffer.unit_name, 0, n)) { + log_warning("Received lookup message with embedded NUL character, ignoring."); + return 0; + } + + buffer.unit_name[n] = 0; + u = manager_get_unit(m, buffer.unit_name); + if (!u) { + log_debug("Got user lookup message but unit doesn't exist, ignoring."); + return 0; + } + + log_unit_debug(u, "User lookup succeeded: uid=" UID_FMT " gid=" GID_FMT, buffer.uid, buffer.gid); + + unit_notify_user_lookup(u, buffer.uid, buffer.gid); + return 0; +} + static const char *const manager_state_table[_MANAGER_STATE_MAX] = { [MANAGER_INITIALIZING] = "initializing", [MANAGER_STARTING] = "starting", diff --git a/src/core/manager.h b/src/core/manager.h index 6ed15c1a41..35172fdba9 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -81,6 +81,7 @@ struct Manager { /* Active jobs and units */ Hashmap *units; /* name string => Unit object n:1 */ + Hashmap *units_by_invocation_id; Hashmap *jobs; /* job id => Job object 1:1 */ /* To make it easy to iterate through the units of a specific @@ -143,6 +144,9 @@ struct Manager { sd_event_source *jobs_in_progress_event_source; + int user_lookup_fds[2]; + sd_event_source *user_lookup_event_source; + UnitFileScope unit_file_scope; LookupPaths lookup_paths; Set *unit_path_cache; @@ -234,7 +238,6 @@ struct Manager { bool dispatching_dbus_queue:1; bool taint_usr:1; - bool test_run:1; /* If non-zero, exit with the following value when the systemd @@ -292,18 +295,26 @@ struct Manager { * value where Unit objects are contained. */ Hashmap *units_requiring_mounts_for; - /* Reference to the kdbus bus control fd */ - int kdbus_fd; - /* Used for processing polkit authorization responses */ Hashmap *polkit_registry; - /* When the user hits C-A-D more than 7 times per 2s, reboot immediately... */ + /* Dynamic users/groups, indexed by their name */ + Hashmap *dynamic_users; + + /* Keep track of all UIDs and GIDs any of our services currently use. This is useful for the RemoveIPC= logic. */ + Hashmap *uid_refs; + Hashmap *gid_refs; + + /* When the user hits C-A-D more than 7 times per 2s, do something immediately... */ RateLimit ctrl_alt_del_ratelimit; + EmergencyAction cad_burst_action; const char *unit_log_field; const char *unit_log_format_string; + const char *invocation_log_field; + const char *invocation_log_format_string; + int first_boot; /* tri-state */ }; @@ -375,5 +386,20 @@ ManagerState manager_state(Manager *m); int manager_update_failed_units(Manager *m, Unit *u, bool failed); +void manager_unref_uid(Manager *m, uid_t uid, bool destroy_now); +int manager_ref_uid(Manager *m, uid_t uid, bool clean_ipc); + +void manager_unref_gid(Manager *m, gid_t gid, bool destroy_now); +int manager_ref_gid(Manager *m, gid_t gid, bool destroy_now); + +void manager_vacuum_uid_refs(Manager *m); +void manager_vacuum_gid_refs(Manager *m); + +void manager_serialize_uid_refs(Manager *m, FILE *f); +void manager_deserialize_uid_refs_one(Manager *m, const char *value); + +void manager_serialize_gid_refs(Manager *m, FILE *f); +void manager_deserialize_gid_refs_one(Manager *m, const char *value); + const char *manager_state_to_string(ManagerState m) _const_; ManagerState manager_state_from_string(const char *s) _pure_; diff --git a/src/core/mount-setup.c b/src/core/mount-setup.c index 5d8ab0ec70..ca63a93e8b 100644 --- a/src/core/mount-setup.c +++ b/src/core/mount-setup.c @@ -99,10 +99,12 @@ static const MountPoint mount_table[] = { cg_is_unified_wanted, MNT_FATAL|MNT_IN_CONTAINER }, { "tmpfs", "/sys/fs/cgroup", "tmpfs", "mode=755", MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME, cg_is_legacy_wanted, MNT_FATAL|MNT_IN_CONTAINER }, + { "cgroup", "/sys/fs/cgroup/systemd", "cgroup2", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, + cg_is_unified_systemd_controller_wanted, MNT_IN_CONTAINER }, { "cgroup", "/sys/fs/cgroup/systemd", "cgroup", "none,name=systemd,xattr", MS_NOSUID|MS_NOEXEC|MS_NODEV, - cg_is_legacy_wanted, MNT_IN_CONTAINER }, + cg_is_legacy_systemd_controller_wanted, MNT_IN_CONTAINER }, { "cgroup", "/sys/fs/cgroup/systemd", "cgroup", "none,name=systemd", MS_NOSUID|MS_NOEXEC|MS_NODEV, - cg_is_legacy_wanted, MNT_FATAL|MNT_IN_CONTAINER }, + cg_is_legacy_systemd_controller_wanted, MNT_FATAL|MNT_IN_CONTAINER }, { "pstore", "/sys/fs/pstore", "pstore", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL, MNT_NONE }, #ifdef ENABLE_EFI diff --git a/src/core/mount.c b/src/core/mount.c index fda4d65d6f..d749e49df5 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -159,17 +159,6 @@ static void mount_init(Unit *u) { m->timeout_usec = u->manager->default_timeout_start_usec; m->directory_mode = 0755; - if (unit_has_name(u, "-.mount")) { - /* Don't allow start/stop for root directory */ - u->refuse_manual_start = true; - u->refuse_manual_stop = true; - } else { - /* The stdio/kmsg bridge socket is on /, in order to avoid a - * dep loop, don't use kmsg logging for -.mount */ - m->exec_context.std_output = u->manager->default_std_output; - m->exec_context.std_error = u->manager->default_std_error; - } - /* We need to make sure that /usr/bin/mount is always called * in the same process group as us, so that the autofs kernel * side doesn't send us another mount request while we are @@ -245,6 +234,8 @@ static void mount_done(Unit *u) { exec_command_done_array(m->exec_command, _MOUNT_EXEC_COMMAND_MAX); m->control_command = NULL; + dynamic_creds_unref(&m->dynamic_creds); + mount_unwatch_control_pid(m); m->timer_event_source = sd_event_source_unref(m->timer_event_source); @@ -482,6 +473,7 @@ static int mount_add_default_dependencies(Mount *m) { static int mount_verify(Mount *m) { _cleanup_free_ char *e = NULL; + MountParameters *p; int r; assert(m); @@ -506,7 +498,8 @@ static int mount_verify(Mount *m) { return -EINVAL; } - if (UNIT(m)->fragment_path && !m->parameters_fragment.what) { + p = get_mount_parameters_fragment(m); + if (p && !p->what) { log_unit_error(UNIT(m), "What= setting is missing. Refusing."); return -EBADMSG; } @@ -573,6 +566,25 @@ static int mount_add_extras(Mount *m) { return 0; } +static int mount_load_root_mount(Unit *u) { + assert(u); + + if (!unit_has_name(u, SPECIAL_ROOT_MOUNT)) + return 0; + + u->perpetual = true; + u->default_dependencies = false; + + /* The stdio/kmsg bridge socket is on /, in order to avoid a dep loop, don't use kmsg logging for -.mount */ + MOUNT(u)->exec_context.std_output = EXEC_OUTPUT_NULL; + MOUNT(u)->exec_context.std_input = EXEC_INPUT_NULL; + + if (!u->description) + u->description = strdup("Root Mount"); + + return 1; +} + static int mount_load(Unit *u) { Mount *m = MOUNT(u); int r; @@ -580,11 +592,14 @@ static int mount_load(Unit *u) { assert(u); assert(u->load_state == UNIT_STUB); - if (m->from_proc_self_mountinfo) + r = mount_load_root_mount(u); + if (r < 0) + return r; + + if (m->from_proc_self_mountinfo || u->perpetual) r = unit_load_fragment_and_dropin_optional(u); else r = unit_load_fragment_and_dropin(u); - if (r < 0) return r; @@ -648,6 +663,9 @@ static int mount_coldplug(Unit *u) { return r; } + if (!IN_SET(new_state, MOUNT_DEAD, MOUNT_FAILED)) + (void) unit_setup_dynamic_creds(u); + mount_set_state(m, new_state); return 0; } @@ -670,7 +688,10 @@ static void mount_dump(Unit *u, FILE *f, const char *prefix) { "%sOptions: %s\n" "%sFrom /proc/self/mountinfo: %s\n" "%sFrom fragment: %s\n" - "%sDirectoryMode: %04o\n", + "%sDirectoryMode: %04o\n" + "%sSloppyOptions: %s\n" + "%sLazyUnmount: %s\n" + "%sForceUnmount: %s\n", prefix, mount_state_to_string(m->state), prefix, mount_result_to_string(m->result), prefix, m->where, @@ -679,7 +700,10 @@ static void mount_dump(Unit *u, FILE *f, const char *prefix) { prefix, p ? strna(p->options) : "n/a", prefix, yes_no(m->from_proc_self_mountinfo), prefix, yes_no(m->from_fragment), - prefix, m->directory_mode); + prefix, m->directory_mode, + prefix, yes_no(m->sloppy_options), + prefix, yes_no(m->lazy_unmount), + prefix, yes_no(m->force_unmount)); if (m->control_pid > 0) fprintf(f, @@ -694,12 +718,10 @@ static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) { pid_t pid; int r; ExecParameters exec_params = { - .apply_permissions = true, - .apply_chroot = true, - .apply_tty_stdin = true, - .stdin_fd = -1, - .stdout_fd = -1, - .stderr_fd = -1, + .flags = EXEC_APPLY_PERMISSIONS|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN, + .stdin_fd = -1, + .stdout_fd = -1, + .stderr_fd = -1, }; assert(m); @@ -716,12 +738,16 @@ static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) { if (r < 0) return r; + r = unit_setup_dynamic_creds(UNIT(m)); + if (r < 0) + return r; + r = mount_arm_timer(m, usec_add(now(CLOCK_MONOTONIC), m->timeout_usec)); if (r < 0) return r; exec_params.environment = UNIT(m)->manager->environment; - exec_params.confirm_spawn = UNIT(m)->manager->confirm_spawn; + exec_params.flags |= UNIT(m)->manager->confirm_spawn ? EXEC_CONFIRM_SPAWN : 0; exec_params.cgroup_supported = UNIT(m)->manager->cgroup_supported; exec_params.cgroup_path = UNIT(m)->cgroup_path; exec_params.cgroup_delegate = m->cgroup_context.delegate; @@ -732,6 +758,7 @@ static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) { &m->exec_context, &exec_params, m->exec_runtime, + &m->dynamic_creds, &pid); if (r < 0) return r; @@ -749,21 +776,25 @@ static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) { static void mount_enter_dead(Mount *m, MountResult f) { assert(m); - if (f != MOUNT_SUCCESS) + if (m->result == MOUNT_SUCCESS) m->result = f; + mount_set_state(m, m->result != MOUNT_SUCCESS ? MOUNT_FAILED : MOUNT_DEAD); + exec_runtime_destroy(m->exec_runtime); m->exec_runtime = exec_runtime_unref(m->exec_runtime); exec_context_destroy_runtime_directory(&m->exec_context, manager_get_runtime_prefix(UNIT(m)->manager)); - mount_set_state(m, m->result != MOUNT_SUCCESS ? MOUNT_FAILED : MOUNT_DEAD); + unit_unref_uid_gid(UNIT(m), true); + + dynamic_creds_destroy(&m->dynamic_creds); } static void mount_enter_mounted(Mount *m, MountResult f) { assert(m); - if (f != MOUNT_SUCCESS) + if (m->result == MOUNT_SUCCESS) m->result = f; mount_set_state(m, MOUNT_MOUNTED); @@ -774,7 +805,7 @@ static void mount_enter_signal(Mount *m, MountState state, MountResult f) { assert(m); - if (f != MOUNT_SUCCESS) + if (m->result == MOUNT_SUCCESS) m->result = f; r = unit_kill_context( @@ -810,7 +841,7 @@ static void mount_enter_signal(Mount *m, MountState state, MountResult f) { fail: log_unit_warning_errno(UNIT(m), r, "Failed to kill processes: %m"); - if (state == MOUNT_REMOUNTING_SIGTERM || state == MOUNT_REMOUNTING_SIGKILL) + if (IN_SET(state, MOUNT_REMOUNTING_SIGTERM, MOUNT_REMOUNTING_SIGKILL)) mount_enter_mounted(m, MOUNT_FAILURE_RESOURCES); else mount_enter_dead(m, MOUNT_FAILURE_RESOURCES); @@ -832,6 +863,10 @@ static void mount_enter_unmounting(Mount *m) { m->control_command = m->exec_command + MOUNT_EXEC_UNMOUNT; r = exec_command_set(m->control_command, UMOUNT_PATH, m->where, NULL); + if (r >= 0 && m->lazy_unmount) + r = exec_command_append(m->control_command, "-l", NULL); + if (r >= 0 && m->force_unmount) + r = exec_command_append(m->control_command, "-f", NULL); if (r < 0) goto fail; @@ -850,11 +885,6 @@ fail: mount_enter_mounted(m, MOUNT_FAILURE_RESOURCES); } -static int mount_get_opts(Mount *m, char **ret) { - return fstab_filter_options(m->parameters_fragment.options, - "nofail\0" "noauto\0" "auto\0", NULL, NULL, ret); -} - static void mount_enter_mounting(Mount *m) { int r; MountParameters *p; @@ -877,19 +907,18 @@ static void mount_enter_mounting(Mount *m) { if (p && mount_is_bind(p)) (void) mkdir_p_label(p->what, m->directory_mode); - if (m->from_fragment) { + if (p) { _cleanup_free_ char *opts = NULL; - r = mount_get_opts(m, &opts); + r = fstab_filter_options(p->options, "nofail\0" "noauto\0" "auto\0", NULL, NULL, &opts); if (r < 0) goto fail; - r = exec_command_set(m->control_command, MOUNT_PATH, - m->parameters_fragment.what, m->where, NULL); + r = exec_command_set(m->control_command, MOUNT_PATH, p->what, m->where, NULL); if (r >= 0 && m->sloppy_options) r = exec_command_append(m->control_command, "-s", NULL); - if (r >= 0 && m->parameters_fragment.fstype) - r = exec_command_append(m->control_command, "-t", m->parameters_fragment.fstype, NULL); + if (r >= 0 && p->fstype) + r = exec_command_append(m->control_command, "-t", p->fstype, NULL); if (r >= 0 && !isempty(opts)) r = exec_command_append(m->control_command, "-o", opts, NULL); } else @@ -915,27 +944,29 @@ fail: static void mount_enter_remounting(Mount *m) { int r; + MountParameters *p; assert(m); m->control_command_id = MOUNT_EXEC_REMOUNT; m->control_command = m->exec_command + MOUNT_EXEC_REMOUNT; - if (m->from_fragment) { + p = get_mount_parameters_fragment(m); + if (p) { const char *o; - if (m->parameters_fragment.options) - o = strjoina("remount,", m->parameters_fragment.options); + if (p->options) + o = strjoina("remount,", p->options); else o = "remount"; r = exec_command_set(m->control_command, MOUNT_PATH, - m->parameters_fragment.what, m->where, + p->what, m->where, "-o", o, NULL); if (r >= 0 && m->sloppy_options) r = exec_command_append(m->control_command, "-s", NULL); - if (r >= 0 && m->parameters_fragment.fstype) - r = exec_command_append(m->control_command, "-t", m->parameters_fragment.fstype, NULL); + if (r >= 0 && p->fstype) + r = exec_command_append(m->control_command, "-t", p->fstype, NULL); } else r = -ENOENT; @@ -966,18 +997,19 @@ static int mount_start(Unit *u) { /* We cannot fulfill this request right now, try again later * please! */ - if (m->state == MOUNT_UNMOUNTING || - m->state == MOUNT_UNMOUNTING_SIGTERM || - m->state == MOUNT_UNMOUNTING_SIGKILL || - m->state == MOUNT_MOUNTING_SIGTERM || - m->state == MOUNT_MOUNTING_SIGKILL) + if (IN_SET(m->state, + MOUNT_UNMOUNTING, + MOUNT_UNMOUNTING_SIGTERM, + MOUNT_UNMOUNTING_SIGKILL, + MOUNT_MOUNTING_SIGTERM, + MOUNT_MOUNTING_SIGKILL)) return -EAGAIN; /* Already on it! */ if (m->state == MOUNT_MOUNTING) return 0; - assert(m->state == MOUNT_DEAD || m->state == MOUNT_FAILED); + assert(IN_SET(m->state, MOUNT_DEAD, MOUNT_FAILED)); r = unit_start_limit_test(u); if (r < 0) { @@ -985,6 +1017,10 @@ static int mount_start(Unit *u) { return r; } + r = unit_acquire_invocation_id(u); + if (r < 0) + return r; + m->result = MOUNT_SUCCESS; m->reload_result = MOUNT_SUCCESS; m->reset_cpu_usage = true; @@ -999,19 +1035,21 @@ static int mount_stop(Unit *u) { assert(m); /* Already on it */ - if (m->state == MOUNT_UNMOUNTING || - m->state == MOUNT_UNMOUNTING_SIGKILL || - m->state == MOUNT_UNMOUNTING_SIGTERM || - m->state == MOUNT_MOUNTING_SIGTERM || - m->state == MOUNT_MOUNTING_SIGKILL) + if (IN_SET(m->state, + MOUNT_UNMOUNTING, + MOUNT_UNMOUNTING_SIGKILL, + MOUNT_UNMOUNTING_SIGTERM, + MOUNT_MOUNTING_SIGTERM, + MOUNT_MOUNTING_SIGKILL)) return 0; - assert(m->state == MOUNT_MOUNTING || - m->state == MOUNT_MOUNTING_DONE || - m->state == MOUNT_MOUNTED || - m->state == MOUNT_REMOUNTING || - m->state == MOUNT_REMOUNTING_SIGTERM || - m->state == MOUNT_REMOUNTING_SIGKILL); + assert(IN_SET(m->state, + MOUNT_MOUNTING, + MOUNT_MOUNTING_DONE, + MOUNT_MOUNTED, + MOUNT_REMOUNTING, + MOUNT_REMOUNTING_SIGTERM, + MOUNT_REMOUNTING_SIGKILL)); mount_enter_unmounting(m); return 1; @@ -1139,7 +1177,7 @@ static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) { m->control_pid = 0; - if (is_clean_exit(code, status, NULL)) + if (is_clean_exit(code, status, EXIT_CLEAN_COMMAND, NULL)) f = MOUNT_SUCCESS; else if (code == CLD_EXITED) f = MOUNT_FAILURE_EXIT_CODE; @@ -1150,7 +1188,7 @@ static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) { else assert_not_reached("Unknown code"); - if (f != MOUNT_SUCCESS) + if (m->result == MOUNT_SUCCESS) m->result = f; if (m->control_command) { @@ -1177,9 +1215,10 @@ static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) { case MOUNT_MOUNTING_SIGKILL: case MOUNT_MOUNTING_SIGTERM: - if (f == MOUNT_SUCCESS) - mount_enter_mounted(m, f); - else if (m->from_proc_self_mountinfo) + if (f == MOUNT_SUCCESS || m->from_proc_self_mountinfo) + /* If /bin/mount returned success, or if we see the mount point in /proc/self/mountinfo we are + * happy. If we see the first condition first, we should see the the second condition + * immediately after – or /bin/mount lies to us and is broken. */ mount_enter_mounted(m, f); else mount_enter_dead(m, f); @@ -1365,11 +1404,7 @@ static int mount_setup_unit( if (!u) { delete = true; - u = unit_new(m, sizeof(Mount)); - if (!u) - return log_oom(); - - r = unit_add_name(u, e); + r = unit_new_for_name(m, sizeof(Mount), e, &u); if (r < 0) goto fail; @@ -1456,17 +1491,9 @@ static int mount_setup_unit( MOUNT(u)->from_proc_self_mountinfo = true; - free(p->what); - p->what = w; - w = NULL; - - free(p->options); - p->options = o; - o = NULL; - - free(p->fstype); - p->fstype = f; - f = NULL; + free_and_replace(p->what, w); + free_and_replace(p->options, o); + free_and_replace(p->fstype, f); if (load_extras) { r = mount_add_extras(MOUNT(u)); @@ -1572,11 +1599,46 @@ static int mount_get_timeout(Unit *u, usec_t *timeout) { return 1; } +static int synthesize_root_mount(Manager *m) { + Unit *u; + int r; + + assert(m); + + /* Whatever happens, we know for sure that the root directory is around, and cannot go away. Let's + * unconditionally synthesize it here and mark it as perpetual. */ + + u = manager_get_unit(m, SPECIAL_ROOT_MOUNT); + if (!u) { + r = unit_new_for_name(m, sizeof(Mount), SPECIAL_ROOT_MOUNT, &u); + if (r < 0) + return log_error_errno(r, "Failed to allocate the special " SPECIAL_ROOT_MOUNT " unit: %m"); + } + + u->perpetual = true; + MOUNT(u)->deserialized_state = MOUNT_MOUNTED; + + unit_add_to_load_queue(u); + unit_add_to_dbus_queue(u); + + return 0; +} + +static bool mount_is_mounted(Mount *m) { + assert(m); + + return UNIT(m)->perpetual || m->is_mounted; +} + static void mount_enumerate(Manager *m) { int r; assert(m); + r = synthesize_root_mount(m); + if (r < 0) + goto fail; + mnt_init_debug(0); if (!m->mount_monitor) { @@ -1683,7 +1745,7 @@ static int mount_dispatch_io(sd_event_source *source, int fd, uint32_t revents, LIST_FOREACH(units_by_type, u, m->units_by_type[UNIT_MOUNT]) { Mount *mount = MOUNT(u); - if (!mount->is_mounted) { + if (!mount_is_mounted(mount)) { /* A mount point is not around right now. It * might be gone, or might never have @@ -1722,9 +1784,10 @@ static int mount_dispatch_io(sd_event_source *source, int fd, uint32_t revents, case MOUNT_DEAD: case MOUNT_FAILED: - /* This has just been mounted by - * somebody else, follow the state - * change. */ + + /* This has just been mounted by somebody else, follow the state change, but let's + * generate a new invocation ID for this implicitly and automatically. */ + (void) unit_acquire_invocation_id(UNIT(mount)); mount_enter_mounted(mount, MOUNT_SUCCESS); break; @@ -1743,7 +1806,7 @@ static int mount_dispatch_io(sd_event_source *source, int fd, uint32_t revents, } } - if (mount->is_mounted && + if (mount_is_mounted(mount) && mount->from_proc_self_mountinfo && mount->parameters_proc_self_mountinfo.what) { @@ -1817,6 +1880,7 @@ const UnitVTable mount_vtable = { .cgroup_context_offset = offsetof(Mount, cgroup_context), .kill_context_offset = offsetof(Mount, kill_context), .exec_runtime_offset = offsetof(Mount, exec_runtime), + .dynamic_creds_offset = offsetof(Mount, dynamic_creds), .sections = "Unit\0" diff --git a/src/core/mount.h b/src/core/mount.h index da529c44f4..9f7326ba6a 100644 --- a/src/core/mount.h +++ b/src/core/mount.h @@ -21,8 +21,8 @@ typedef struct Mount Mount; -#include "execute.h" #include "kill.h" +#include "dynamic-user.h" typedef enum MountExecCommand { MOUNT_EXEC_MOUNT, @@ -71,6 +71,9 @@ struct Mount { bool sloppy_options; + bool lazy_unmount; + bool force_unmount; + MountResult result; MountResult reload_result; @@ -85,6 +88,7 @@ struct Mount { CGroupContext cgroup_context; ExecRuntime *exec_runtime; + DynamicCreds dynamic_creds; MountState state, deserialized_state; diff --git a/src/core/namespace.c b/src/core/namespace.c index 52a2505d94..1195e9a854 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -29,6 +29,7 @@ #include "alloc-util.h" #include "dev-setup.h" #include "fd-util.h" +#include "fs-util.h" #include "loopback-setup.h" #include "missing.h" #include "mkdir.h" @@ -53,61 +54,245 @@ typedef enum MountMode { PRIVATE_TMP, PRIVATE_VAR_TMP, PRIVATE_DEV, - READWRITE + READWRITE, } MountMode; typedef struct BindMount { - const char *path; + const char *path; /* stack memory, doesn't need to be freed explicitly */ + char *chased; /* malloc()ed memory, needs to be freed */ MountMode mode; - bool done; - bool ignore; + bool ignore; /* Ignore if path does not exist */ } BindMount; +typedef struct TargetMount { + const char *path; + MountMode mode; + bool ignore; /* Ignore if path does not exist */ +} TargetMount; + +/* + * The following Protect tables are to protect paths and mark some of them + * READONLY, in case a path is covered by an option from another table, then + * it is marked READWRITE in the current one, and the more restrictive mode is + * applied from that other table. This way all options can be combined in a + * safe and comprehensible way for users. + */ + +/* ProtectKernelTunables= option and the related filesystem APIs */ +static const TargetMount protect_kernel_tunables_table[] = { + { "/proc/sys", READONLY, false }, + { "/proc/sysrq-trigger", READONLY, true }, + { "/proc/latency_stats", READONLY, true }, + { "/proc/mtrr", READONLY, true }, + { "/proc/apm", READONLY, true }, + { "/proc/acpi", READONLY, true }, + { "/proc/timer_stats", READONLY, true }, + { "/proc/asound", READONLY, true }, + { "/proc/bus", READONLY, true }, + { "/proc/fs", READONLY, true }, + { "/proc/irq", READONLY, true }, + { "/sys", READONLY, false }, + { "/sys/kernel/debug", READONLY, true }, + { "/sys/kernel/tracing", READONLY, true }, + { "/sys/fs/cgroup", READWRITE, false }, /* READONLY is set by ProtectControlGroups= option */ +}; + +/* ProtectKernelModules= option */ +static const TargetMount protect_kernel_modules_table[] = { +#ifdef HAVE_SPLIT_USR + { "/lib/modules", INACCESSIBLE, true }, +#endif + { "/usr/lib/modules", INACCESSIBLE, true }, +}; + +/* + * ProtectHome=read-only table, protect $HOME and $XDG_RUNTIME_DIR and rest of + * system should be protected by ProtectSystem= + */ +static const TargetMount protect_home_read_only_table[] = { + { "/home", READONLY, true }, + { "/run/user", READONLY, true }, + { "/root", READONLY, true }, +}; + +/* ProtectHome=yes table */ +static const TargetMount protect_home_yes_table[] = { + { "/home", INACCESSIBLE, true }, + { "/run/user", INACCESSIBLE, true }, + { "/root", INACCESSIBLE, true }, +}; + +/* ProtectSystem=yes table */ +static const TargetMount protect_system_yes_table[] = { + { "/usr", READONLY, false }, + { "/boot", READONLY, true }, + { "/efi", READONLY, true }, +}; + +/* ProtectSystem=full includes ProtectSystem=yes */ +static const TargetMount protect_system_full_table[] = { + { "/usr", READONLY, false }, + { "/boot", READONLY, true }, + { "/efi", READONLY, true }, + { "/etc", READONLY, false }, +}; + +/* + * ProtectSystem=strict table. In this strict mode, we mount everything + * read-only, except for /proc, /dev, /sys which are the kernel API VFS, + * which are left writable, but PrivateDevices= + ProtectKernelTunables= + * protect those, and these options should be fully orthogonal. + * (And of course /home and friends are also left writable, as ProtectHome= + * shall manage those, orthogonally). + */ +static const TargetMount protect_system_strict_table[] = { + { "/", READONLY, false }, + { "/proc", READWRITE, false }, /* ProtectKernelTunables= */ + { "/sys", READWRITE, false }, /* ProtectKernelTunables= */ + { "/dev", READWRITE, false }, /* PrivateDevices= */ + { "/home", READWRITE, true }, /* ProtectHome= */ + { "/run/user", READWRITE, true }, /* ProtectHome= */ + { "/root", READWRITE, true }, /* ProtectHome= */ +}; + +static void set_bind_mount(BindMount **p, const char *path, MountMode mode, bool ignore) { + (*p)->path = path; + (*p)->mode = mode; + (*p)->ignore = ignore; +} + static int append_mounts(BindMount **p, char **strv, MountMode mode) { char **i; assert(p); STRV_FOREACH(i, strv) { + bool ignore = false; - (*p)->ignore = false; - (*p)->done = false; - - if ((mode == INACCESSIBLE || mode == READONLY || mode == READWRITE) && (*i)[0] == '-') { - (*p)->ignore = true; + if (IN_SET(mode, INACCESSIBLE, READONLY, READWRITE) && startswith(*i, "-")) { (*i)++; + ignore = true; } if (!path_is_absolute(*i)) return -EINVAL; - (*p)->path = *i; - (*p)->mode = mode; + set_bind_mount(p, *i, mode, ignore); (*p)++; } return 0; } -static int mount_path_compare(const void *a, const void *b) { - const BindMount *p = a, *q = b; - int d; +static int append_target_mounts(BindMount **p, const char *root_directory, const TargetMount *mounts, const size_t size) { + unsigned i; - d = path_compare(p->path, q->path); + assert(p); + assert(mounts); + + for (i = 0; i < size; i++) { + /* + * Here we assume that the ignore field is set during + * declaration we do not support "-" at the beginning. + */ + const TargetMount *m = &mounts[i]; + const char *path = prefix_roota(root_directory, m->path); + + if (!path_is_absolute(path)) + return -EINVAL; + + set_bind_mount(p, path, m->mode, m->ignore); + (*p)++; + } + + return 0; +} + +static int append_protect_kernel_tunables(BindMount **p, const char *root_directory) { + assert(p); + + return append_target_mounts(p, root_directory, protect_kernel_tunables_table, + ELEMENTSOF(protect_kernel_tunables_table)); +} + +static int append_protect_kernel_modules(BindMount **p, const char *root_directory) { + assert(p); + + return append_target_mounts(p, root_directory, protect_kernel_modules_table, + ELEMENTSOF(protect_kernel_modules_table)); +} + +static int append_protect_home(BindMount **p, const char *root_directory, ProtectHome protect_home) { + int r = 0; + + assert(p); + + if (protect_home == PROTECT_HOME_NO) + return 0; + + switch (protect_home) { + case PROTECT_HOME_READ_ONLY: + r = append_target_mounts(p, root_directory, protect_home_read_only_table, + ELEMENTSOF(protect_home_read_only_table)); + break; + case PROTECT_HOME_YES: + r = append_target_mounts(p, root_directory, protect_home_yes_table, + ELEMENTSOF(protect_home_yes_table)); + break; + default: + r = -EINVAL; + break; + } + + return r; +} - if (d == 0) { - /* If the paths are equal, check the mode */ - if (p->mode < q->mode) - return -1; +static int append_protect_system(BindMount **p, const char *root_directory, ProtectSystem protect_system) { + int r = 0; - if (p->mode > q->mode) - return 1; + assert(p); + if (protect_system == PROTECT_SYSTEM_NO) return 0; + + switch (protect_system) { + case PROTECT_SYSTEM_STRICT: + r = append_target_mounts(p, root_directory, protect_system_strict_table, + ELEMENTSOF(protect_system_strict_table)); + break; + case PROTECT_SYSTEM_YES: + r = append_target_mounts(p, root_directory, protect_system_yes_table, + ELEMENTSOF(protect_system_yes_table)); + break; + case PROTECT_SYSTEM_FULL: + r = append_target_mounts(p, root_directory, protect_system_full_table, + ELEMENTSOF(protect_system_full_table)); + break; + default: + r = -EINVAL; + break; } + return r; +} + +static int mount_path_compare(const void *a, const void *b) { + const BindMount *p = a, *q = b; + int d; + /* If the paths are not equal, then order prefixes first */ - return d; + d = path_compare(p->path, q->path); + if (d != 0) + return d; + + /* If the paths are equal, check the mode */ + if (p->mode < q->mode) + return -1; + + if (p->mode > q->mode) + return 1; + + return 0; } static void drop_duplicates(BindMount *m, unsigned *n) { @@ -116,16 +301,110 @@ static void drop_duplicates(BindMount *m, unsigned *n) { assert(m); assert(n); + /* Drops duplicate entries. Expects that the array is properly ordered already. */ + for (f = m, t = m, previous = NULL; f < m+*n; f++) { - /* The first one wins */ - if (previous && path_equal(f->path, previous->path)) + /* The first one wins (which is the one with the more restrictive mode), see mount_path_compare() + * above. */ + if (previous && path_equal(f->path, previous->path)) { + log_debug("%s is duplicate.", f->path); continue; + } *t = *f; - previous = t; + t++; + } + + *n = t - m; +} + +static void drop_inaccessible(BindMount *m, unsigned *n) { + BindMount *f, *t; + const char *clear = NULL; + assert(m); + assert(n); + + /* Drops all entries obstructed by another entry further up the tree. Expects that the array is properly + * ordered already. */ + + for (f = m, t = m; f < m+*n; f++) { + + /* If we found a path set for INACCESSIBLE earlier, and this entry has it as prefix we should drop + * it, as inaccessible paths really should drop the entire subtree. */ + if (clear && path_startswith(f->path, clear)) { + log_debug("%s is masked by %s.", f->path, clear); + continue; + } + + clear = f->mode == INACCESSIBLE ? f->path : NULL; + + *t = *f; + t++; + } + + *n = t - m; +} + +static void drop_nop(BindMount *m, unsigned *n) { + BindMount *f, *t; + + assert(m); + assert(n); + + /* Drops all entries which have an immediate parent that has the same type, as they are redundant. Assumes the + * list is ordered by prefixes. */ + + for (f = m, t = m; f < m+*n; f++) { + + /* Only suppress such subtrees for READONLY and READWRITE entries */ + if (IN_SET(f->mode, READONLY, READWRITE)) { + BindMount *p; + bool found = false; + + /* Now let's find the first parent of the entry we are looking at. */ + for (p = t-1; p >= m; p--) { + if (path_startswith(f->path, p->path)) { + found = true; + break; + } + } + + /* We found it, let's see if it's the same mode, if so, we can drop this entry */ + if (found && p->mode == f->mode) { + log_debug("%s is redundant by %s", f->path, p->path); + continue; + } + } + + *t = *f; + t++; + } + + *n = t - m; +} + +static void drop_outside_root(const char *root_directory, BindMount *m, unsigned *n) { + BindMount *f, *t; + + assert(m); + assert(n); + + if (!root_directory) + return; + + /* Drops all mounts that are outside of the root directory. */ + + for (f = m, t = m; f < m+*n; f++) { + + if (!path_startswith(f->path, root_directory)) { + log_debug("%s is outside of root directory.", f->path); + continue; + } + + *t = *f; t++; } @@ -278,24 +557,23 @@ static int apply_mount( const char *what; int r; - struct stat target; assert(m); + log_debug("Applying namespace mount on %s", m->path); + switch (m->mode) { - case INACCESSIBLE: + case INACCESSIBLE: { + struct stat target; /* First, get rid of everything that is below if there * is anything... Then, overmount it with an * inaccessible path. */ - umount_recursive(m->path, 0); + (void) umount_recursive(m->path, 0); - if (lstat(m->path, &target) < 0) { - if (m->ignore && errno == ENOENT) - return 0; - return -errno; - } + if (lstat(m->path, &target) < 0) + return log_debug_errno(errno, "Failed to lstat() %s to determine what to mount over it: %m", m->path); what = mode_to_inaccessible_node(target.st_mode); if (!what) { @@ -303,11 +581,20 @@ static int apply_mount( return -ELOOP; } break; + } + case READONLY: case READWRITE: - /* Nothing to mount here, we just later toggle the - * MS_RDONLY bit for the mount point */ - return 0; + + r = path_is_mount_point(m->path, 0); + if (r < 0) + return log_debug_errno(r, "Failed to determine whether %s is already a mount point: %m", m->path); + if (r > 0) /* Nothing to do here, it is already a mount. We just later toggle the MS_RDONLY bit for the mount point if needed. */ + return 0; + + /* This isn't a mount point yet, let's make it one. */ + what = m->path; + break; case PRIVATE_TMP: what = tmp_dir; @@ -326,68 +613,133 @@ static int apply_mount( assert(what); - r = mount(what, m->path, NULL, MS_BIND|MS_REC, NULL); - if (r >= 0) { - log_debug("Successfully mounted %s to %s", what, m->path); - return r; - } else { - if (m->ignore && errno == ENOENT) - return 0; + if (mount(what, m->path, NULL, MS_BIND|MS_REC, NULL) < 0) return log_debug_errno(errno, "Failed to mount %s to %s: %m", what, m->path); - } + + log_debug("Successfully mounted %s to %s", what, m->path); + return 0; } -static int make_read_only(BindMount *m) { - int r; +static int make_read_only(BindMount *m, char **blacklist) { + int r = 0; assert(m); if (IN_SET(m->mode, INACCESSIBLE, READONLY)) - r = bind_remount_recursive(m->path, true); - else if (IN_SET(m->mode, READWRITE, PRIVATE_TMP, PRIVATE_VAR_TMP, PRIVATE_DEV)) { - r = bind_remount_recursive(m->path, false); - if (r == 0 && m->mode == PRIVATE_DEV) /* can be readonly but the submounts can't*/ - if (mount(NULL, m->path, NULL, MS_REMOUNT|DEV_MOUNT_OPTIONS|MS_RDONLY, NULL) < 0) - r = -errno; + r = bind_remount_recursive(m->path, true, blacklist); + else if (m->mode == PRIVATE_DEV) { /* Can be readonly but the submounts can't*/ + if (mount(NULL, m->path, NULL, MS_REMOUNT|DEV_MOUNT_OPTIONS|MS_RDONLY, NULL) < 0) + r = -errno; } else - r = 0; - - if (m->ignore && r == -ENOENT) return 0; + /* Not that we only turn on the MS_RDONLY flag here, we never turn it off. Something that was marked read-only + * already stays this way. This improves compatibility with container managers, where we won't attempt to undo + * read-only mounts already applied. */ + return r; } +static int chase_all_symlinks(const char *root_directory, BindMount *m, unsigned *n) { + BindMount *f, *t; + int r; + + assert(m); + assert(n); + + /* Since mount() will always follow symlinks and we need to take the different root directory into account we + * chase the symlinks on our own first. This call wil do so for all entries and remove all entries where we + * can't resolve the path, and which have been marked for such removal. */ + + for (f = m, t = m; f < m+*n; f++) { + + r = chase_symlinks(f->path, root_directory, &f->chased); + if (r == -ENOENT && f->ignore) /* Doesn't exist? Then remove it! */ + continue; + if (r < 0) + return log_debug_errno(r, "Failed to chase symlinks for %s: %m", f->path); + + if (path_equal(f->path, f->chased)) + f->chased = mfree(f->chased); + else { + log_debug("Chased %s → %s", f->path, f->chased); + f->path = f->chased; + } + + *t = *f; + t++; + } + + *n = t - m; + return 0; +} + +static unsigned namespace_calculate_mounts( + const NameSpaceInfo *ns_info, + char** read_write_paths, + char** read_only_paths, + char** inaccessible_paths, + const char* tmp_dir, + const char* var_tmp_dir, + ProtectHome protect_home, + ProtectSystem protect_system) { + + unsigned protect_home_cnt; + unsigned protect_system_cnt = + (protect_system == PROTECT_SYSTEM_STRICT ? + ELEMENTSOF(protect_system_strict_table) : + ((protect_system == PROTECT_SYSTEM_FULL) ? + ELEMENTSOF(protect_system_full_table) : + ((protect_system == PROTECT_SYSTEM_YES) ? + ELEMENTSOF(protect_system_yes_table) : 0))); + + protect_home_cnt = + (protect_home == PROTECT_HOME_YES ? + ELEMENTSOF(protect_home_yes_table) : + ((protect_home == PROTECT_HOME_READ_ONLY) ? + ELEMENTSOF(protect_home_read_only_table) : 0)); + + return !!tmp_dir + !!var_tmp_dir + + strv_length(read_write_paths) + + strv_length(read_only_paths) + + strv_length(inaccessible_paths) + + ns_info->private_dev + + (ns_info->protect_kernel_tunables ? ELEMENTSOF(protect_kernel_tunables_table) : 0) + + (ns_info->protect_control_groups ? 1 : 0) + + (ns_info->protect_kernel_modules ? ELEMENTSOF(protect_kernel_modules_table) : 0) + + protect_home_cnt + protect_system_cnt; +} + int setup_namespace( const char* root_directory, + const NameSpaceInfo *ns_info, char** read_write_paths, char** read_only_paths, char** inaccessible_paths, const char* tmp_dir, const char* var_tmp_dir, - bool private_dev, ProtectHome protect_home, ProtectSystem protect_system, unsigned long mount_flags) { BindMount *m, *mounts = NULL; + bool make_slave = false; unsigned n; int r = 0; if (mount_flags == 0) mount_flags = MS_SHARED; - if (unshare(CLONE_NEWNS) < 0) - return -errno; + n = namespace_calculate_mounts(ns_info, + read_write_paths, + read_only_paths, + inaccessible_paths, + tmp_dir, var_tmp_dir, + protect_home, protect_system); - n = !!tmp_dir + !!var_tmp_dir + - strv_length(read_write_paths) + - strv_length(read_only_paths) + - strv_length(inaccessible_paths) + - private_dev + - (protect_home != PROTECT_HOME_NO ? 3 : 0) + - (protect_system != PROTECT_SYSTEM_NO ? 2 : 0) + - (protect_system == PROTECT_SYSTEM_FULL ? 1 : 0); + /* Set mount slave mode */ + if (root_directory || n > 0) + make_slave = true; if (n > 0) { m = mounts = (BindMount *) alloca0(n * sizeof(BindMount)); @@ -415,100 +767,127 @@ int setup_namespace( m++; } - if (private_dev) { + if (ns_info->private_dev) { m->path = prefix_roota(root_directory, "/dev"); m->mode = PRIVATE_DEV; m++; } - if (protect_home != PROTECT_HOME_NO) { - const char *home_dir, *run_user_dir, *root_dir; - - home_dir = prefix_roota(root_directory, "/home"); - home_dir = strjoina("-", home_dir); - run_user_dir = prefix_roota(root_directory, "/run/user"); - run_user_dir = strjoina("-", run_user_dir); - root_dir = prefix_roota(root_directory, "/root"); - root_dir = strjoina("-", root_dir); - - r = append_mounts(&m, STRV_MAKE(home_dir, run_user_dir, root_dir), - protect_home == PROTECT_HOME_READ_ONLY ? READONLY : INACCESSIBLE); + if (ns_info->protect_kernel_tunables) { + r = append_protect_kernel_tunables(&m, root_directory); if (r < 0) return r; } - if (protect_system != PROTECT_SYSTEM_NO) { - const char *usr_dir, *boot_dir, *etc_dir; - - usr_dir = prefix_roota(root_directory, "/usr"); - boot_dir = prefix_roota(root_directory, "/boot"); - boot_dir = strjoina("-", boot_dir); - etc_dir = prefix_roota(root_directory, "/etc"); - - r = append_mounts(&m, protect_system == PROTECT_SYSTEM_FULL - ? STRV_MAKE(usr_dir, boot_dir, etc_dir) - : STRV_MAKE(usr_dir, boot_dir), READONLY); + if (ns_info->protect_kernel_modules) { + r = append_protect_kernel_modules(&m, root_directory); if (r < 0) return r; } + if (ns_info->protect_control_groups) { + m->path = prefix_roota(root_directory, "/sys/fs/cgroup"); + m->mode = READONLY; + m++; + } + + r = append_protect_home(&m, root_directory, protect_home); + if (r < 0) + return r; + + r = append_protect_system(&m, root_directory, protect_system); + if (r < 0) + return r; + assert(mounts + n == m); + /* Resolve symlinks manually first, as mount() will always follow them relative to the host's + * root. Moreover we want to suppress duplicates based on the resolved paths. This of course is a bit + * racy. */ + r = chase_all_symlinks(root_directory, mounts, &n); + if (r < 0) + goto finish; + qsort(mounts, n, sizeof(BindMount), mount_path_compare); + drop_duplicates(mounts, &n); + drop_outside_root(root_directory, mounts, &n); + drop_inaccessible(mounts, &n); + drop_nop(mounts, &n); } - if (n > 0 || root_directory) { + if (unshare(CLONE_NEWNS) < 0) { + r = -errno; + goto finish; + } + + if (make_slave) { /* Remount / as SLAVE so that nothing now mounted in the namespace shows up in the parent */ - if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) - return -errno; + if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) { + r = -errno; + goto finish; + } } if (root_directory) { - /* Turn directory into bind mount */ - if (mount(root_directory, root_directory, NULL, MS_BIND|MS_REC, NULL) < 0) - return -errno; + /* Turn directory into bind mount, if it isn't one yet */ + r = path_is_mount_point(root_directory, AT_SYMLINK_FOLLOW); + if (r < 0) + goto finish; + if (r == 0) { + if (mount(root_directory, root_directory, NULL, MS_BIND|MS_REC, NULL) < 0) { + r = -errno; + goto finish; + } + } } if (n > 0) { + char **blacklist; + unsigned j; + + /* First round, add in all special mounts we need */ for (m = mounts; m < mounts + n; ++m) { r = apply_mount(m, tmp_dir, var_tmp_dir); if (r < 0) - goto fail; + goto finish; } + /* Create a blacklist we can pass to bind_mount_recursive() */ + blacklist = newa(char*, n+1); + for (j = 0; j < n; j++) + blacklist[j] = (char*) mounts[j].path; + blacklist[j] = NULL; + + /* Second round, flip the ro bits if necessary. */ for (m = mounts; m < mounts + n; ++m) { - r = make_read_only(m); + r = make_read_only(m, blacklist); if (r < 0) - goto fail; + goto finish; } } if (root_directory) { /* MS_MOVE does not work on MS_SHARED so the remount MS_SHARED will be done later */ r = mount_move_root(root_directory); - - /* at this point, we cannot rollback */ if (r < 0) - return r; + goto finish; } /* Remount / as the desired mode. Not that this will not * reestablish propagation from our side to the host, since * what's disconnected is disconnected. */ - if (mount(NULL, "/", NULL, mount_flags | MS_REC, NULL) < 0) - /* at this point, we cannot rollback */ - return -errno; + if (mount(NULL, "/", NULL, mount_flags | MS_REC, NULL) < 0) { + r = -errno; + goto finish; + } - return 0; + r = 0; -fail: - if (n > 0) { - for (m = mounts; m < mounts + n; ++m) - if (m->done) - (void) umount2(m->path, MNT_DETACH); - } +finish: + for (m = mounts; m < mounts + n; m++) + free(m->chased); return r; } @@ -658,6 +1037,7 @@ static const char *const protect_system_table[_PROTECT_SYSTEM_MAX] = { [PROTECT_SYSTEM_NO] = "no", [PROTECT_SYSTEM_YES] = "yes", [PROTECT_SYSTEM_FULL] = "full", + [PROTECT_SYSTEM_STRICT] = "strict", }; DEFINE_STRING_TABLE_LOOKUP(protect_system, ProtectSystem); diff --git a/src/core/namespace.h b/src/core/namespace.h index 1aedf5f208..6310638e9a 100644 --- a/src/core/namespace.h +++ b/src/core/namespace.h @@ -4,6 +4,7 @@ This file is part of systemd. Copyright 2010 Lennart Poettering + Copyright 2016 Djalal Harouni 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 @@ -19,6 +20,8 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +typedef struct NameSpaceInfo NameSpaceInfo; + #include <stdbool.h> #include "macro.h" @@ -35,17 +38,25 @@ typedef enum ProtectSystem { PROTECT_SYSTEM_NO, PROTECT_SYSTEM_YES, PROTECT_SYSTEM_FULL, + PROTECT_SYSTEM_STRICT, _PROTECT_SYSTEM_MAX, _PROTECT_SYSTEM_INVALID = -1 } ProtectSystem; +struct NameSpaceInfo { + bool private_dev:1; + bool protect_control_groups:1; + bool protect_kernel_tunables:1; + bool protect_kernel_modules:1; +}; + int setup_namespace(const char *chroot, + const NameSpaceInfo *ns_info, char **read_write_paths, char **read_only_paths, char **inaccessible_paths, const char *tmp_dir, const char *var_tmp_dir, - bool private_dev, ProtectHome protect_home, ProtectSystem protect_system, unsigned long mount_flags); diff --git a/src/core/org.freedesktop.systemd1.conf b/src/core/org.freedesktop.systemd1.conf index 3c64f20872..a61677e645 100644 --- a/src/core/org.freedesktop.systemd1.conf +++ b/src/core/org.freedesktop.systemd1.conf @@ -54,6 +54,10 @@ <allow send_destination="org.freedesktop.systemd1" send_interface="org.freedesktop.systemd1.Manager" + send_member="GetUnitByInvocationID"/> + + <allow send_destination="org.freedesktop.systemd1" + send_interface="org.freedesktop.systemd1.Manager" send_member="LoadUnit"/> <allow send_destination="org.freedesktop.systemd1" @@ -90,6 +94,10 @@ <allow send_destination="org.freedesktop.systemd1" send_interface="org.freedesktop.systemd1.Manager" + send_member="GetUnitFileLinks"/> + + <allow send_destination="org.freedesktop.systemd1" + send_interface="org.freedesktop.systemd1.Manager" send_member="ListJobs"/> <allow send_destination="org.freedesktop.systemd1" @@ -108,6 +116,14 @@ send_interface="org.freedesktop.systemd1.Manager" send_member="GetDefaultTarget"/> + <allow send_destination="org.freedesktop.systemd1" + send_interface="org.freedesktop.systemd1.Manager" + send_member="LookupDynamicUserByName"/> + + <allow send_destination="org.freedesktop.systemd1" + send_interface="org.freedesktop.systemd1.Manager" + send_member="LookupDynamicUserByUID"/> + <!-- Managed via polkit or other criteria --> <allow send_destination="org.freedesktop.systemd1" @@ -176,6 +192,14 @@ <allow send_destination="org.freedesktop.systemd1" send_interface="org.freedesktop.systemd1.Manager" + send_member="RefUnit"/> + + <allow send_destination="org.freedesktop.systemd1" + send_interface="org.freedesktop.systemd1.Manager" + send_member="UnrefUnit"/> + + <allow send_destination="org.freedesktop.systemd1" + send_interface="org.freedesktop.systemd1.Manager" send_member="EnableUnitFiles"/> <allow send_destination="org.freedesktop.systemd1" diff --git a/src/core/path.c b/src/core/path.c index 0dd0d375d8..83f794be89 100644 --- a/src/core/path.c +++ b/src/core/path.c @@ -454,7 +454,7 @@ static int path_coldplug(Unit *u) { static void path_enter_dead(Path *p, PathResult f) { assert(p); - if (f != PATH_SUCCESS) + if (p->result == PATH_SUCCESS) p->result = f; path_set_state(p, p->result != PATH_SUCCESS ? PATH_FAILED : PATH_DEAD); @@ -577,6 +577,10 @@ static int path_start(Unit *u) { return r; } + r = unit_acquire_invocation_id(u); + if (r < 0) + return r; + path_mkdir(p); p->result = PATH_SUCCESS; diff --git a/src/core/scope.c b/src/core/scope.c index b45e238974..d6e1f8e392 100644 --- a/src/core/scope.c +++ b/src/core/scope.c @@ -147,6 +147,32 @@ static int scope_verify(Scope *s) { return 0; } +static int scope_load_init_scope(Unit *u) { + assert(u); + + if (!unit_has_name(u, SPECIAL_INIT_SCOPE)) + return 0; + + u->transient = true; + u->perpetual = true; + + /* init.scope is a bit special, as it has to stick around forever. Because of its special semantics we + * synthesize it here, instead of relying on the unit file on disk. */ + + u->default_dependencies = false; + u->ignore_on_isolate = true; + + SCOPE(u)->kill_context.kill_signal = SIGRTMIN+14; + + /* Prettify things, if we can. */ + if (!u->description) + u->description = strdup("System and Service Manager"); + if (!u->documentation) + (void) strv_extend(&u->documentation, "man:systemd(1)"); + + return 1; +} + static int scope_load(Unit *u) { Scope *s = SCOPE(u); int r; @@ -158,6 +184,9 @@ static int scope_load(Unit *u) { /* Refuse to load non-transient scope units, but allow them while reloading. */ return -ENOENT; + r = scope_load_init_scope(u); + if (r < 0) + return r; r = unit_load_fragment_and_dropin_optional(u); if (r < 0) return r; @@ -221,7 +250,7 @@ static void scope_dump(Unit *u, FILE *f, const char *prefix) { static void scope_enter_dead(Scope *s, ScopeResult f) { assert(s); - if (f != SCOPE_SUCCESS) + if (s->result == SCOPE_SUCCESS) s->result = f; scope_set_state(s, s->result != SCOPE_SUCCESS ? SCOPE_FAILED : SCOPE_DEAD); @@ -233,7 +262,7 @@ static void scope_enter_signal(Scope *s, ScopeState state, ScopeResult f) { assert(s); - if (f != SCOPE_SUCCESS) + if (s->result == SCOPE_SUCCESS) s->result = f; unit_watch_all_pids(UNIT(s)); @@ -298,6 +327,10 @@ static int scope_start(Unit *u) { if (!u->transient && !MANAGER_IS_RELOADING(u->manager)) return -ENOENT; + r = unit_acquire_invocation_id(u); + if (r < 0) + return r; + (void) unit_realize_cgroup(u); (void) unit_reset_cpu_usage(u); @@ -441,7 +474,7 @@ static void scope_sigchld_event(Unit *u, pid_t pid, int code, int status) { /* If the PID set is empty now, then let's finish this off (On unified we use proper notifications) */ - if (cg_unified() <= 0 && set_isempty(u->pids)) + if (cg_unified(SYSTEMD_CGROUP_CONTROLLER) <= 0 && set_isempty(u->pids)) scope_notify_cgroup_empty_event(u); } @@ -530,34 +563,16 @@ static void scope_enumerate(Manager *m) { u = manager_get_unit(m, SPECIAL_INIT_SCOPE); if (!u) { - u = unit_new(m, sizeof(Scope)); - if (!u) { - log_oom(); - return; - } - - r = unit_add_name(u, SPECIAL_INIT_SCOPE); + r = unit_new_for_name(m, sizeof(Scope), SPECIAL_INIT_SCOPE, &u); if (r < 0) { - unit_free(u); - log_error_errno(r, "Failed to add init.scope name"); + log_error_errno(r, "Failed to allocate the special " SPECIAL_INIT_SCOPE " unit: %m"); return; } } u->transient = true; - u->default_dependencies = false; - u->no_gc = true; - u->ignore_on_isolate = true; - u->refuse_manual_start = true; - u->refuse_manual_stop = true; + u->perpetual = true; SCOPE(u)->deserialized_state = SCOPE_RUNNING; - SCOPE(u)->kill_context.kill_signal = SIGRTMIN+14; - - /* Prettify things, if we can. */ - if (!u->description) - u->description = strdup("System and Service Manager"); - if (!u->documentation) - (void) strv_extend(&u->documentation, "man:systemd(1)"); unit_add_to_load_queue(u); unit_add_to_dbus_queue(u); diff --git a/src/core/selinux-access.h b/src/core/selinux-access.h index 8f1f058a32..f46370d020 100644 --- a/src/core/selinux-access.h +++ b/src/core/selinux-access.h @@ -33,7 +33,7 @@ int mac_selinux_generic_access_check(sd_bus_message *message, const char *path, #define mac_selinux_unit_access_check(unit, message, permission, error) \ ({ \ - Unit *_unit = (unit); \ + const Unit *_unit = (unit); \ mac_selinux_generic_access_check((message), _unit->source_path ?: _unit->fragment_path, (permission), (error)); \ }) diff --git a/src/core/service.c b/src/core/service.c index afb198507b..a7274a758f 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -289,7 +289,17 @@ static void service_fd_store_unlink(ServiceFDStore *fs) { free(fs); } -static void service_release_resources(Unit *u) { +static void service_release_fd_store(Service *s) { + assert(s); + + log_unit_debug(UNIT(s), "Releasing all stored fds"); + while (s->fd_store) + service_fd_store_unlink(s->fd_store); + + assert(s->n_fd_store == 0); +} + +static void service_release_resources(Unit *u, bool inactive) { Service *s = SERVICE(u); assert(s); @@ -297,16 +307,14 @@ static void service_release_resources(Unit *u) { if (!s->fd_store && s->stdin_fd < 0 && s->stdout_fd < 0 && s->stderr_fd < 0) return; - log_unit_debug(u, "Releasing all resources."); + log_unit_debug(u, "Releasing resources."); s->stdin_fd = safe_close(s->stdin_fd); s->stdout_fd = safe_close(s->stdout_fd); s->stderr_fd = safe_close(s->stderr_fd); - while (s->fd_store) - service_fd_store_unlink(s->fd_store); - - assert(s->n_fd_store == 0); + if (inactive) + service_release_fd_store(s); } static void service_done(Unit *u) { @@ -322,6 +330,8 @@ static void service_done(Unit *u) { s->control_command = NULL; s->main_command = NULL; + dynamic_creds_unref(&s->dynamic_creds); + exit_status_set_free(&s->restart_prevent_status); exit_status_set_free(&s->restart_force_status); exit_status_set_free(&s->success_status); @@ -340,6 +350,7 @@ static void service_done(Unit *u) { s->bus_name_owner = mfree(s->bus_name_owner); service_close_socket_fd(s); + s->peer = socket_peer_unref(s->peer); unit_ref_unset(&s->accept_socket); @@ -347,7 +358,7 @@ static void service_done(Unit *u) { s->timer_event_source = sd_event_source_unref(s->timer_event_source); - service_release_resources(u); + service_release_resources(u, true); } static int on_fd_store_io(sd_event_source *e, int fd, uint32_t revents, void *userdata) { @@ -357,6 +368,10 @@ static int on_fd_store_io(sd_event_source *e, int fd, uint32_t revents, void *us assert(fs); /* If we get either EPOLLHUP or EPOLLERR, it's time to remove this entry from the fd store */ + log_unit_debug(UNIT(fs->service), + "Received %s on stored fd %d (%s), closing.", + revents & EPOLLERR ? "EPOLLERR" : "EPOLLHUP", + fs->fd, strna(fs->fdname)); service_fd_store_unlink(fs); return 0; } @@ -365,20 +380,23 @@ static int service_add_fd_store(Service *s, int fd, const char *name) { ServiceFDStore *fs; int r; + /* fd is always consumed if we return >= 0 */ + assert(s); assert(fd >= 0); if (s->n_fd_store >= s->n_fd_store_max) - return 0; + return -EXFULL; /* Our store is full. + * Use this errno rather than E[NM]FILE to distinguish from + * the case where systemd itself hits the file limit. */ LIST_FOREACH(fd_store, fs, s->fd_store) { r = same_fd(fs->fd, fd); if (r < 0) return r; if (r > 0) { - /* Already included */ safe_close(fd); - return 1; + return 0; /* fd already included */ } } @@ -406,7 +424,7 @@ static int service_add_fd_store(Service *s, int fd, const char *name) { LIST_PREPEND(fd_store, s->fd_store, fs); s->n_fd_store++; - return 1; + return 1; /* fd newly stored */ } static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name) { @@ -414,10 +432,7 @@ static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name) { assert(s); - if (fdset_size(fds) <= 0) - return 0; - - while (s->n_fd_store < s->n_fd_store_max) { + while (fdset_size(fds) > 0) { _cleanup_close_ int fd = -1; fd = fdset_steal_first(fds); @@ -425,17 +440,17 @@ static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name) { break; r = service_add_fd_store(s, fd, name); + if (r == -EXFULL) + return log_unit_warning_errno(UNIT(s), r, + "Cannot store more fds than FileDescriptorStoreMax=%u, closing remaining.", + s->n_fd_store_max); if (r < 0) - return log_unit_error_errno(UNIT(s), r, "Couldn't add fd to fd store: %m"); - if (r > 0) { - log_unit_debug(UNIT(s), "Added fd to fd store."); - fd = -1; - } + return log_unit_error_errno(UNIT(s), r, "Failed to add fd to store: %m"); + if (r > 0) + log_unit_debug(UNIT(s), "Added fd %u (%s) to fd store.", fd, strna(name)); + fd = -1; } - if (fdset_size(fds) > 0) - log_unit_warning(UNIT(s), "Tried to store more fds than FileDescriptorStoreMax=%u allows, closing remaining.", s->n_fd_store_max); - return 0; } @@ -758,6 +773,11 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) { prefix, s->bus_name, prefix, yes_no(s->bus_name_good)); + if (UNIT_ISSET(s->accept_socket)) + fprintf(f, + "%sAccept Socket: %s\n", + prefix, UNIT_DEREF(s->accept_socket)->id); + kill_context_dump(&s->kill_context, f, prefix); exec_context_dump(&s->exec_context, f, prefix); @@ -1030,6 +1050,23 @@ static int service_coldplug(Unit *u) { if (IN_SET(s->deserialized_state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) service_start_watchdog(s); + if (!IN_SET(s->deserialized_state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART)) + (void) unit_setup_dynamic_creds(u); + + if (UNIT_ISSET(s->accept_socket)) { + Socket* socket = SOCKET(UNIT_DEREF(s->accept_socket)); + + if (socket->max_connections_per_source > 0) { + SocketPeer *peer; + + /* Make a best-effort attempt at bumping the connection count */ + if (socket_acquire_peer(socket, s->socket_fd, &peer) > 0) { + socket_peer_unref(s->peer); + s->peer = peer; + } + } + } + service_set_state(s, s->deserialized_state); return 0; } @@ -1146,11 +1183,7 @@ static int service_spawn( Service *s, ExecCommand *c, usec_t timeout, - bool pass_fds, - bool apply_permissions, - bool apply_chroot, - bool apply_tty_stdin, - bool is_control, + ExecFlags flags, pid_t *_pid) { _cleanup_strv_free_ char **argv = NULL, **final_env = NULL, **our_env = NULL, **fd_names = NULL; @@ -1160,12 +1193,10 @@ static int service_spawn( pid_t pid; ExecParameters exec_params = { - .apply_permissions = apply_permissions, - .apply_chroot = apply_chroot, - .apply_tty_stdin = apply_tty_stdin, - .stdin_fd = -1, - .stdout_fd = -1, - .stderr_fd = -1, + .flags = flags, + .stdin_fd = -1, + .stdout_fd = -1, + .stderr_fd = -1, }; int r; @@ -1174,6 +1205,14 @@ static int service_spawn( assert(c); assert(_pid); + if (flags & EXEC_IS_CONTROL) { + /* If this is a control process, mask the permissions/chroot application if this is requested. */ + if (s->permissions_start_only) + exec_params.flags &= ~EXEC_APPLY_PERMISSIONS; + if (s->root_directory_start_only) + exec_params.flags &= ~EXEC_APPLY_CHROOT; + } + (void) unit_realize_cgroup(UNIT(s)); if (s->reset_cpu_usage) { (void) unit_reset_cpu_usage(UNIT(s)); @@ -1184,7 +1223,11 @@ static int service_spawn( if (r < 0) return r; - if (pass_fds || + r = unit_setup_dynamic_creds(UNIT(s)); + if (r < 0) + return r; + + if ((flags & EXEC_PASS_FDS) || s->exec_context.std_input == EXEC_INPUT_SOCKET || s->exec_context.std_output == EXEC_OUTPUT_SOCKET || s->exec_context.std_error == EXEC_OUTPUT_SOCKET) { @@ -1194,6 +1237,7 @@ static int service_spawn( return r; n_fds = r; + log_unit_debug(UNIT(s), "Passing %i fds to service", n_fds); } r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), timeout)); @@ -1204,11 +1248,11 @@ static int service_spawn( if (r < 0) return r; - our_env = new0(char*, 6); + our_env = new0(char*, 9); if (!our_env) return -ENOMEM; - if (is_control ? s->notify_access == NOTIFY_ALL : s->notify_access != NOTIFY_NONE) + if ((flags & EXEC_IS_CONTROL) ? s->notify_access == NOTIFY_ALL : s->notify_access != NOTIFY_NONE) if (asprintf(our_env + n_env++, "NOTIFY_SOCKET=%s", UNIT(s)->manager->notify_socket) < 0) return -ENOMEM; @@ -1216,7 +1260,7 @@ static int service_spawn( if (asprintf(our_env + n_env++, "MAINPID="PID_FMT, s->main_pid) < 0) return -ENOMEM; - if (!MANAGER_IS_SYSTEM(UNIT(s)->manager)) + if (MANAGER_IS_USER(UNIT(s)->manager)) if (asprintf(our_env + n_env++, "MANAGERPID="PID_FMT, getpid()) < 0) return -ENOMEM; @@ -1225,10 +1269,16 @@ static int service_spawn( socklen_t salen = sizeof(sa); r = getpeername(s->socket_fd, &sa.sa, &salen); - if (r < 0) - return -errno; + if (r < 0) { + r = -errno; - if (IN_SET(sa.sa.sa_family, AF_INET, AF_INET6)) { + /* ENOTCONN is legitimate if the endpoint disappeared on shutdown. + * This connection is over, but the socket unit lives on. */ + if (r != -ENOTCONN || !IN_SET(s->control_command_id, SERVICE_EXEC_STOP, SERVICE_EXEC_STOP_POST)) + return r; + } + + if (r == 0 && IN_SET(sa.sa.sa_family, AF_INET, AF_INET6)) { _cleanup_free_ char *addr = NULL; char *t; int port; @@ -1252,22 +1302,40 @@ static int service_spawn( } } + if (flags & EXEC_SETENV_RESULT) { + if (asprintf(our_env + n_env++, "SERVICE_RESULT=%s", service_result_to_string(s->result)) < 0) + return -ENOMEM; + + if (s->main_exec_status.pid > 0 && + dual_timestamp_is_set(&s->main_exec_status.exit_timestamp)) { + if (asprintf(our_env + n_env++, "EXIT_CODE=%s", sigchld_code_to_string(s->main_exec_status.code)) < 0) + return -ENOMEM; + + if (s->main_exec_status.code == CLD_EXITED) + r = asprintf(our_env + n_env++, "EXIT_STATUS=%i", s->main_exec_status.status); + else + r = asprintf(our_env + n_env++, "EXIT_STATUS=%s", signal_to_string(s->main_exec_status.status)); + if (r < 0) + return -ENOMEM; + } + } + final_env = strv_env_merge(2, UNIT(s)->manager->environment, our_env, NULL); if (!final_env) return -ENOMEM; - if (is_control && UNIT(s)->cgroup_path) { + if ((flags & EXEC_IS_CONTROL) && UNIT(s)->cgroup_path) { path = strjoina(UNIT(s)->cgroup_path, "/control"); (void) cg_create(SYSTEMD_CGROUP_CONTROLLER, path); } else path = UNIT(s)->cgroup_path; exec_params.argv = argv; + exec_params.environment = final_env; exec_params.fds = fds; exec_params.fd_names = fd_names; exec_params.n_fds = n_fds; - exec_params.environment = final_env; - exec_params.confirm_spawn = UNIT(s)->manager->confirm_spawn; + exec_params.flags |= UNIT(s)->manager->confirm_spawn ? EXEC_CONFIRM_SPAWN : 0; exec_params.cgroup_supported = UNIT(s)->manager->cgroup_supported; exec_params.cgroup_path = path; exec_params.cgroup_delegate = s->cgroup_context.delegate; @@ -1285,6 +1353,7 @@ static int service_spawn( &s->exec_context, &exec_params, s->exec_runtime, + &s->dynamic_creds, &pid); if (r < 0) return r; @@ -1392,14 +1461,14 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) int r; assert(s); - if (f != SERVICE_SUCCESS) + if (s->result == SERVICE_SUCCESS) s->result = f; service_set_state(s, s->result != SERVICE_SUCCESS ? SERVICE_FAILED : SERVICE_DEAD); if (s->result != SERVICE_SUCCESS) { log_unit_warning(UNIT(s), "Failed with result '%s'.", service_result_to_string(s->result)); - failure_action(UNIT(s)->manager, s->failure_action, UNIT(s)->reboot_arg); + emergency_action(UNIT(s)->manager, s->emergency_action, UNIT(s)->reboot_arg, "service failed"); } if (allow_restart && service_shall_restart(s)) { @@ -1418,9 +1487,15 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) exec_runtime_destroy(s->exec_runtime); s->exec_runtime = exec_runtime_unref(s->exec_runtime); - /* Also, remove the runtime directory in */ + /* Also, remove the runtime directory */ exec_context_destroy_runtime_directory(&s->exec_context, manager_get_runtime_prefix(UNIT(s)->manager)); + /* Get rid of the IPC bits of the user */ + unit_unref_uid_gid(UNIT(s), true); + + /* Release the user, and destroy it if we are the only remaining owner */ + dynamic_creds_destroy(&s->dynamic_creds); + /* Try to delete the pid file. At this point it will be * out-of-date, and some software might be confused by it, so * let's remove it. */ @@ -1438,7 +1513,7 @@ static void service_enter_stop_post(Service *s, ServiceResult f) { int r; assert(s); - if (f != SERVICE_SUCCESS) + if (s->result == SERVICE_SUCCESS) s->result = f; service_unwatch_control_pid(s); @@ -1451,11 +1526,7 @@ static void service_enter_stop_post(Service *s, ServiceResult f) { r = service_spawn(s, s->control_command, s->timeout_stop_usec, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - true, - true, + EXEC_APPLY_PERMISSIONS|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN|EXEC_IS_CONTROL|EXEC_SETENV_RESULT, &s->control_pid); if (r < 0) goto fail; @@ -1495,7 +1566,7 @@ static void service_enter_signal(Service *s, ServiceState state, ServiceResult f assert(s); - if (f != SERVICE_SUCCESS) + if (s->result == SERVICE_SUCCESS) s->result = f; unit_watch_all_pids(UNIT(s)); @@ -1553,7 +1624,7 @@ static void service_enter_stop(Service *s, ServiceResult f) { assert(s); - if (f != SERVICE_SUCCESS) + if (s->result == SERVICE_SUCCESS) s->result = f; service_unwatch_control_pid(s); @@ -1566,11 +1637,7 @@ static void service_enter_stop(Service *s, ServiceResult f) { r = service_spawn(s, s->control_command, s->timeout_stop_usec, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - false, - true, + EXEC_APPLY_PERMISSIONS|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL|EXEC_SETENV_RESULT, &s->control_pid); if (r < 0) goto fail; @@ -1609,7 +1676,7 @@ static bool service_good(Service *s) { static void service_enter_running(Service *s, ServiceResult f) { assert(s); - if (f != SERVICE_SUCCESS) + if (s->result == SERVICE_SUCCESS) s->result = f; service_unwatch_control_pid(s); @@ -1647,11 +1714,7 @@ static void service_enter_start_post(Service *s) { r = service_spawn(s, s->control_command, s->timeout_start_usec, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - false, - true, + EXEC_APPLY_PERMISSIONS|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL, &s->control_pid); if (r < 0) goto fail; @@ -1706,7 +1769,15 @@ static void service_enter_start(Service *s) { } if (!c) { - assert(s->type == SERVICE_ONESHOT); + if (s->type != SERVICE_ONESHOT) { + /* There's no command line configured for the main command? Hmm, that is strange. This can only + * happen if the configuration changes at runtime. In this case, let's enter a failure + * state. */ + log_unit_error(UNIT(s), "There's no 'start' task anymore we could start: %m"); + r = -ENXIO; + goto fail; + } + service_enter_start_post(s); return; } @@ -1721,11 +1792,7 @@ static void service_enter_start(Service *s) { r = service_spawn(s, c, timeout, - true, - true, - true, - true, - false, + EXEC_PASS_FDS|EXEC_APPLY_PERMISSIONS|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN|EXEC_SET_WATCHDOG, &pid); if (r < 0) goto fail; @@ -1784,11 +1851,7 @@ static void service_enter_start_pre(Service *s) { r = service_spawn(s, s->control_command, s->timeout_start_usec, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - true, - true, + EXEC_APPLY_PERMISSIONS|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL|EXEC_APPLY_TTY_STDIN, &s->control_pid); if (r < 0) goto fail; @@ -1863,11 +1926,7 @@ static void service_enter_reload(Service *s) { r = service_spawn(s, s->control_command, s->timeout_start_usec, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - false, - true, + EXEC_APPLY_PERMISSIONS|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL, &s->control_pid); if (r < 0) goto fail; @@ -1905,12 +1964,9 @@ static void service_run_next_control(Service *s) { r = service_spawn(s, s->control_command, timeout, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - s->control_command_id == SERVICE_EXEC_START_PRE || - s->control_command_id == SERVICE_EXEC_STOP_POST, - true, + EXEC_APPLY_PERMISSIONS|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL| + (IN_SET(s->control_command_id, SERVICE_EXEC_START_PRE, SERVICE_EXEC_STOP_POST) ? EXEC_APPLY_TTY_STDIN : 0)| + (IN_SET(s->control_command_id, SERVICE_EXEC_STOP, SERVICE_EXEC_STOP_POST) ? EXEC_SETENV_RESULT : 0), &s->control_pid); if (r < 0) goto fail; @@ -1948,11 +2004,7 @@ static void service_run_next_main(Service *s) { r = service_spawn(s, s->main_command, s->timeout_start_usec, - true, - true, - true, - true, - false, + EXEC_PASS_FDS|EXEC_APPLY_PERMISSIONS|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN|EXEC_SET_WATCHDOG, &pid); if (r < 0) goto fail; @@ -2002,6 +2054,10 @@ static int service_start(Unit *u) { return r; } + r = unit_acquire_invocation_id(u); + if (r < 0) + return r; + s->result = SERVICE_SUCCESS; s->reload_result = SERVICE_SUCCESS; s->main_pid_known = false; @@ -2116,6 +2172,12 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) { if (r < 0) return r; + if (UNIT_ISSET(s->accept_socket)) { + r = unit_serialize_item(u, f, "accept-socket", UNIT_DEREF(s->accept_socket)->id); + if (r < 0) + return r; + } + r = unit_serialize_item_fd(u, f, fds, "socket-fd", s->socket_fd); if (r < 0) return r; @@ -2246,6 +2308,17 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value, s->control_command_id = id; s->control_command = s->exec_command[id]; } + } else if (streq(key, "accept-socket")) { + Unit *socket; + + r = manager_load_unit(u->manager, value, NULL, NULL, &socket); + if (r < 0) + log_unit_debug_errno(u, r, "Failed to load accept-socket unit: %s", value); + else { + unit_ref_set(&s->accept_socket, socket); + SOCKET(socket)->n_connections++; + } + } else if (streq(key, "socket-fd")) { int fd; @@ -2276,7 +2349,7 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value, r = service_add_fd_store(s, fd, t); if (r < 0) log_unit_error_errno(u, r, "Failed to add fd to store: %m"); - else if (r > 0) + else fdset_remove(fds, fd); } @@ -2552,8 +2625,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { assert(s); assert(pid >= 0); - if (UNIT(s)->fragment_path ? is_clean_exit(code, status, &s->success_status) : - is_clean_exit_lsb(code, status, &s->success_status)) + if (is_clean_exit(code, status, s->type == SERVICE_ONESHOT ? EXIT_CLEAN_COMMAND : EXIT_CLEAN_DAEMON, &s->success_status)) f = SERVICE_SUCCESS; else if (code == CLD_EXITED) f = SERVICE_FAILURE_EXIT_CODE; @@ -2595,7 +2667,14 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { f = SERVICE_SUCCESS; } - log_struct(f == SERVICE_SUCCESS ? LOG_DEBUG : LOG_NOTICE, + /* When this is a successful exit, let's log about the exit code on DEBUG level. If this is a failure + * and the process exited on its own via exit(), then let's make this a NOTICE, under the assumption + * that the service already logged the reason at a higher log level on its own. However, if the service + * died due to a signal, then it most likely didn't say anything about any reason, hence let's raise + * our log level to WARNING then. */ + + log_struct(f == SERVICE_SUCCESS ? LOG_DEBUG : + (code == CLD_EXITED ? LOG_NOTICE : LOG_WARNING), LOG_UNIT_ID(u), LOG_UNIT_MESSAGE(u, "Main process exited, code=%s, status=%i/%s", sigchld_code_to_string(code), status, @@ -2606,7 +2685,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { "EXIT_STATUS=%i", status, NULL); - if (f != SERVICE_SUCCESS) + if (s->result == SERVICE_SUCCESS) s->result = f; if (s->main_command && @@ -2687,7 +2766,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { "Control process exited, code=%s status=%i", sigchld_code_to_string(code), status); - if (f != SERVICE_SUCCESS) + if (s->result == SERVICE_SUCCESS) s->result = f; /* Immediately get rid of the cgroup, so that the @@ -2827,7 +2906,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { /* If the PID set is empty now, then let's finish this off (On unified we use proper notifications) */ - if (cg_unified() <= 0 && set_isempty(u->pids)) + if (cg_unified(SYSTEMD_CGROUP_CONTROLLER) <= 0 && set_isempty(u->pids)) service_notify_cgroup_empty_event(u); } @@ -3037,9 +3116,7 @@ static void service_notify_message(Unit *u, pid_t pid, char **tags, FDSet *fds) if (!streq_ptr(s->status_text, t)) { - free(s->status_text); - s->status_text = t; - t = NULL; + free_and_replace(s->status_text, t); notify_dbus = true; } @@ -3323,6 +3400,7 @@ const UnitVTable service_vtable = { .cgroup_context_offset = offsetof(Service, cgroup_context), .kill_context_offset = offsetof(Service, kill_context), .exec_runtime_offset = offsetof(Service, exec_runtime), + .dynamic_creds_offset = offsetof(Service, dynamic_creds), .sections = "Unit\0" diff --git a/src/core/service.h b/src/core/service.h index cfef375b03..2869144fcb 100644 --- a/src/core/service.h +++ b/src/core/service.h @@ -148,9 +148,11 @@ struct Service { /* Runtime data of the execution context */ ExecRuntime *exec_runtime; + DynamicCreds dynamic_creds; pid_t main_pid, control_pid; int socket_fd; + SocketPeer *peer; bool socket_fd_selinux_context_net; bool permissions_start_only; @@ -176,7 +178,7 @@ struct Service { char *status_text; int status_errno; - FailureAction failure_action; + EmergencyAction emergency_action; UnitRef accept_socket; diff --git a/src/core/show-status.c b/src/core/show-status.c index 59ebdc7219..65f9cb888a 100644 --- a/src/core/show-status.c +++ b/src/core/show-status.c @@ -61,6 +61,11 @@ int status_vprintf(const char *status, bool ellipse, bool ephemeral, const char if (vasprintf(&s, format, ap) < 0) return log_oom(); + /* Before you ask: yes, on purpose we open/close the console for each status line we write individually. This + * is a good strategy to avoid PID 1 getting killed by the kernel's SAK concept (it doesn't fix this entirely, + * but minimizes the time window the kernel might end up killing PID 1 due to SAK). It also makes things easier + * for us so that we don't have to recover from hangups and suchlike triggered on the console. */ + fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); if (fd < 0) return fd; diff --git a/src/core/slice.c b/src/core/slice.c index c7700b8857..ed5d3fd701 100644 --- a/src/core/slice.c +++ b/src/core/slice.c @@ -130,6 +130,28 @@ static int slice_verify(Slice *s) { return 0; } +static int slice_load_root_slice(Unit *u) { + assert(u); + + if (!unit_has_name(u, SPECIAL_ROOT_SLICE)) + return 0; + + u->perpetual = true; + + /* The root slice is a bit special. For example it is always running and cannot be terminated. Because of its + * special semantics we synthesize it here, instead of relying on the unit file on disk. */ + + u->default_dependencies = false; + u->ignore_on_isolate = true; + + if (!u->description) + u->description = strdup("Root Slice"); + if (!u->documentation) + u->documentation = strv_new("man:systemd.special(7)", NULL); + + return 1; +} + static int slice_load(Unit *u) { Slice *s = SLICE(u); int r; @@ -137,6 +159,9 @@ static int slice_load(Unit *u) { assert(s); assert(u->load_state == UNIT_STUB); + r = slice_load_root_slice(u); + if (r < 0) + return r; r = unit_load_fragment_and_dropin_optional(u); if (r < 0) return r; @@ -187,10 +212,15 @@ static void slice_dump(Unit *u, FILE *f, const char *prefix) { static int slice_start(Unit *u) { Slice *t = SLICE(u); + int r; assert(t); assert(t->state == SLICE_DEAD); + r = unit_acquire_invocation_id(u); + if (r < 0) + return r; + (void) unit_realize_cgroup(u); (void) unit_reset_cpu_usage(u); @@ -269,32 +299,16 @@ static void slice_enumerate(Manager *m) { u = manager_get_unit(m, SPECIAL_ROOT_SLICE); if (!u) { - u = unit_new(m, sizeof(Slice)); - if (!u) { - log_oom(); - return; - } - - r = unit_add_name(u, SPECIAL_ROOT_SLICE); + r = unit_new_for_name(m, sizeof(Slice), SPECIAL_ROOT_SLICE, &u); if (r < 0) { - unit_free(u); - log_error_errno(r, "Failed to add -.slice name"); + log_error_errno(r, "Failed to allocate the special " SPECIAL_ROOT_SLICE " unit: %m"); return; } } - u->default_dependencies = false; - u->no_gc = true; - u->ignore_on_isolate = true; - u->refuse_manual_start = true; - u->refuse_manual_stop = true; + u->perpetual = true; SLICE(u)->deserialized_state = SLICE_ACTIVE; - if (!u->description) - u->description = strdup("Root Slice"); - if (!u->documentation) - (void) strv_extend(&u->documentation, "man:systemd.special(7)"); - unit_add_to_load_queue(u); unit_add_to_dbus_queue(u); } diff --git a/src/core/socket.c b/src/core/socket.c index e098055885..0b1c4acfec 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -57,6 +57,14 @@ #include "unit-printf.h" #include "unit.h" #include "user-util.h" +#include "in-addr-util.h" + +struct SocketPeer { + unsigned n_ref; + + Socket *socket; + union sockaddr_union peer; +}; static const UnitActiveState state_translation_table[_SOCKET_STATE_MAX] = { [SOCKET_DEAD] = UNIT_INACTIVE, @@ -141,15 +149,23 @@ void socket_free_ports(Socket *s) { static void socket_done(Unit *u) { Socket *s = SOCKET(u); + SocketPeer *p; assert(s); socket_free_ports(s); + while ((p = set_steal_first(s->peers_by_address))) + p->socket = NULL; + + s->peers_by_address = set_free(s->peers_by_address); + s->exec_runtime = exec_runtime_unref(s->exec_runtime); exec_command_free_array(s->exec_command, _SOCKET_EXEC_COMMAND_MAX); s->control_command = NULL; + dynamic_creds_unref(&s->dynamic_creds); + socket_unwatch_control_pid(s); unit_ref_unset(&s->service); @@ -466,6 +482,40 @@ static int socket_verify(Socket *s) { return 0; } +static void peer_address_hash_func(const void *p, struct siphash *state) { + const SocketPeer *s = p; + + assert(s); + assert(IN_SET(s->peer.sa.sa_family, AF_INET, AF_INET6)); + + if (s->peer.sa.sa_family == AF_INET) + siphash24_compress(&s->peer.in.sin_addr, sizeof(s->peer.in.sin_addr), state); + else + siphash24_compress(&s->peer.in6.sin6_addr, sizeof(s->peer.in6.sin6_addr), state); +} + +static int peer_address_compare_func(const void *a, const void *b) { + const SocketPeer *x = a, *y = b; + + if (x->peer.sa.sa_family < y->peer.sa.sa_family) + return -1; + if (x->peer.sa.sa_family > y->peer.sa.sa_family) + return 1; + + switch(x->peer.sa.sa_family) { + case AF_INET: + return memcmp(&x->peer.in.sin_addr, &y->peer.in.sin_addr, sizeof(x->peer.in.sin_addr)); + case AF_INET6: + return memcmp(&x->peer.in6.sin6_addr, &y->peer.in6.sin6_addr, sizeof(x->peer.in6.sin6_addr)); + } + assert_not_reached("Black sheep in the family!"); +} + +const struct hash_ops peer_address_hash_ops = { + .hash = peer_address_hash_func, + .compare = peer_address_compare_func +}; + static int socket_load(Unit *u) { Socket *s = SOCKET(u); int r; @@ -473,6 +523,10 @@ static int socket_load(Unit *u) { assert(u); assert(u->load_state == UNIT_STUB); + r = set_ensure_allocated(&s->peers_by_address, &peer_address_hash_ops); + if (r < 0) + return r; + r = unit_load_fragment_and_dropin(u); if (r < 0) return r; @@ -487,6 +541,87 @@ static int socket_load(Unit *u) { return socket_verify(s); } +static SocketPeer *socket_peer_new(void) { + SocketPeer *p; + + p = new0(SocketPeer, 1); + if (!p) + return NULL; + + p->n_ref = 1; + + return p; +} + +SocketPeer *socket_peer_ref(SocketPeer *p) { + if (!p) + return NULL; + + assert(p->n_ref > 0); + p->n_ref++; + + return p; +} + +SocketPeer *socket_peer_unref(SocketPeer *p) { + if (!p) + return NULL; + + assert(p->n_ref > 0); + + p->n_ref--; + + if (p->n_ref > 0) + return NULL; + + if (p->socket) + set_remove(p->socket->peers_by_address, p); + + return mfree(p); +} + +int socket_acquire_peer(Socket *s, int fd, SocketPeer **p) { + _cleanup_(socket_peer_unrefp) SocketPeer *remote = NULL; + SocketPeer sa = {}, *i; + socklen_t salen = sizeof(sa.peer); + int r; + + assert(fd >= 0); + assert(s); + + r = getpeername(fd, &sa.peer.sa, &salen); + if (r < 0) + return log_error_errno(errno, "getpeername failed: %m"); + + if (!IN_SET(sa.peer.sa.sa_family, AF_INET, AF_INET6)) { + *p = NULL; + return 0; + } + + i = set_get(s->peers_by_address, &sa); + if (i) { + *p = socket_peer_ref(i); + return 1; + } + + remote = socket_peer_new(); + if (!remote) + return log_oom(); + + remote->peer = sa.peer; + + r = set_put(s->peers_by_address, remote); + if (r < 0) + return r; + + remote->socket = s; + + *p = remote; + remote = NULL; + + return 1; +} + _const_ static const char* listen_lookup(int family, int type) { if (family == AF_NETLINK) @@ -1199,14 +1334,9 @@ static int usbffs_select_ep(const struct dirent *d) { static int usbffs_dispatch_eps(SocketPort *p) { _cleanup_free_ struct dirent **ent = NULL; - _cleanup_free_ char *path = NULL; int r, i, n, k; - path = dirname_malloc(p->path); - if (!path) - return -ENOMEM; - - r = scandir(path, &ent, usbffs_select_ep, alphasort); + r = scandir(p->path, &ent, usbffs_select_ep, alphasort); if (r < 0) return -errno; @@ -1221,7 +1351,7 @@ static int usbffs_dispatch_eps(SocketPort *p) { for (i = 0; i < n; ++i) { _cleanup_free_ char *ep = NULL; - ep = path_make_absolute(ent[i]->d_name, path); + ep = path_make_absolute(ent[i]->d_name, p->path); if (!ep) return -ENOMEM; @@ -1602,6 +1732,9 @@ static int socket_coldplug(Unit *u) { return r; } + if (!IN_SET(s->deserialized_state, SOCKET_DEAD, SOCKET_FAILED)) + (void) unit_setup_dynamic_creds(u); + socket_set_state(s, s->deserialized_state); return 0; } @@ -1611,12 +1744,10 @@ static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) { pid_t pid; int r; ExecParameters exec_params = { - .apply_permissions = true, - .apply_chroot = true, - .apply_tty_stdin = true, - .stdin_fd = -1, - .stdout_fd = -1, - .stderr_fd = -1, + .flags = EXEC_APPLY_PERMISSIONS|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN, + .stdin_fd = -1, + .stdout_fd = -1, + .stderr_fd = -1, }; assert(s); @@ -1633,6 +1764,10 @@ static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) { if (r < 0) return r; + r = unit_setup_dynamic_creds(UNIT(s)); + if (r < 0) + return r; + r = socket_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec)); if (r < 0) return r; @@ -1643,7 +1778,7 @@ static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) { exec_params.argv = argv; exec_params.environment = UNIT(s)->manager->environment; - exec_params.confirm_spawn = UNIT(s)->manager->confirm_spawn; + exec_params.flags |= UNIT(s)->manager->confirm_spawn ? EXEC_CONFIRM_SPAWN : 0; exec_params.cgroup_supported = UNIT(s)->manager->cgroup_supported; exec_params.cgroup_path = UNIT(s)->cgroup_path; exec_params.cgroup_delegate = s->cgroup_context.delegate; @@ -1654,6 +1789,7 @@ static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) { &s->exec_context, &exec_params, s->exec_runtime, + &s->dynamic_creds, &pid); if (r < 0) return r; @@ -1754,15 +1890,19 @@ fail: static void socket_enter_dead(Socket *s, SocketResult f) { assert(s); - if (f != SOCKET_SUCCESS) + if (s->result == SOCKET_SUCCESS) s->result = f; + socket_set_state(s, s->result != SOCKET_SUCCESS ? SOCKET_FAILED : SOCKET_DEAD); + exec_runtime_destroy(s->exec_runtime); s->exec_runtime = exec_runtime_unref(s->exec_runtime); exec_context_destroy_runtime_directory(&s->exec_context, manager_get_runtime_prefix(UNIT(s)->manager)); - socket_set_state(s, s->result != SOCKET_SUCCESS ? SOCKET_FAILED : SOCKET_DEAD); + unit_unref_uid_gid(UNIT(s), true); + + dynamic_creds_destroy(&s->dynamic_creds); } static void socket_enter_signal(Socket *s, SocketState state, SocketResult f); @@ -1771,7 +1911,7 @@ static void socket_enter_stop_post(Socket *s, SocketResult f) { int r; assert(s); - if (f != SOCKET_SUCCESS) + if (s->result == SOCKET_SUCCESS) s->result = f; socket_unwatch_control_pid(s); @@ -1799,7 +1939,7 @@ static void socket_enter_signal(Socket *s, SocketState state, SocketResult f) { assert(s); - if (f != SOCKET_SUCCESS) + if (s->result == SOCKET_SUCCESS) s->result = f; r = unit_kill_context( @@ -1843,7 +1983,7 @@ static void socket_enter_stop_pre(Socket *s, SocketResult f) { int r; assert(s); - if (f != SOCKET_SUCCESS) + if (s->result == SOCKET_SUCCESS) s->result = f; socket_unwatch_control_pid(s); @@ -2038,14 +2178,34 @@ static void socket_enter_running(Socket *s, int cfd) { socket_set_state(s, SOCKET_RUNNING); } else { _cleanup_free_ char *prefix = NULL, *instance = NULL, *name = NULL; + _cleanup_(socket_peer_unrefp) SocketPeer *p = NULL; Service *service; if (s->n_connections >= s->max_connections) { - log_unit_warning(UNIT(s), "Too many incoming connections (%u), refusing connection attempt.", s->n_connections); + log_unit_warning(UNIT(s), "Too many incoming connections (%u), dropping connection.", + s->n_connections); safe_close(cfd); return; } + if (s->max_connections_per_source > 0) { + r = socket_acquire_peer(s, cfd, &p); + if (r < 0) { + safe_close(cfd); + return; + } else if (r > 0 && p->n_ref > s->max_connections_per_source) { + _cleanup_free_ char *t = NULL; + + sockaddr_pretty(&p->peer.sa, FAMILY_ADDRESS_SIZE(p->peer.sa.sa_family), true, false, &t); + + log_unit_warning(UNIT(s), + "Too many incoming connections (%u) from source %s, dropping connection.", + p->n_ref, strnull(t)); + safe_close(cfd); + return; + } + } + r = socket_instantiate_service(s); if (r < 0) goto fail; @@ -2087,6 +2247,9 @@ static void socket_enter_running(Socket *s, int cfd) { cfd = -1; /* We passed ownership of the fd to the service now. Forget it here. */ s->n_connections++; + service->peer = p; /* Pass ownership of the peer reference */ + p = NULL; + r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT(service), JOB_REPLACE, &error, NULL); if (r < 0) { /* We failed to activate the new service, but it still exists. Let's make sure the service @@ -2191,11 +2354,14 @@ static int socket_start(Unit *u) { return r; } + r = unit_acquire_invocation_id(u); + if (r < 0) + return r; + s->result = SOCKET_SUCCESS; s->reset_cpu_usage = true; socket_enter_start_pre(s); - return 1; } @@ -2286,6 +2452,11 @@ static int socket_serialize(Unit *u, FILE *f, FDSet *fds) { return 0; } +static void socket_port_take_fd(SocketPort *p, FDSet *fds, int fd) { + safe_close(p->fd); + p->fd = fdset_remove(fds, fd); +} + static int socket_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { Socket *s = SOCKET(u); @@ -2340,18 +2511,13 @@ static int socket_deserialize_item(Unit *u, const char *key, const char *value, if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) log_unit_debug(u, "Failed to parse fifo value: %s", value); - else { - + else LIST_FOREACH(port, p, s->ports) if (p->type == SOCKET_FIFO && - path_equal_or_files_same(p->path, value+skip)) + path_equal_or_files_same(p->path, value+skip)) { + socket_port_take_fd(p, fds, fd); break; - - if (p) { - safe_close(p->fd); - p->fd = fdset_remove(fds, fd); - } - } + } } else if (streq(key, "special")) { int fd, skip = 0; @@ -2359,18 +2525,13 @@ static int socket_deserialize_item(Unit *u, const char *key, const char *value, if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) log_unit_debug(u, "Failed to parse special value: %s", value); - else { - + else LIST_FOREACH(port, p, s->ports) if (p->type == SOCKET_SPECIAL && - path_equal_or_files_same(p->path, value+skip)) + path_equal_or_files_same(p->path, value+skip)) { + socket_port_take_fd(p, fds, fd); break; - - if (p) { - safe_close(p->fd); - p->fd = fdset_remove(fds, fd); - } - } + } } else if (streq(key, "mqueue")) { int fd, skip = 0; @@ -2378,18 +2539,13 @@ static int socket_deserialize_item(Unit *u, const char *key, const char *value, if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) log_unit_debug(u, "Failed to parse mqueue value: %s", value); - else { - + else LIST_FOREACH(port, p, s->ports) if (p->type == SOCKET_MQUEUE && - streq(p->path, value+skip)) + streq(p->path, value+skip)) { + socket_port_take_fd(p, fds, fd); break; - - if (p) { - safe_close(p->fd); - p->fd = fdset_remove(fds, fd); - } - } + } } else if (streq(key, "socket")) { int fd, type, skip = 0; @@ -2397,17 +2553,12 @@ static int socket_deserialize_item(Unit *u, const char *key, const char *value, if (sscanf(value, "%i %i %n", &fd, &type, &skip) < 2 || fd < 0 || type < 0 || !fdset_contains(fds, fd)) log_unit_debug(u, "Failed to parse socket value: %s", value); - else { - + else LIST_FOREACH(port, p, s->ports) - if (socket_address_is(&p->address, value+skip, type)) + if (socket_address_is(&p->address, value+skip, type)) { + socket_port_take_fd(p, fds, fd); break; - - if (p) { - safe_close(p->fd); - p->fd = fdset_remove(fds, fd); - } - } + } } else if (streq(key, "netlink")) { int fd, skip = 0; @@ -2415,17 +2566,12 @@ static int socket_deserialize_item(Unit *u, const char *key, const char *value, if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) log_unit_debug(u, "Failed to parse socket value: %s", value); - else { - + else LIST_FOREACH(port, p, s->ports) - if (socket_address_is_netlink(&p->address, value+skip)) + if (socket_address_is_netlink(&p->address, value+skip)) { + socket_port_take_fd(p, fds, fd); break; - - if (p) { - safe_close(p->fd); - p->fd = fdset_remove(fds, fd); - } - } + } } else if (streq(key, "ffs")) { int fd, skip = 0; @@ -2433,18 +2579,13 @@ static int socket_deserialize_item(Unit *u, const char *key, const char *value, if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) log_unit_debug(u, "Failed to parse ffs value: %s", value); - else { - + else LIST_FOREACH(port, p, s->ports) if (p->type == SOCKET_USB_FUNCTION && - path_equal_or_files_same(p->path, value+skip)) + path_equal_or_files_same(p->path, value+skip)) { + socket_port_take_fd(p, fds, fd); break; - - if (p) { - safe_close(p->fd); - p->fd = fdset_remove(fds, fd); - } - } + } } else log_unit_debug(UNIT(s), "Unknown serialization key: %s", key); @@ -2605,7 +2746,7 @@ static void socket_sigchld_event(Unit *u, pid_t pid, int code, int status) { s->control_pid = 0; - if (is_clean_exit(code, status, NULL)) + if (is_clean_exit(code, status, EXIT_CLEAN_COMMAND, NULL)) f = SOCKET_SUCCESS; else if (code == CLD_EXITED) f = SOCKET_FAILURE_EXIT_CODE; @@ -2627,7 +2768,7 @@ static void socket_sigchld_event(Unit *u, pid_t pid, int code, int status) { "Control process exited, code=%s status=%i", sigchld_code_to_string(code), status); - if (f != SOCKET_SUCCESS) + if (s->result == SOCKET_SUCCESS) s->result = f; if (s->control_command && @@ -2930,6 +3071,7 @@ const UnitVTable socket_vtable = { .cgroup_context_offset = offsetof(Socket, cgroup_context), .kill_context_offset = offsetof(Socket, kill_context), .exec_runtime_offset = offsetof(Socket, exec_runtime), + .dynamic_creds_offset = offsetof(Socket, dynamic_creds), .sections = "Unit\0" diff --git a/src/core/socket.h b/src/core/socket.h index 0f1ac69c6f..89f4664510 100644 --- a/src/core/socket.h +++ b/src/core/socket.h @@ -20,6 +20,7 @@ ***/ typedef struct Socket Socket; +typedef struct SocketPeer SocketPeer; #include "mount.h" #include "service.h" @@ -79,9 +80,12 @@ struct Socket { LIST_HEAD(SocketPort, ports); + Set *peers_by_address; + unsigned n_accepted; unsigned n_connections; unsigned max_connections; + unsigned max_connections_per_source; unsigned backlog; unsigned keep_alive_cnt; @@ -94,7 +98,9 @@ struct Socket { ExecContext exec_context; KillContext kill_context; CGroupContext cgroup_context; + ExecRuntime *exec_runtime; + DynamicCreds dynamic_creds; /* For Accept=no sockets refers to the one service we'll activate. For Accept=yes sockets is either NULL, or filled @@ -162,6 +168,12 @@ struct Socket { RateLimit trigger_limit; }; +SocketPeer *socket_peer_ref(SocketPeer *p); +SocketPeer *socket_peer_unref(SocketPeer *p); +int socket_acquire_peer(Socket *s, int fd, SocketPeer **p); + +DEFINE_TRIVIAL_CLEANUP_FUNC(SocketPeer*, socket_peer_unref); + /* Called from the service code when collecting fds */ int socket_collect_fds(Socket *s, int **fds); diff --git a/src/core/swap.c b/src/core/swap.c index a532b15be8..2228a254bb 100644 --- a/src/core/swap.c +++ b/src/core/swap.c @@ -153,6 +153,8 @@ static void swap_done(Unit *u) { exec_command_done_array(s->exec_command, _SWAP_EXEC_COMMAND_MAX); s->control_command = NULL; + dynamic_creds_unref(&s->dynamic_creds); + swap_unwatch_control_pid(s); s->timer_event_source = sd_event_source_unref(s->timer_event_source); @@ -379,11 +381,7 @@ static int swap_setup_unit( if (!u) { delete = true; - u = unit_new(m, sizeof(Swap)); - if (!u) - return log_oom(); - - r = unit_add_name(u, e); + r = unit_new_for_name(m, sizeof(Swap), e, &u); if (r < 0) goto fail; @@ -553,6 +551,9 @@ static int swap_coldplug(Unit *u) { return r; } + if (!IN_SET(new_state, SWAP_DEAD, SWAP_FAILED)) + (void) unit_setup_dynamic_creds(u); + swap_set_state(s, new_state); return 0; } @@ -606,12 +607,10 @@ static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) { pid_t pid; int r; ExecParameters exec_params = { - .apply_permissions = true, - .apply_chroot = true, - .apply_tty_stdin = true, - .stdin_fd = -1, - .stdout_fd = -1, - .stderr_fd = -1, + .flags = EXEC_APPLY_PERMISSIONS|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN, + .stdin_fd = -1, + .stdout_fd = -1, + .stderr_fd = -1, }; assert(s); @@ -628,12 +627,16 @@ static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) { if (r < 0) goto fail; + r = unit_setup_dynamic_creds(UNIT(s)); + if (r < 0) + return r; + r = swap_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec)); if (r < 0) goto fail; exec_params.environment = UNIT(s)->manager->environment; - exec_params.confirm_spawn = UNIT(s)->manager->confirm_spawn; + exec_params.flags |= UNIT(s)->manager->confirm_spawn ? EXEC_CONFIRM_SPAWN : 0; exec_params.cgroup_supported = UNIT(s)->manager->cgroup_supported; exec_params.cgroup_path = UNIT(s)->cgroup_path; exec_params.cgroup_delegate = s->cgroup_context.delegate; @@ -644,6 +647,7 @@ static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) { &s->exec_context, &exec_params, s->exec_runtime, + &s->dynamic_creds, &pid); if (r < 0) goto fail; @@ -665,21 +669,25 @@ fail: static void swap_enter_dead(Swap *s, SwapResult f) { assert(s); - if (f != SWAP_SUCCESS) + if (s->result == SWAP_SUCCESS) s->result = f; + swap_set_state(s, s->result != SWAP_SUCCESS ? SWAP_FAILED : SWAP_DEAD); + exec_runtime_destroy(s->exec_runtime); s->exec_runtime = exec_runtime_unref(s->exec_runtime); exec_context_destroy_runtime_directory(&s->exec_context, manager_get_runtime_prefix(UNIT(s)->manager)); - swap_set_state(s, s->result != SWAP_SUCCESS ? SWAP_FAILED : SWAP_DEAD); + unit_unref_uid_gid(UNIT(s), true); + + dynamic_creds_destroy(&s->dynamic_creds); } static void swap_enter_active(Swap *s, SwapResult f) { assert(s); - if (f != SWAP_SUCCESS) + if (s->result == SWAP_SUCCESS) s->result = f; swap_set_state(s, SWAP_ACTIVE); @@ -690,7 +698,7 @@ static void swap_enter_signal(Swap *s, SwapState state, SwapResult f) { assert(s); - if (f != SWAP_SUCCESS) + if (s->result == SWAP_SUCCESS) s->result = f; r = unit_kill_context( @@ -849,6 +857,10 @@ static int swap_start(Unit *u) { return r; } + r = unit_acquire_invocation_id(u); + if (r < 0) + return r; + s->result = SWAP_SUCCESS; s->reset_cpu_usage = true; @@ -976,7 +988,7 @@ static void swap_sigchld_event(Unit *u, pid_t pid, int code, int status) { s->control_pid = 0; - if (is_clean_exit(code, status, NULL)) + if (is_clean_exit(code, status, EXIT_CLEAN_COMMAND, NULL)) f = SWAP_SUCCESS; else if (code == CLD_EXITED) f = SWAP_FAILURE_EXIT_CODE; @@ -987,7 +999,7 @@ static void swap_sigchld_event(Unit *u, pid_t pid, int code, int status) { else assert_not_reached("Unknown code"); - if (f != SWAP_SUCCESS) + if (s->result == SWAP_SUCCESS) s->result = f; if (s->control_command) { @@ -1177,6 +1189,7 @@ static int swap_dispatch_io(sd_event_source *source, int fd, uint32_t revents, v case SWAP_DEAD: case SWAP_FAILED: + (void) unit_acquire_invocation_id(UNIT(swap)); swap_enter_active(swap, SWAP_SUCCESS); break; @@ -1466,6 +1479,7 @@ const UnitVTable swap_vtable = { .cgroup_context_offset = offsetof(Swap, cgroup_context), .kill_context_offset = offsetof(Swap, kill_context), .exec_runtime_offset = offsetof(Swap, exec_runtime), + .dynamic_creds_offset = offsetof(Swap, dynamic_creds), .sections = "Unit\0" diff --git a/src/core/swap.h b/src/core/swap.h index fbf66debdc..b0ef50f1e8 100644 --- a/src/core/swap.h +++ b/src/core/swap.h @@ -82,6 +82,7 @@ struct Swap { CGroupContext cgroup_context; ExecRuntime *exec_runtime; + DynamicCreds dynamic_creds; SwapState state, deserialized_state; diff --git a/src/core/system.conf b/src/core/system.conf index c6bb050aac..746572b7ff 100644 --- a/src/core/system.conf +++ b/src/core/system.conf @@ -21,6 +21,7 @@ #CrashChangeVT=no #CrashShell=no #CrashReboot=no +#CtrlAltDelBurstAction=reboot-force #CPUAffinity=1 2 #JoinControllers=cpu,cpuacct net_cls,net_prio #RuntimeWatchdogSec=0 diff --git a/src/core/target.c b/src/core/target.c index 61a91aad07..765c1f3fa4 100644 --- a/src/core/target.c +++ b/src/core/target.c @@ -124,10 +124,15 @@ static void target_dump(Unit *u, FILE *f, const char *prefix) { static int target_start(Unit *u) { Target *t = TARGET(u); + int r; assert(t); assert(t->state == TARGET_DEAD); + r = unit_acquire_invocation_id(u); + if (r < 0) + return r; + target_set_state(t, TARGET_ACTIVE); return 1; } diff --git a/src/core/timer.c b/src/core/timer.c index 3206296f09..2469a517ea 100644 --- a/src/core/timer.c +++ b/src/core/timer.c @@ -261,6 +261,8 @@ static void timer_set_state(Timer *t, TimerState state) { if (state != TIMER_WAITING) { t->monotonic_event_source = sd_event_source_unref(t->monotonic_event_source); t->realtime_event_source = sd_event_source_unref(t->realtime_event_source); + t->next_elapse_monotonic_or_boottime = USEC_INFINITY; + t->next_elapse_realtime = USEC_INFINITY; } if (state != old_state) @@ -291,7 +293,7 @@ static int timer_coldplug(Unit *u) { static void timer_enter_dead(Timer *t, TimerResult f) { assert(t); - if (f != TIMER_SUCCESS) + if (t->result == TIMER_SUCCESS) t->result = f; timer_set_state(t, t->result != TIMER_SUCCESS ? TIMER_FAILED : TIMER_DEAD); @@ -616,6 +618,10 @@ static int timer_start(Unit *u) { return r; } + r = unit_acquire_invocation_id(u); + if (r < 0) + return r; + t->last_trigger = DUAL_TIMESTAMP_NULL; /* Reenable all timers that depend on unit activation time */ @@ -632,7 +638,7 @@ static int timer_start(Unit *u) { /* The timer has never run before, * make sure a stamp file exists. */ - touch_file(t->stamp_path, true, USEC_INFINITY, UID_INVALID, GID_INVALID, MODE_INVALID); + (void) touch_file(t->stamp_path, true, USEC_INFINITY, UID_INVALID, GID_INVALID, MODE_INVALID); } t->result = TIMER_SUCCESS; diff --git a/src/core/transaction.c b/src/core/transaction.c index 8370b864fb..e22e3b30c2 100644 --- a/src/core/transaction.c +++ b/src/core/transaction.c @@ -1085,10 +1085,8 @@ Transaction *transaction_new(bool irreversible) { return NULL; tr->jobs = hashmap_new(NULL); - if (!tr->jobs) { - free(tr); - return NULL; - } + if (!tr->jobs) + return mfree(tr); tr->irreversible = irreversible; diff --git a/src/core/umount.c b/src/core/umount.c index c21a2be54e..1e5459ed80 100644 --- a/src/core/umount.c +++ b/src/core/umount.c @@ -375,7 +375,7 @@ static int mount_points_list_umount(MountPoint **head, bool *changed, bool log_e /* If we are in a container, don't attempt to read-only mount anything as that brings no real benefits, but might confuse the host, as we remount - the superblock here, not the bind mound. */ + the superblock here, not the bind mount. */ if (detect_container() <= 0) { _cleanup_free_ char *options = NULL; /* MS_REMOUNT requires that the data parameter diff --git a/src/core/unit.c b/src/core/unit.c index 4934a0e56f..e664e23892 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -37,6 +37,7 @@ #include "execute.h" #include "fileio-label.h" #include "formats-util.h" +#include "id128-util.h" #include "load-dropin.h" #include "load-fragment.h" #include "log.h" @@ -87,10 +88,8 @@ Unit *unit_new(Manager *m, size_t size) { return NULL; u->names = set_new(&string_hash_ops); - if (!u->names) { - free(u); - return NULL; - } + if (!u->names) + return mfree(u); u->manager = m; u->type = _UNIT_TYPE_INVALID; @@ -100,7 +99,9 @@ Unit *unit_new(Manager *m, size_t size) { u->on_failure_job_mode = JOB_REPLACE; u->cgroup_inotify_wd = -1; u->job_timeout = USEC_INFINITY; - u->sigchldgen = 0; + u->ref_uid = UID_INVALID; + u->ref_gid = GID_INVALID; + u->cpu_usage_last = NSEC_INFINITY; RATELIMIT_INIT(u->start_limit, m->default_start_limit_interval, m->default_start_limit_burst); RATELIMIT_INIT(u->auto_stop_ratelimit, 10 * USEC_PER_SEC, 16); @@ -108,11 +109,29 @@ Unit *unit_new(Manager *m, size_t size) { return u; } +int unit_new_for_name(Manager *m, size_t size, const char *name, Unit **ret) { + Unit *u; + int r; + + u = unit_new(m, size); + if (!u) + return -ENOMEM; + + r = unit_add_name(u, name); + if (r < 0) { + unit_free(u); + return r; + } + + *ret = u; + return r; +} + bool unit_has_name(Unit *u, const char *name) { assert(u); assert(name); - return !!set_get(u->names, (char*) name); + return set_contains(u->names, (char*) name); } static void unit_init(Unit *u) { @@ -301,6 +320,7 @@ int unit_set_description(Unit *u, const char *description) { bool unit_check_gc(Unit *u) { UnitActiveState state; + bool inactive; assert(u); if (u->job) @@ -310,24 +330,28 @@ bool unit_check_gc(Unit *u) { return true; state = unit_active_state(u); + inactive = state == UNIT_INACTIVE; /* If the unit is inactive and failed and no job is queued for * it, then release its runtime resources */ if (UNIT_IS_INACTIVE_OR_FAILED(state) && UNIT_VTABLE(u)->release_resources) - UNIT_VTABLE(u)->release_resources(u); + UNIT_VTABLE(u)->release_resources(u, inactive); /* But we keep the unit object around for longer when it is * referenced or configured to not be gc'ed */ - if (state != UNIT_INACTIVE) + if (!inactive) return true; - if (u->no_gc) + if (u->perpetual) return true; if (u->refs) return true; + if (sd_bus_track_count(u->bus_track) > 0) + return true; + if (UNIT_VTABLE(u)->check_gc) if (UNIT_VTABLE(u)->check_gc(u)) return true; @@ -508,11 +532,17 @@ void unit_free(Unit *u) { sd_bus_slot_unref(u->match_bus_slot); + sd_bus_track_unref(u->bus_track); + u->deserialized_refs = strv_free(u->deserialized_refs); + unit_free_requires_mounts_for(u); SET_FOREACH(t, u->names, i) hashmap_remove_value(u->manager->units, t, u); + if (!sd_id128_is_null(u->invocation_id)) + hashmap_remove_value(u->manager->units_by_invocation_id, &u->invocation_id, u); + if (u->job) { Job *j = u->job; job_uninstall(j); @@ -550,6 +580,8 @@ void unit_free(Unit *u) { unit_release_cgroup(u); + unit_unref_uid_gid(u, false); + (void) manager_update_failed_units(u->manager, u, false); set_remove(u->manager->startup_units, u); @@ -846,18 +878,14 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) { return r; } - if (c->std_output != EXEC_OUTPUT_KMSG && - c->std_output != EXEC_OUTPUT_SYSLOG && - c->std_output != EXEC_OUTPUT_JOURNAL && - c->std_output != EXEC_OUTPUT_KMSG_AND_CONSOLE && - c->std_output != EXEC_OUTPUT_SYSLOG_AND_CONSOLE && - c->std_output != EXEC_OUTPUT_JOURNAL_AND_CONSOLE && - c->std_error != EXEC_OUTPUT_KMSG && - c->std_error != EXEC_OUTPUT_SYSLOG && - c->std_error != EXEC_OUTPUT_JOURNAL && - c->std_error != EXEC_OUTPUT_KMSG_AND_CONSOLE && - c->std_error != EXEC_OUTPUT_JOURNAL_AND_CONSOLE && - c->std_error != EXEC_OUTPUT_SYSLOG_AND_CONSOLE) + if (!IN_SET(c->std_output, + EXEC_OUTPUT_JOURNAL, EXEC_OUTPUT_JOURNAL_AND_CONSOLE, + EXEC_OUTPUT_KMSG, EXEC_OUTPUT_KMSG_AND_CONSOLE, + EXEC_OUTPUT_SYSLOG, EXEC_OUTPUT_SYSLOG_AND_CONSOLE) && + !IN_SET(c->std_error, + EXEC_OUTPUT_JOURNAL, EXEC_OUTPUT_JOURNAL_AND_CONSOLE, + EXEC_OUTPUT_KMSG, EXEC_OUTPUT_KMSG_AND_CONSOLE, + EXEC_OUTPUT_SYSLOG, EXEC_OUTPUT_SYSLOG_AND_CONSOLE)) return 0; /* If syslog or kernel logging is requested, make sure our own @@ -894,6 +922,7 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { Unit *following; _cleanup_set_free_ Set *following_set = NULL; int r; + const char *n; assert(u); assert(u->type >= 0); @@ -915,6 +944,7 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { "%s\tGC Check Good: %s\n" "%s\tNeed Daemon Reload: %s\n" "%s\tTransient: %s\n" + "%s\tPerpetual: %s\n" "%s\tSlice: %s\n" "%s\tCGroup: %s\n" "%s\tCGroup realized: %s\n" @@ -933,6 +963,7 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { prefix, yes_no(unit_check_gc(u)), prefix, yes_no(unit_need_daemon_reload(u)), prefix, yes_no(u->transient), + prefix, yes_no(u->perpetual), prefix, strna(unit_slice_name(u)), prefix, strna(u->cgroup_path), prefix, yes_no(u->cgroup_realized), @@ -942,6 +973,10 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { SET_FOREACH(t, u->names, i) fprintf(f, "%s\tName: %s\n", prefix, t); + if (!sd_id128_is_null(u->invocation_id)) + fprintf(f, "%s\tInvocation ID: " SD_ID128_FORMAT_STR "\n", + prefix, SD_ID128_FORMAT_VAL(u->invocation_id)); + STRV_FOREACH(j, u->documentation) fprintf(f, "%s\tDocumentation: %s\n", prefix, *j); @@ -969,8 +1004,8 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { if (u->job_timeout != USEC_INFINITY) fprintf(f, "%s\tJob Timeout: %s\n", prefix, format_timespan(timespan, sizeof(timespan), u->job_timeout, 0)); - if (u->job_timeout_action != FAILURE_ACTION_NONE) - fprintf(f, "%s\tJob Timeout Action: %s\n", prefix, failure_action_to_string(u->job_timeout_action)); + if (u->job_timeout_action != EMERGENCY_ACTION_NONE) + fprintf(f, "%s\tJob Timeout Action: %s\n", prefix, emergency_action_to_string(u->job_timeout_action)); if (u->job_timeout_reboot_arg) fprintf(f, "%s\tJob Timeout Reboot Argument: %s\n", prefix, u->job_timeout_reboot_arg); @@ -1035,13 +1070,14 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { else if (u->load_state == UNIT_ERROR) fprintf(f, "%s\tLoad Error Code: %s\n", prefix, strerror(-u->load_error)); + for (n = sd_bus_track_first(u->bus_track); n; n = sd_bus_track_next(u->bus_track)) + fprintf(f, "%s\tBus Ref: %s\n", prefix, n); if (u->job) job_dump(u->job, f, prefix2); if (u->nop_job) job_dump(u->nop_job, f, prefix2); - } /* Common implementation for multiple backends */ @@ -1436,7 +1472,7 @@ static void unit_status_log_starting_stopping_reloading(Unit *u, JobType t) { format = unit_get_status_message_format(u, t); DISABLE_WARNING_FORMAT_NONLITERAL; - xsprintf(buf, format, unit_description(u)); + snprintf(buf, sizeof buf, format, unit_description(u)); REENABLE_WARNING; mid = t == JOB_START ? SD_MESSAGE_UNIT_STARTING : @@ -1476,7 +1512,7 @@ int unit_start_limit_test(Unit *u) { log_unit_warning(u, "Start request repeated too quickly."); u->start_limit_hit = true; - return failure_action(u->manager, u->start_limit_action, u->reboot_arg); + return emergency_action(u->manager, u->start_limit_action, u->reboot_arg, "unit failed"); } /* Errors: @@ -1602,6 +1638,18 @@ int unit_stop(Unit *u) { return UNIT_VTABLE(u)->stop(u); } +bool unit_can_stop(Unit *u) { + assert(u); + + if (!unit_supported(u)) + return false; + + if (u->perpetual) + return false; + + return !!UNIT_VTABLE(u)->stop; +} + /* Errors: * -EBADR: This unit type does not support reloading. * -ENOEXEC: Unit is not started. @@ -2136,13 +2184,20 @@ bool unit_job_is_applicable(Unit *u, JobType j) { case JOB_VERIFY_ACTIVE: case JOB_START: - case JOB_STOP: case JOB_NOP: + /* Note that we don't check unit_can_start() here. That's because .device units and suchlike are not + * startable by us but may appear due to external events, and it thus makes sense to permit enqueing + * jobs for it. */ return true; + case JOB_STOP: + /* Similar as above. However, perpetual units can never be stopped (neither explicitly nor due to + * external events), hence it makes no sense to permit enqueing such a request either. */ + return !u->perpetual; + case JOB_RESTART: case JOB_TRY_RESTART: - return unit_can_start(u); + return unit_can_stop(u) && unit_can_start(u); case JOB_RELOAD: case JOB_TRY_RELOAD: @@ -2212,6 +2267,11 @@ int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_referen return 0; } + if (d == UNIT_BEFORE && other->type == UNIT_DEVICE) { + log_unit_warning(u, "Dependency Before=%s ignored (.device units cannot be delayed)", other->id); + return 0; + } + r = set_ensure_allocated(&u->dependencies[d], NULL); if (r < 0) return r; @@ -2374,6 +2434,15 @@ char *unit_dbus_path(Unit *u) { return unit_dbus_path_from_name(u->id); } +char *unit_dbus_path_invocation_id(Unit *u) { + assert(u); + + if (sd_id128_is_null(u->invocation_id)) + return NULL; + + return unit_dbus_path_from_name(u->invocation_id_string); +} + int unit_set_slice(Unit *u, Unit *slice) { assert(u); assert(slice); @@ -2608,21 +2677,34 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) { unit_serialize_item(u, f, "assert-result", yes_no(u->assert_result)); unit_serialize_item(u, f, "transient", yes_no(u->transient)); - unit_serialize_item_format(u, f, "cpuacct-usage-base", "%" PRIu64, u->cpuacct_usage_base); + + unit_serialize_item_format(u, f, "cpu-usage-base", "%" PRIu64, u->cpu_usage_base); + if (u->cpu_usage_last != NSEC_INFINITY) + unit_serialize_item_format(u, f, "cpu-usage-last", "%" PRIu64, u->cpu_usage_last); if (u->cgroup_path) unit_serialize_item(u, f, "cgroup", u->cgroup_path); unit_serialize_item(u, f, "cgroup-realized", yes_no(u->cgroup_realized)); + if (uid_is_valid(u->ref_uid)) + unit_serialize_item_format(u, f, "ref-uid", UID_FMT, u->ref_uid); + if (gid_is_valid(u->ref_gid)) + unit_serialize_item_format(u, f, "ref-gid", GID_FMT, u->ref_gid); + + if (!sd_id128_is_null(u->invocation_id)) + unit_serialize_item_format(u, f, "invocation-id", SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(u->invocation_id)); + + bus_track_serialize(u->bus_track, f, "ref"); + if (serialize_jobs) { if (u->job) { fprintf(f, "job\n"); - job_serialize(u->job, f, fds); + job_serialize(u->job, f); } if (u->nop_job) { fprintf(f, "job\n"); - job_serialize(u->nop_job, f, fds); + job_serialize(u->nop_job, f); } } @@ -2752,7 +2834,7 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { if (!j) return log_oom(); - r = job_deserialize(j, f, fds); + r = job_deserialize(j, f); if (r < 0) { job_free(j); return r; @@ -2824,11 +2906,19 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { continue; - } else if (streq(l, "cpuacct-usage-base")) { + } else if (STR_IN_SET(l, "cpu-usage-base", "cpuacct-usage-base")) { + + r = safe_atou64(v, &u->cpu_usage_base); + if (r < 0) + log_unit_debug(u, "Failed to parse CPU usage base %s, ignoring.", v); + + continue; + + } else if (streq(l, "cpu-usage-last")) { - r = safe_atou64(v, &u->cpuacct_usage_base); + r = safe_atou64(v, &u->cpu_usage_last); if (r < 0) - log_unit_debug(u, "Failed to parse CPU usage %s, ignoring.", v); + log_unit_debug(u, "Failed to read CPU usage last %s, ignoring.", v); continue; @@ -2851,6 +2941,47 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { u->cgroup_realized = b; continue; + + } else if (streq(l, "ref-uid")) { + uid_t uid; + + r = parse_uid(v, &uid); + if (r < 0) + log_unit_debug(u, "Failed to parse referenced UID %s, ignoring.", v); + else + unit_ref_uid_gid(u, uid, GID_INVALID); + + continue; + + } else if (streq(l, "ref-gid")) { + gid_t gid; + + r = parse_gid(v, &gid); + if (r < 0) + log_unit_debug(u, "Failed to parse referenced GID %s, ignoring.", v); + else + unit_ref_uid_gid(u, UID_INVALID, gid); + + } else if (streq(l, "ref")) { + + r = strv_extend(&u->deserialized_refs, v); + if (r < 0) + log_oom(); + + continue; + } else if (streq(l, "invocation-id")) { + sd_id128_t id; + + r = sd_id128_from_string(v, &id); + if (r < 0) + log_unit_debug(u, "Failed to parse invocation id %s, ignoring.", v); + else { + r = unit_set_invocation_id(u, id); + if (r < 0) + log_unit_warning_errno(u, r, "Failed to set invocation ID for unit: %m"); + } + + continue; } if (unit_can_serialize(u)) { @@ -2925,7 +3056,8 @@ int unit_add_node_link(Unit *u, const char *what, bool wants, UnitDependency dep } int unit_coldplug(Unit *u) { - int r = 0, q = 0; + int r = 0, q; + char **i; assert(u); @@ -2936,21 +3068,29 @@ int unit_coldplug(Unit *u) { u->coldplugged = true; - if (UNIT_VTABLE(u)->coldplug) - r = UNIT_VTABLE(u)->coldplug(u); + STRV_FOREACH(i, u->deserialized_refs) { + q = bus_unit_track_add_name(u, *i); + if (q < 0 && r >= 0) + r = q; + } + u->deserialized_refs = strv_free(u->deserialized_refs); - if (u->job) - q = job_coldplug(u->job); + if (UNIT_VTABLE(u)->coldplug) { + q = UNIT_VTABLE(u)->coldplug(u); + if (q < 0 && r >= 0) + r = q; + } - if (r < 0) - return r; - if (q < 0) - return q; + if (u->job) { + q = job_coldplug(u->job); + if (q < 0 && r >= 0) + r = q; + } - return 0; + return r; } -static bool fragment_mtime_newer(const char *path, usec_t mtime) { +static bool fragment_mtime_newer(const char *path, usec_t mtime, bool path_masked) { struct stat st; if (!path) @@ -2960,12 +3100,12 @@ static bool fragment_mtime_newer(const char *path, usec_t mtime) { /* What, cannot access this anymore? */ return true; - if (mtime > 0) + if (path_masked) + /* For masked files check if they are still so */ + return !null_or_empty(&st); + else /* For non-empty files check the mtime */ return timespec_load(&st.st_mtim) > mtime; - else if (!null_or_empty(&st)) - /* For masked files check if they are still so */ - return true; return false; } @@ -2976,18 +3116,22 @@ bool unit_need_daemon_reload(Unit *u) { assert(u); - if (fragment_mtime_newer(u->fragment_path, u->fragment_mtime)) + /* For unit files, we allow masking… */ + if (fragment_mtime_newer(u->fragment_path, u->fragment_mtime, + u->load_state == UNIT_MASKED)) return true; - if (fragment_mtime_newer(u->source_path, u->source_mtime)) + /* Source paths should not be masked… */ + if (fragment_mtime_newer(u->source_path, u->source_mtime, false)) return true; (void) unit_find_dropin_paths(u, &t); if (!strv_equal(u->dropin_paths, t)) return true; + /* … any drop-ins that are masked are simply omitted from the list. */ STRV_FOREACH(path, u->dropin_paths) - if (fragment_mtime_newer(*path, u->dropin_mtime)) + if (fragment_mtime_newer(*path, u->dropin_mtime, false)) return true; return false; @@ -3224,6 +3368,33 @@ void unit_ref_unset(UnitRef *ref) { ref->unit = NULL; } +static int user_from_unit_name(Unit *u, char **ret) { + + static const uint8_t hash_key[] = { + 0x58, 0x1a, 0xaf, 0xe6, 0x28, 0x58, 0x4e, 0x96, + 0xb4, 0x4e, 0xf5, 0x3b, 0x8c, 0x92, 0x07, 0xec + }; + + _cleanup_free_ char *n = NULL; + int r; + + r = unit_name_to_prefix(u->id, &n); + if (r < 0) + return r; + + if (valid_user_group_name(n)) { + *ret = n; + n = NULL; + return 0; + } + + /* If we can't use the unit name as a user name, then let's hash it and use that */ + if (asprintf(ret, "_du%016" PRIx64, siphash24(n, strlen(n), hash_key)) < 0) + return -ENOMEM; + + return 0; +} + int unit_patch_contexts(Unit *u) { CGroupContext *cc; ExecContext *ec; @@ -3267,7 +3438,33 @@ int unit_patch_contexts(Unit *u) { ec->no_new_privileges = true; if (ec->private_devices) - ec->capability_bounding_set &= ~(UINT64_C(1) << CAP_MKNOD); + ec->capability_bounding_set &= ~((UINT64_C(1) << CAP_MKNOD) | (UINT64_C(1) << CAP_SYS_RAWIO)); + + if (ec->protect_kernel_modules) + ec->capability_bounding_set &= ~(UINT64_C(1) << CAP_SYS_MODULE); + + if (ec->dynamic_user) { + if (!ec->user) { + r = user_from_unit_name(u, &ec->user); + if (r < 0) + return r; + } + + if (!ec->group) { + ec->group = strdup(ec->user); + if (!ec->group) + return -ENOMEM; + } + + /* If the dynamic user option is on, let's make sure that the unit can't leave its UID/GID + * around in the file system or on IPC objects. Hence enforce a strict sandbox. */ + + ec->private_tmp = true; + ec->remove_ipc = true; + ec->protect_system = PROTECT_SYSTEM_STRICT; + if (ec->protect_home == PROTECT_HOME_NO) + ec->protect_home = PROTECT_HOME_READ_ONLY; + } } cc = unit_get_cgroup_context(u); @@ -3652,7 +3849,7 @@ int unit_kill_context( * there we get proper events. Hence rely on * them.*/ - if (cg_unified() > 0 || + if (cg_unified(SYSTEMD_CGROUP_CONTROLLER) > 0 || (detect_container() == 0 && !unit_cgroup_delegate(u))) wait_for_exit = true; @@ -3776,6 +3973,26 @@ int unit_setup_exec_runtime(Unit *u) { return exec_runtime_make(rt, unit_get_exec_context(u), u->id); } +int unit_setup_dynamic_creds(Unit *u) { + ExecContext *ec; + DynamicCreds *dcreds; + size_t offset; + + assert(u); + + offset = UNIT_VTABLE(u)->dynamic_creds_offset; + assert(offset > 0); + dcreds = (DynamicCreds*) ((uint8_t*) u + offset); + + ec = unit_get_exec_context(u); + assert(ec); + + if (!ec->dynamic_user) + return 0; + + return dynamic_creds_acquire(dcreds, u->manager, ec->user, ec->group); +} + bool unit_type_supported(UnitType t) { if (_unlikely_(t < 0)) return false; @@ -3869,3 +4086,198 @@ pid_t unit_main_pid(Unit *u) { return 0; } + +static void unit_unref_uid_internal( + Unit *u, + uid_t *ref_uid, + bool destroy_now, + void (*_manager_unref_uid)(Manager *m, uid_t uid, bool destroy_now)) { + + assert(u); + assert(ref_uid); + assert(_manager_unref_uid); + + /* Generic implementation of both unit_unref_uid() and unit_unref_gid(), under the assumption that uid_t and + * gid_t are actually the same time, with the same validity rules. + * + * Drops a reference to UID/GID from a unit. */ + + assert_cc(sizeof(uid_t) == sizeof(gid_t)); + assert_cc(UID_INVALID == (uid_t) GID_INVALID); + + if (!uid_is_valid(*ref_uid)) + return; + + _manager_unref_uid(u->manager, *ref_uid, destroy_now); + *ref_uid = UID_INVALID; +} + +void unit_unref_uid(Unit *u, bool destroy_now) { + unit_unref_uid_internal(u, &u->ref_uid, destroy_now, manager_unref_uid); +} + +void unit_unref_gid(Unit *u, bool destroy_now) { + unit_unref_uid_internal(u, (uid_t*) &u->ref_gid, destroy_now, manager_unref_gid); +} + +static int unit_ref_uid_internal( + Unit *u, + uid_t *ref_uid, + uid_t uid, + bool clean_ipc, + int (*_manager_ref_uid)(Manager *m, uid_t uid, bool clean_ipc)) { + + int r; + + assert(u); + assert(ref_uid); + assert(uid_is_valid(uid)); + assert(_manager_ref_uid); + + /* Generic implementation of both unit_ref_uid() and unit_ref_guid(), under the assumption that uid_t and gid_t + * are actually the same type, and have the same validity rules. + * + * Adds a reference on a specific UID/GID to this unit. Each unit referencing the same UID/GID maintains a + * reference so that we can destroy the UID/GID's IPC resources as soon as this is requested and the counter + * drops to zero. */ + + assert_cc(sizeof(uid_t) == sizeof(gid_t)); + assert_cc(UID_INVALID == (uid_t) GID_INVALID); + + if (*ref_uid == uid) + return 0; + + if (uid_is_valid(*ref_uid)) /* Already set? */ + return -EBUSY; + + r = _manager_ref_uid(u->manager, uid, clean_ipc); + if (r < 0) + return r; + + *ref_uid = uid; + return 1; +} + +int unit_ref_uid(Unit *u, uid_t uid, bool clean_ipc) { + return unit_ref_uid_internal(u, &u->ref_uid, uid, clean_ipc, manager_ref_uid); +} + +int unit_ref_gid(Unit *u, gid_t gid, bool clean_ipc) { + return unit_ref_uid_internal(u, (uid_t*) &u->ref_gid, (uid_t) gid, clean_ipc, manager_ref_gid); +} + +static int unit_ref_uid_gid_internal(Unit *u, uid_t uid, gid_t gid, bool clean_ipc) { + int r = 0, q = 0; + + assert(u); + + /* Reference both a UID and a GID in one go. Either references both, or neither. */ + + if (uid_is_valid(uid)) { + r = unit_ref_uid(u, uid, clean_ipc); + if (r < 0) + return r; + } + + if (gid_is_valid(gid)) { + q = unit_ref_gid(u, gid, clean_ipc); + if (q < 0) { + if (r > 0) + unit_unref_uid(u, false); + + return q; + } + } + + return r > 0 || q > 0; +} + +int unit_ref_uid_gid(Unit *u, uid_t uid, gid_t gid) { + ExecContext *c; + int r; + + assert(u); + + c = unit_get_exec_context(u); + + r = unit_ref_uid_gid_internal(u, uid, gid, c ? c->remove_ipc : false); + if (r < 0) + return log_unit_warning_errno(u, r, "Couldn't add UID/GID reference to unit, proceeding without: %m"); + + return r; +} + +void unit_unref_uid_gid(Unit *u, bool destroy_now) { + assert(u); + + unit_unref_uid(u, destroy_now); + unit_unref_gid(u, destroy_now); +} + +void unit_notify_user_lookup(Unit *u, uid_t uid, gid_t gid) { + int r; + + assert(u); + + /* This is invoked whenever one of the forked off processes let's us know the UID/GID its user name/group names + * resolved to. We keep track of which UID/GID is currently assigned in order to be able to destroy its IPC + * objects when no service references the UID/GID anymore. */ + + r = unit_ref_uid_gid(u, uid, gid); + if (r > 0) + bus_unit_send_change_signal(u); +} + +int unit_set_invocation_id(Unit *u, sd_id128_t id) { + int r; + + assert(u); + + /* Set the invocation ID for this unit. If we cannot, this will not roll back, but reset the whole thing. */ + + if (sd_id128_equal(u->invocation_id, id)) + return 0; + + if (!sd_id128_is_null(u->invocation_id)) + (void) hashmap_remove_value(u->manager->units_by_invocation_id, &u->invocation_id, u); + + if (sd_id128_is_null(id)) { + r = 0; + goto reset; + } + + r = hashmap_ensure_allocated(&u->manager->units_by_invocation_id, &id128_hash_ops); + if (r < 0) + goto reset; + + u->invocation_id = id; + sd_id128_to_string(id, u->invocation_id_string); + + r = hashmap_put(u->manager->units_by_invocation_id, &u->invocation_id, u); + if (r < 0) + goto reset; + + return 0; + +reset: + u->invocation_id = SD_ID128_NULL; + u->invocation_id_string[0] = 0; + return r; +} + +int unit_acquire_invocation_id(Unit *u) { + sd_id128_t id; + int r; + + assert(u); + + r = sd_id128_randomize(&id); + if (r < 0) + return log_unit_error_errno(u, r, "Failed to generate invocation ID for unit: %m"); + + r = unit_set_invocation_id(u, id); + if (r < 0) + return log_unit_error_errno(u, r, "Failed to set invocation ID for unit: %m"); + + return 0; +} diff --git a/src/core/unit.h b/src/core/unit.h index 1eabfa51e2..991543664b 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -29,7 +29,7 @@ typedef struct UnitRef UnitRef; typedef struct UnitStatusMessageFormats UnitStatusMessageFormats; #include "condition.h" -#include "failure-action.h" +#include "emergency-action.h" #include "install.h" #include "list.h" #include "unit-name.h" @@ -108,9 +108,13 @@ struct Unit { /* The slot used for watching NameOwnerChanged signals */ sd_bus_slot *match_bus_slot; + /* References to this unit from clients */ + sd_bus_track *bus_track; + char **deserialized_refs; + /* Job timeout and action to take */ usec_t job_timeout; - FailureAction job_timeout_action; + EmergencyAction job_timeout_action; char *job_timeout_reboot_arg; /* References to this */ @@ -174,18 +178,23 @@ struct Unit { /* Put a ratelimit on unit starting */ RateLimit start_limit; - FailureAction start_limit_action; + EmergencyAction start_limit_action; char *reboot_arg; /* Make sure we never enter endless loops with the check unneeded logic, or the BindsTo= logic */ RateLimit auto_stop_ratelimit; + /* Reference to a specific UID/GID */ + uid_t ref_uid; + gid_t ref_gid; + /* Cached unit file state and preset */ UnitFileState unit_file_state; int unit_file_preset; - /* Where the cpuacct.usage cgroup counter was at the time the unit was started */ - nsec_t cpuacct_usage_base; + /* Where the cpu.stat or cpuacct.usage was at the time the unit was started */ + nsec_t cpu_usage_base; + nsec_t cpu_usage_last; /* the most recently read value */ /* Counterparts in the cgroup filesystem */ char *cgroup_path; @@ -195,11 +204,13 @@ struct Unit { CGroupMask cgroup_members_mask; int cgroup_inotify_wd; - uint32_t cgroup_netclass_id; - /* How to start OnFailure units */ JobMode on_failure_job_mode; + /* The current invocation ID */ + sd_id128_t invocation_id; + char invocation_id_string[SD_ID128_STRING_MAX]; /* useful when logging */ + /* Garbage collect us we nobody wants or requires us anymore */ bool stop_when_unneeded; @@ -225,6 +236,9 @@ struct Unit { /* Is this a transient unit? */ bool transient; + /* Is this a unit that is always running and cannot be stopped? */ + bool perpetual; + bool in_load_queue:1; bool in_dbus_queue:1; bool in_cleanup_queue:1; @@ -233,8 +247,6 @@ struct Unit { bool sent_dbus_new_signal:1; - bool no_gc:1; - bool in_audit:1; bool cgroup_realized:1; @@ -245,6 +257,9 @@ struct Unit { /* Did we already invoke unit_coldplug() for this unit? */ bool coldplugged:1; + + /* For transient units: whether to add a bus track reference after creating the unit */ + bool bus_track_add:1; }; struct UnitStatusMessageFormats { @@ -291,6 +306,10 @@ struct UnitVTable { * that */ size_t exec_runtime_offset; + /* If greater than 0, the offset into the object where the pointer to DynamicCreds is found, if the unit type + * has that. */ + size_t dynamic_creds_offset; + /* The name of the configuration file section with the private settings of this unit */ const char *private_section; @@ -354,7 +373,7 @@ struct UnitVTable { /* When the unit is not running and no job for it queued we * shall release its runtime resources */ - void (*release_resources)(Unit *u); + void (*release_resources)(Unit *u, bool inactive); /* Invoked on every child that died */ void (*sigchld_event)(Unit *u, pid_t pid, int code, int status); @@ -369,8 +388,7 @@ struct UnitVTable { /* Called whenever a process of this unit sends us a message */ void (*notify_message)(Unit *u, pid_t pid, char **tags, FDSet *fds); - /* Called whenever a name this Unit registered for comes or - * goes away. */ + /* Called whenever a name this Unit registered for comes or goes away. */ void (*bus_name_owner_change)(Unit *u, const char *name, const char *old_owner, const char *new_owner); /* Called for each property that is being set */ @@ -463,6 +481,7 @@ DEFINE_CAST(SCOPE, Scope); Unit *unit_new(Manager *m, size_t size); void unit_free(Unit *u); +int unit_new_for_name(Manager *m, size_t size, const char *name, Unit **ret); int unit_add_name(Unit *u, const char *name); int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_reference); @@ -507,6 +526,7 @@ void unit_dump(Unit *u, FILE *f, const char *prefix); bool unit_can_reload(Unit *u) _pure_; bool unit_can_start(Unit *u) _pure_; +bool unit_can_stop(Unit *u) _pure_; bool unit_can_isolate(Unit *u) _pure_; int unit_start(Unit *u); @@ -533,6 +553,7 @@ bool unit_job_is_applicable(Unit *u, JobType j); int set_unit_path(const char *p); char *unit_dbus_path(Unit *u); +char *unit_dbus_path_invocation_id(Unit *u); int unit_load_related_unit(Unit *u, const char *type, Unit **_found); @@ -589,6 +610,7 @@ CGroupContext *unit_get_cgroup_context(Unit *u) _pure_; ExecRuntime *unit_get_exec_runtime(Unit *u) _pure_; int unit_setup_exec_runtime(Unit *u); +int unit_setup_dynamic_creds(Unit *u); int unit_write_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data); int unit_write_drop_in_format(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *format, ...) _printf_(4,5); @@ -618,12 +640,26 @@ int unit_fail_if_symlink(Unit *u, const char* where); int unit_start_limit_test(Unit *u); +void unit_unref_uid(Unit *u, bool destroy_now); +int unit_ref_uid(Unit *u, uid_t uid, bool clean_ipc); + +void unit_unref_gid(Unit *u, bool destroy_now); +int unit_ref_gid(Unit *u, gid_t gid, bool clean_ipc); + +int unit_ref_uid_gid(Unit *u, uid_t uid, gid_t gid); +void unit_unref_uid_gid(Unit *u, bool destroy_now); + +void unit_notify_user_lookup(Unit *u, uid_t uid, gid_t gid); + +int unit_set_invocation_id(Unit *u, sd_id128_t id); +int unit_acquire_invocation_id(Unit *u); + /* Macros which append UNIT= or USER_UNIT= to the message */ #define log_unit_full(unit, level, error, ...) \ ({ \ - Unit *_u = (unit); \ - _u ? log_object_internal(level, error, __FILE__, __LINE__, __func__, _u->manager->unit_log_field, _u->id, ##__VA_ARGS__) : \ + const Unit *_u = (unit); \ + _u ? log_object_internal(level, error, __FILE__, __LINE__, __func__, _u->manager->unit_log_field, _u->id, _u->manager->invocation_log_field, _u->invocation_id_string, ##__VA_ARGS__) : \ log_internal(level, error, __FILE__, __LINE__, __func__, ##__VA_ARGS__); \ }) diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c index dcc09fcc6d..a982c204be 100644 --- a/src/coredump/coredump.c +++ b/src/coredump/coredump.c @@ -28,9 +28,10 @@ #include <elfutils/libdwfl.h> #endif +#include "sd-daemon.h" #include "sd-journal.h" #include "sd-login.h" -#include "sd-daemon.h" +#include "sd-messages.h" #include "acl-util.h" #include "alloc-util.h" @@ -93,7 +94,6 @@ typedef enum CoredumpStorage { COREDUMP_STORAGE_NONE, COREDUMP_STORAGE_EXTERNAL, COREDUMP_STORAGE_JOURNAL, - COREDUMP_STORAGE_BOTH, _COREDUMP_STORAGE_MAX, _COREDUMP_STORAGE_INVALID = -1 } CoredumpStorage; @@ -102,7 +102,6 @@ static const char* const coredump_storage_table[_COREDUMP_STORAGE_MAX] = { [COREDUMP_STORAGE_NONE] = "none", [COREDUMP_STORAGE_EXTERNAL] = "external", [COREDUMP_STORAGE_JOURNAL] = "journal", - [COREDUMP_STORAGE_BOTH] = "both", }; DEFINE_PRIVATE_STRING_TABLE_LOOKUP(coredump_storage, CoredumpStorage); @@ -128,13 +127,17 @@ static int parse_config(void) { {} }; - return config_parse_many(PKGSYSCONFDIR "/coredump.conf", + return config_parse_many_nulstr(PKGSYSCONFDIR "/coredump.conf", CONF_PATHS_NULSTR("systemd/coredump.conf.d"), "Coredump\0", config_item_table_lookup, items, false, NULL); } +static inline uint64_t storage_size_max(void) { + return arg_storage == COREDUMP_STORAGE_EXTERNAL ? arg_external_size_max : arg_journal_size_max; +} + static int fix_acl(int fd, uid_t uid) { #ifdef HAVE_ACL @@ -247,7 +250,7 @@ static int maybe_remove_external_coredump(const char *filename, uint64_t size) { /* Returns 1 if might remove, 0 if will not remove, < 0 on error. */ - if (IN_SET(arg_storage, COREDUMP_STORAGE_EXTERNAL, COREDUMP_STORAGE_BOTH) && + if (arg_storage == COREDUMP_STORAGE_EXTERNAL && size <= arg_external_size_max) return 0; @@ -327,14 +330,17 @@ static int save_external_coredump( r = safe_atou64(context[CONTEXT_RLIMIT], &rlimit); if (r < 0) return log_error_errno(r, "Failed to parse resource limit: %s", context[CONTEXT_RLIMIT]); - if (rlimit <= 0) { - /* Is coredumping disabled? Then don't bother saving/processing the coredump */ - log_info("Core Dumping has been disabled for process %s (%s).", context[CONTEXT_PID], context[CONTEXT_COMM]); + if (rlimit < page_size()) { + /* Is coredumping disabled? Then don't bother saving/processing the coredump. + * Anything below PAGE_SIZE cannot give a readable coredump (the kernel uses + * ELF_EXEC_PAGESIZE which is not easily accessible, but is usually the same as PAGE_SIZE. */ + log_info("Resource limits disable core dumping for process %s (%s).", + context[CONTEXT_PID], context[CONTEXT_COMM]); return -EBADSLT; } /* Never store more than the process configured, or than we actually shall keep or process */ - max_size = MIN(rlimit, MAX(arg_process_size_max, arg_external_size_max)); + max_size = MIN(rlimit, MAX(arg_process_size_max, storage_size_max())); r = make_filename(context, &fn); if (r < 0) @@ -347,19 +353,18 @@ static int save_external_coredump( return log_error_errno(fd, "Failed to create temporary file for coredump %s: %m", fn); r = copy_bytes(input_fd, fd, max_size, false); - if (r == -EFBIG) { - log_error("Coredump of %s (%s) is larger than configured processing limit, refusing.", context[CONTEXT_PID], context[CONTEXT_COMM]); - goto fail; - } else if (IN_SET(r, -EDQUOT, -ENOSPC)) { - log_error("Not enough disk space for coredump of %s (%s), refusing.", context[CONTEXT_PID], context[CONTEXT_COMM]); - goto fail; - } else if (r < 0) { - log_error_errno(r, "Failed to dump coredump to file: %m"); + if (r < 0) { + log_error_errno(r, "Cannot store coredump of %s (%s): %m", context[CONTEXT_PID], context[CONTEXT_COMM]); goto fail; - } + } else if (r == 1) + log_struct(LOG_INFO, + LOG_MESSAGE("Core file was truncated to %zu bytes.", max_size), + "SIZE_LIMIT=%zu", max_size, + LOG_MESSAGE_ID(SD_MESSAGE_TRUNCATED_CORE), + NULL); if (fstat(fd, &st) < 0) { - log_error_errno(errno, "Failed to fstat coredump %s: %m", coredump_tmpfile_name(tmp)); + log_error_errno(errno, "Failed to fstat core file %s: %m", coredump_tmpfile_name(tmp)); goto fail; } @@ -370,8 +375,7 @@ static int save_external_coredump( #if defined(HAVE_XZ) || defined(HAVE_LZ4) /* If we will remove the coredump anyway, do not compress. */ - if (maybe_remove_external_coredump(NULL, st.st_size) == 0 - && arg_compress) { + if (arg_compress && !maybe_remove_external_coredump(NULL, st.st_size)) { _cleanup_free_ char *fn_compressed = NULL, *tmp_compressed = NULL; _cleanup_close_ int fd_compressed = -1; @@ -558,6 +562,89 @@ static int compose_open_fds(pid_t pid, char **open_fds) { return 0; } +static int get_process_ns(pid_t pid, const char *namespace, ino_t *ns) { + const char *p; + struct stat stbuf; + _cleanup_close_ int proc_ns_dir_fd; + + p = procfs_file_alloca(pid, "ns"); + + proc_ns_dir_fd = open(p, O_DIRECTORY | O_CLOEXEC | O_RDONLY); + if (proc_ns_dir_fd < 0) + return -errno; + + if (fstatat(proc_ns_dir_fd, namespace, &stbuf, /* flags */0) < 0) + return -errno; + + *ns = stbuf.st_ino; + return 0; +} + +static int get_mount_namespace_leader(pid_t pid, pid_t *container_pid) { + pid_t cpid = pid, ppid = 0; + ino_t proc_mntns; + int r = 0; + + r = get_process_ns(pid, "mnt", &proc_mntns); + if (r < 0) + return r; + + for (;;) { + ino_t parent_mntns; + + r = get_process_ppid(cpid, &ppid); + if (r < 0) + return r; + + r = get_process_ns(ppid, "mnt", &parent_mntns); + if (r < 0) + return r; + + if (proc_mntns != parent_mntns) + break; + + if (ppid == 1) + return -ENOENT; + + cpid = ppid; + } + + *container_pid = ppid; + return 0; +} + +/* Returns 1 if the parent was found. + * Returns 0 if there is not a process we can call the pid's + * container parent (the pid's process isn't 'containerized'). + * Returns a negative number on errors. + */ +static int get_process_container_parent_cmdline(pid_t pid, char** cmdline) { + int r = 0; + pid_t container_pid; + const char *proc_root_path; + struct stat root_stat, proc_root_stat; + + /* To compare inodes of / and /proc/[pid]/root */ + if (stat("/", &root_stat) < 0) + return -errno; + + proc_root_path = procfs_file_alloca(pid, "root"); + if (stat(proc_root_path, &proc_root_stat) < 0) + return -errno; + + /* The process uses system root. */ + if (proc_root_stat.st_ino == root_stat.st_ino) { + *cmdline = NULL; + return 0; + } + + r = get_mount_namespace_leader(pid, &container_pid); + if (r < 0) + return r; + + return get_process_cmdline(container_pid, 0, false, cmdline); +} + static int change_uid_gid(const char *context[]) { uid_t uid; gid_t gid; @@ -593,7 +680,7 @@ static int submit_coredump( _cleanup_close_ int coredump_fd = -1, coredump_node_fd = -1; _cleanup_free_ char *core_message = NULL, *filename = NULL, *coredump_data = NULL; - uint64_t coredump_size; + uint64_t coredump_size = UINT64_MAX; int r; assert(context); @@ -620,7 +707,9 @@ static int submit_coredump( coredump_filename = strjoina("COREDUMP_FILENAME=", filename); IOVEC_SET_STRING(iovec[n_iovec++], coredump_filename); - } + } else if (arg_storage == COREDUMP_STORAGE_EXTERNAL) + log_info("The core will not be stored: size %zu is greater than %zu (the configured maximum)", + coredump_size, arg_external_size_max); /* Vacuum again, but exclude the coredump we just created */ (void) coredump_vacuum(coredump_node_fd >= 0 ? coredump_node_fd : coredump_fd, arg_keep_free, arg_max_use); @@ -645,7 +734,9 @@ static int submit_coredump( log_warning("Failed to generate stack trace: %s", dwfl_errmsg(dwfl_errno())); else log_warning_errno(r, "Failed to generate stack trace: %m"); - } + } else + log_debug("Not generating stack trace: core size %zu is greater than %zu (the configured maximum)", + coredump_size, arg_process_size_max); if (!core_message) #endif @@ -655,18 +746,22 @@ log: IOVEC_SET_STRING(iovec[n_iovec++], core_message); /* Optionally store the entire coredump in the journal */ - if (IN_SET(arg_storage, COREDUMP_STORAGE_JOURNAL, COREDUMP_STORAGE_BOTH) && - coredump_size <= arg_journal_size_max) { - size_t sz = 0; - - /* Store the coredump itself in the journal */ - - r = allocate_journal_field(coredump_fd, (size_t) coredump_size, &coredump_data, &sz); - if (r >= 0) { - iovec[n_iovec].iov_base = coredump_data; - iovec[n_iovec].iov_len = sz; - n_iovec++; - } + if (arg_storage == COREDUMP_STORAGE_JOURNAL) { + if (coredump_size <= arg_journal_size_max) { + size_t sz = 0; + + /* Store the coredump itself in the journal */ + + r = allocate_journal_field(coredump_fd, (size_t) coredump_size, &coredump_data, &sz); + if (r >= 0) { + iovec[n_iovec].iov_base = coredump_data; + iovec[n_iovec].iov_len = sz; + n_iovec++; + } else + log_warning_errno(r, "Failed to attach the core to the journal entry: %m"); + } else + log_info("The core will not be stored: size %zu is greater than %zu (the configured maximum)", + coredump_size, arg_journal_size_max); } assert(n_iovec <= n_iovec_allocated); @@ -933,11 +1028,13 @@ static int process_kernel(int argc, char* argv[]) { /* The larger ones we allocate on the heap */ _cleanup_free_ char *core_owner_uid = NULL, *core_open_fds = NULL, *core_proc_status = NULL, - *core_proc_maps = NULL, *core_proc_limits = NULL, *core_proc_cgroup = NULL, *core_environ = NULL; + *core_proc_maps = NULL, *core_proc_limits = NULL, *core_proc_cgroup = NULL, *core_environ = NULL, + *core_proc_mountinfo = NULL, *core_container_cmdline = NULL; _cleanup_free_ char *exe = NULL, *comm = NULL; const char *context[_CONTEXT_MAX]; - struct iovec iovec[25]; + bool proc_self_root_is_slash; + struct iovec iovec[27]; size_t n_iovec = 0; uid_t owner_uid; const char *p; @@ -1110,6 +1207,15 @@ static int process_kernel(int argc, char* argv[]) { IOVEC_SET_STRING(iovec[n_iovec++], core_proc_cgroup); } + p = procfs_file_alloca(pid, "mountinfo"); + if (read_full_file(p, &t, NULL) >=0) { + core_proc_mountinfo = strappend("COREDUMP_PROC_MOUNTINFO=", t); + free(t); + + if (core_proc_mountinfo) + IOVEC_SET_STRING(iovec[n_iovec++], core_proc_mountinfo); + } + if (get_process_cwd(pid, &t) >= 0) { core_cwd = strjoina("COREDUMP_CWD=", t); free(t); @@ -1119,9 +1225,20 @@ static int process_kernel(int argc, char* argv[]) { if (get_process_root(pid, &t) >= 0) { core_root = strjoina("COREDUMP_ROOT=", t); - free(t); IOVEC_SET_STRING(iovec[n_iovec++], core_root); + + /* If the process' root is "/", then there is a chance it has + * mounted own root and hence being containerized. */ + proc_self_root_is_slash = strcmp(t, "/") == 0; + free(t); + if (proc_self_root_is_slash && get_process_container_parent_cmdline(pid, &t) > 0) { + core_container_cmdline = strappend("COREDUMP_CONTAINER_CMDLINE=", t); + free(t); + + if (core_container_cmdline) + IOVEC_SET_STRING(iovec[n_iovec++], core_container_cmdline); + } } if (get_process_environ(pid, &t) >= 0) { diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c index 27b1e0fb3f..0e5351e621 100644 --- a/src/coredump/coredumpctl.c +++ b/src/coredump/coredumpctl.c @@ -30,6 +30,7 @@ #include "compress.h" #include "fd-util.h" #include "fileio.h" +#include "fs-util.h" #include "journal-internal.h" #include "log.h" #include "macro.h" @@ -279,11 +280,10 @@ static int retrieve(const void *data, free(*var); *var = v; - return 0; + return 1; } -static void print_field(FILE* file, sd_journal *j) { - _cleanup_free_ char *value = NULL; +static int print_field(FILE* file, sd_journal *j) { const void *d; size_t l; @@ -292,37 +292,59 @@ static void print_field(FILE* file, sd_journal *j) { assert(arg_field); - SD_JOURNAL_FOREACH_DATA(j, d, l) - retrieve(d, l, arg_field, &value); + /* A (user-specified) field may appear more than once for a given entry. + * We will print all of the occurences. + * This is different below for fields that systemd-coredump uses, + * because they cannot meaningfully appear more than once. + */ + SD_JOURNAL_FOREACH_DATA(j, d, l) { + _cleanup_free_ char *value = NULL; + int r; + + r = retrieve(d, l, arg_field, &value); + if (r < 0) + return r; + if (r > 0) + fprintf(file, "%s\n", value); + } - if (value) - fprintf(file, "%s\n", value); + return 0; } +#define RETRIEVE(d, l, name, arg) \ + { \ + int _r = retrieve(d, l, name, &arg); \ + if (_r < 0) \ + return _r; \ + if (_r > 0) \ + continue; \ + } + static int print_list(FILE* file, sd_journal *j, int had_legend) { _cleanup_free_ char *pid = NULL, *uid = NULL, *gid = NULL, *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL, - *filename = NULL; + *filename = NULL, *coredump = NULL; const void *d; size_t l; usec_t t; char buf[FORMAT_TIMESTAMP_MAX]; int r; - bool present; + const char *present; assert(file); assert(j); SD_JOURNAL_FOREACH_DATA(j, d, l) { - retrieve(d, l, "COREDUMP_PID", &pid); - retrieve(d, l, "COREDUMP_UID", &uid); - retrieve(d, l, "COREDUMP_GID", &gid); - retrieve(d, l, "COREDUMP_SIGNAL", &sgnl); - retrieve(d, l, "COREDUMP_EXE", &exe); - retrieve(d, l, "COREDUMP_COMM", &comm); - retrieve(d, l, "COREDUMP_CMDLINE", &cmdline); - retrieve(d, l, "COREDUMP_FILENAME", &filename); + RETRIEVE(d, l, "COREDUMP_PID", pid); + RETRIEVE(d, l, "COREDUMP_UID", uid); + RETRIEVE(d, l, "COREDUMP_GID", gid); + RETRIEVE(d, l, "COREDUMP_SIGNAL", sgnl); + RETRIEVE(d, l, "COREDUMP_EXE", exe); + RETRIEVE(d, l, "COREDUMP_COMM", comm); + RETRIEVE(d, l, "COREDUMP_CMDLINE", cmdline); + RETRIEVE(d, l, "COREDUMP_FILENAME", filename); + RETRIEVE(d, l, "COREDUMP", coredump); } if (!pid && !uid && !gid && !sgnl && !exe && !comm && !cmdline && !filename) { @@ -335,7 +357,6 @@ static int print_list(FILE* file, sd_journal *j, int had_legend) { return log_error_errno(r, "Failed to get realtime timestamp: %m"); format_timestamp(buf, sizeof(buf), t); - present = filename && access(filename, F_OK) == 0; if (!had_legend && !arg_no_legend) fprintf(file, "%-*s %*s %*s %*s %*s %*s %s\n", @@ -344,16 +365,28 @@ static int print_list(FILE* file, sd_journal *j, int had_legend) { 5, "UID", 5, "GID", 3, "SIG", - 1, "PRESENT", + 8, "COREFILE", "EXE"); - fprintf(file, "%-*s %*s %*s %*s %*s %*s %s\n", + if (filename) + if (access(filename, R_OK) == 0) + present = "present"; + else if (errno == ENOENT) + present = "missing"; + else + present = "error"; + else if (coredump) + present = "journal"; + else + present = "none"; + + fprintf(file, "%-*s %*s %*s %*s %*s %-*s %s\n", FORMAT_TIMESTAMP_WIDTH, buf, 6, strna(pid), 5, strna(uid), 5, strna(gid), 3, strna(sgnl), - 1, present ? "*" : "", + 8, present, strna(exe ?: (comm ?: cmdline))); return 0; @@ -366,7 +399,8 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) { *unit = NULL, *user_unit = NULL, *session = NULL, *boot_id = NULL, *machine_id = NULL, *hostname = NULL, *slice = NULL, *cgroup = NULL, *owner_uid = NULL, - *message = NULL, *timestamp = NULL, *filename = NULL; + *message = NULL, *timestamp = NULL, *filename = NULL, + *coredump = NULL; const void *d; size_t l; int r; @@ -375,25 +409,26 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) { assert(j); SD_JOURNAL_FOREACH_DATA(j, d, l) { - retrieve(d, l, "COREDUMP_PID", &pid); - retrieve(d, l, "COREDUMP_UID", &uid); - retrieve(d, l, "COREDUMP_GID", &gid); - retrieve(d, l, "COREDUMP_SIGNAL", &sgnl); - retrieve(d, l, "COREDUMP_EXE", &exe); - retrieve(d, l, "COREDUMP_COMM", &comm); - retrieve(d, l, "COREDUMP_CMDLINE", &cmdline); - retrieve(d, l, "COREDUMP_UNIT", &unit); - retrieve(d, l, "COREDUMP_USER_UNIT", &user_unit); - retrieve(d, l, "COREDUMP_SESSION", &session); - retrieve(d, l, "COREDUMP_OWNER_UID", &owner_uid); - retrieve(d, l, "COREDUMP_SLICE", &slice); - retrieve(d, l, "COREDUMP_CGROUP", &cgroup); - retrieve(d, l, "COREDUMP_TIMESTAMP", ×tamp); - retrieve(d, l, "COREDUMP_FILENAME", &filename); - retrieve(d, l, "_BOOT_ID", &boot_id); - retrieve(d, l, "_MACHINE_ID", &machine_id); - retrieve(d, l, "_HOSTNAME", &hostname); - retrieve(d, l, "MESSAGE", &message); + RETRIEVE(d, l, "COREDUMP_PID", pid); + RETRIEVE(d, l, "COREDUMP_UID", uid); + RETRIEVE(d, l, "COREDUMP_GID", gid); + RETRIEVE(d, l, "COREDUMP_SIGNAL", sgnl); + RETRIEVE(d, l, "COREDUMP_EXE", exe); + RETRIEVE(d, l, "COREDUMP_COMM", comm); + RETRIEVE(d, l, "COREDUMP_CMDLINE", cmdline); + RETRIEVE(d, l, "COREDUMP_UNIT", unit); + RETRIEVE(d, l, "COREDUMP_USER_UNIT", user_unit); + RETRIEVE(d, l, "COREDUMP_SESSION", session); + RETRIEVE(d, l, "COREDUMP_OWNER_UID", owner_uid); + RETRIEVE(d, l, "COREDUMP_SLICE", slice); + RETRIEVE(d, l, "COREDUMP_CGROUP", cgroup); + RETRIEVE(d, l, "COREDUMP_TIMESTAMP", timestamp); + RETRIEVE(d, l, "COREDUMP_FILENAME", filename); + RETRIEVE(d, l, "COREDUMP", coredump); + RETRIEVE(d, l, "_BOOT_ID", boot_id); + RETRIEVE(d, l, "_MACHINE_ID", machine_id); + RETRIEVE(d, l, "_HOSTNAME", hostname); + RETRIEVE(d, l, "MESSAGE", message); } if (need_space) @@ -476,7 +511,7 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) { if (unit) fprintf(file, " Unit: %s\n", unit); if (user_unit) - fprintf(file, " User Unit: %s\n", unit); + fprintf(file, " User Unit: %s\n", user_unit); if (slice) fprintf(file, " Slice: %s\n", slice); if (session) @@ -504,8 +539,13 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) { if (hostname) fprintf(file, " Hostname: %s\n", hostname); - if (filename && access(filename, F_OK) == 0) - fprintf(file, " Coredump: %s\n", filename); + if (filename) + fprintf(file, " Storage: %s%s\n", filename, + access(filename, R_OK) < 0 ? " (inaccessible)" : ""); + else if (coredump) + fprintf(file, " Storage: journal\n"); + else + fprintf(file, " Storage: none\n"); if (message) { _cleanup_free_ char *m = NULL; @@ -533,15 +573,15 @@ static int focus(sd_journal *j) { return r; } -static void print_entry(sd_journal *j, unsigned n_found) { +static int print_entry(sd_journal *j, unsigned n_found) { assert(j); if (arg_action == ACTION_INFO) - print_info(stdout, j, n_found); + return print_info(stdout, j, n_found); else if (arg_field) - print_field(stdout, j); + return print_field(stdout, j); else - print_list(stdout, j, n_found); + return print_list(stdout, j, n_found); } static int dump_list(sd_journal *j) { @@ -560,10 +600,13 @@ static int dump_list(sd_journal *j) { if (r < 0) return r; - print_entry(j, 0); + return print_entry(j, 0); } else { - SD_JOURNAL_FOREACH(j) - print_entry(j, n_found++); + SD_JOURNAL_FOREACH(j) { + r = print_entry(j, n_found++); + if (r < 0) + return r; + } if (!arg_field && n_found <= 0) { log_notice("No coredumps found."); @@ -574,116 +617,142 @@ static int dump_list(sd_journal *j) { return 0; } -static int save_core(sd_journal *j, int fd, char **path, bool *unlink_temp) { +static int save_core(sd_journal *j, FILE *file, char **path, bool *unlink_temp) { const char *data; _cleanup_free_ char *filename = NULL; size_t len; - int r; + int r, fd; + _cleanup_close_ int fdt = -1; + char *temp = NULL; - assert((fd >= 0) != !!path); - assert(!!path == !!unlink_temp); + assert(!(file && path)); /* At most one can be specified */ + assert(!!path == !!unlink_temp); /* Those must be specified together */ - /* Prefer uncompressed file to journal (probably cached) to - * compressed file (probably uncached). */ + /* Look for a coredump on disk first. */ r = sd_journal_get_data(j, "COREDUMP_FILENAME", (const void**) &data, &len); - if (r < 0 && r != -ENOENT) - log_warning_errno(r, "Failed to retrieve COREDUMP_FILENAME: %m"); - else if (r == 0) + if (r == 0) retrieve(data, len, "COREDUMP_FILENAME", &filename); + else { + if (r != -ENOENT) + return log_error_errno(r, "Failed to retrieve COREDUMP_FILENAME field: %m"); + /* Check that we can have a COREDUMP field. We still haven't set a high + * data threshold, so we'll get a few kilobytes at most. + */ - if (filename && access(filename, R_OK) < 0) { - log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, - "File %s is not readable: %m", filename); - filename = mfree(filename); + r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len); + if (r == -ENOENT) + return log_error_errno(r, "Coredump entry has no core attached (neither internally in the journal nor externally on disk)."); + if (r < 0) + return log_error_errno(r, "Failed to retrieve COREDUMP field: %m"); } - if (filename && !endswith(filename, ".xz") && !endswith(filename, ".lz4")) { - if (path) { + if (filename) { + if (access(filename, R_OK) < 0) + return log_error_errno(errno, "File \"%s\" is not readable: %m", filename); + + if (path && !endswith(filename, ".xz") && !endswith(filename, ".lz4")) { *path = filename; filename = NULL; + + return 0; } + } - return 0; - } else { - _cleanup_close_ int fdt = -1; - char *temp = NULL; + if (path) { + const char *vt; - if (fd < 0) { - temp = strdup("/var/tmp/coredump-XXXXXX"); - if (!temp) - return log_oom(); + /* Create a temporary file to write the uncompressed core to. */ - fdt = mkostemp_safe(temp, O_WRONLY|O_CLOEXEC); - if (fdt < 0) - return log_error_errno(fdt, "Failed to create temporary file: %m"); - log_debug("Created temporary file %s", temp); + r = var_tmp_dir(&vt); + if (r < 0) + return log_error_errno(r, "Failed to acquire temporary directory path: %m"); + + temp = strjoin(vt, "/coredump-XXXXXX", NULL); + if (!temp) + return log_oom(); - fd = fdt; + fdt = mkostemp_safe(temp); + if (fdt < 0) + return log_error_errno(fdt, "Failed to create temporary file: %m"); + log_debug("Created temporary file %s", temp); + + fd = fdt; + } else { + /* If neither path or file are specified, we will write to stdout. Let's now check + * if stdout is connected to a tty. We checked that the file exists, or that the + * core might be stored in the journal. In this second case, if we found the entry, + * in all likelyhood we will be able to access the COREDUMP= field. In either case, + * we stop before doing any "real" work, i.e. before starting decompression or + * reading from the file or creating temporary files. + */ + if (!file) { + if (on_tty()) + return log_error_errno(ENOTTY, "Refusing to dump core to tty" + " (use shell redirection or specify --output)."); + file = stdout; } - r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len); - if (r == 0) { - ssize_t sz; - - assert(len >= 9); - data += 9; - len -= 9; - - sz = write(fdt, data, len); - if (sz < 0) { - r = log_error_errno(errno, - "Failed to write temporary file: %m"); - goto error; - } - if (sz != (ssize_t) len) { - log_error("Short write to temporary file."); - r = -EIO; - goto error; - } - } else if (filename) { + fd = fileno(file); + } + + if (filename) { #if defined(HAVE_XZ) || defined(HAVE_LZ4) - _cleanup_close_ int fdf; - - fdf = open(filename, O_RDONLY | O_CLOEXEC); - if (fdf < 0) { - r = log_error_errno(errno, - "Failed to open %s: %m", - filename); - goto error; - } + _cleanup_close_ int fdf; - r = decompress_stream(filename, fdf, fd, -1); - if (r < 0) { - log_error_errno(r, "Failed to decompress %s: %m", filename); - goto error; - } -#else - log_error("Cannot decompress file. Compiled without compression support."); - r = -EOPNOTSUPP; + fdf = open(filename, O_RDONLY | O_CLOEXEC); + if (fdf < 0) { + r = log_error_errno(errno, "Failed to open %s: %m", filename); goto error; -#endif - } else { - if (r == -ENOENT) - log_error("Cannot retrieve coredump from journal or disk."); - else - log_error_errno(r, "Failed to retrieve COREDUMP field: %m"); + } + + r = decompress_stream(filename, fdf, fd, -1); + if (r < 0) { + log_error_errno(r, "Failed to decompress %s: %m", filename); goto error; } +#else + log_error("Cannot decompress file. Compiled without compression support."); + r = -EOPNOTSUPP; + goto error; +#endif + } else { + ssize_t sz; + + /* We want full data, nothing truncated. */ + sd_journal_set_data_threshold(j, 0); + + r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len); + if (r < 0) + return log_error_errno(r, "Failed to retrieve COREDUMP field: %m"); - if (temp) { - *path = temp; - *unlink_temp = true; + assert(len >= 9); + data += 9; + len -= 9; + + sz = write(fd, data, len); + if (sz < 0) { + r = log_error_errno(errno, "Failed to write output: %m"); + goto error; + } + if (sz != (ssize_t) len) { + log_error("Short write to output."); + r = -EIO; + goto error; } + } - return 0; + if (temp) { + *path = temp; + *unlink_temp = true; + } + return 0; error: - if (temp) { - unlink(temp); - log_debug("Removed temporary file %s", temp); - } - return r; + if (temp) { + unlink(temp); + log_debug("Removed temporary file %s", temp); } + return r; } static int dump_core(sd_journal* j) { @@ -697,17 +766,12 @@ static int dump_core(sd_journal* j) { print_info(arg_output ? stdout : stderr, j, false); - if (on_tty() && !arg_output) { - log_error("Refusing to dump core to tty."); - return -ENOTTY; - } - - r = save_core(j, arg_output ? fileno(arg_output) : STDOUT_FILENO, NULL, NULL); + r = save_core(j, arg_output, NULL, NULL); if (r < 0) - return log_error_errno(r, "Coredump retrieval failed: %m"); + return r; r = sd_journal_previous(j); - if (r >= 0) + if (r > 0) log_warning("More than one entry matches, ignoring rest."); return 0; @@ -753,9 +817,9 @@ static int run_gdb(sd_journal *j) { return -ENOENT; } - r = save_core(j, -1, &path, &unlink_path); + r = save_core(j, NULL, &path, &unlink_path); if (r < 0) - return log_error_errno(r, "Failed to retrieve core: %m"); + return r; pid = fork(); if (pid < 0) { @@ -829,9 +893,6 @@ int main(int argc, char *argv[]) { } } - /* We want full data, nothing truncated. */ - sd_journal_set_data_threshold(j, 0); - SET_FOREACH(match, matches, it) { r = sd_journal_add_match(j, match, strlen(match)); if (r != 0) { diff --git a/src/cryptsetup/cryptsetup-generator.c b/src/cryptsetup/cryptsetup-generator.c index 8ac5ab730a..e2dc4327fe 100644 --- a/src/cryptsetup/cryptsetup-generator.c +++ b/src/cryptsetup/cryptsetup-generator.c @@ -264,28 +264,25 @@ static crypto_device *get_crypto_device(const char *uuid) { d->keyfile = d->options = d->name = NULL; d->uuid = strdup(uuid); - if (!d->uuid) { - free(d); - return NULL; - } + if (!d->uuid) + return mfree(d); r = hashmap_put(arg_disks, d->uuid, d); if (r < 0) { free(d->uuid); - free(d); - return NULL; + return mfree(d); } } return d; } -static int parse_proc_cmdline_item(const char *key, const char *value) { +static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { int r; crypto_device *d; _cleanup_free_ char *uuid = NULL, *uuid_value = NULL; - if (STR_IN_SET(key, "luks", "rd.luks") && value) { + if (streq(key, "luks") && value) { r = parse_boolean(value); if (r < 0) @@ -293,7 +290,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value) { else arg_enabled = r; - } else if (STR_IN_SET(key, "luks.crypttab", "rd.luks.crypttab") && value) { + } else if (streq(key, "luks.crypttab") && value) { r = parse_boolean(value); if (r < 0) @@ -301,7 +298,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value) { else arg_read_crypttab = r; - } else if (STR_IN_SET(key, "luks.uuid", "rd.luks.uuid") && value) { + } else if (streq(key, "luks.uuid") && value) { d = get_crypto_device(startswith(value, "luks-") ? value+5 : value); if (!d) @@ -309,7 +306,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value) { d->create = arg_whitelist = true; - } else if (STR_IN_SET(key, "luks.options", "rd.luks.options") && value) { + } else if (streq(key, "luks.options") && value) { r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value); if (r == 2) { @@ -323,7 +320,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value) { } else if (free_and_strdup(&arg_default_options, value) < 0) return log_oom(); - } else if (STR_IN_SET(key, "luks.key", "rd.luks.key") && value) { + } else if (streq(key, "luks.key") && value) { r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value); if (r == 2) { @@ -337,7 +334,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value) { } else if (free_and_strdup(&arg_default_keyfile, value) < 0) return log_oom(); - } else if (STR_IN_SET(key, "luks.name", "rd.luks.name") && value) { + } else if (streq(key, "luks.name") && value) { r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value); if (r == 2) { @@ -481,7 +478,7 @@ int main(int argc, char *argv[]) { if (!arg_disks) goto cleanup; - r = parse_proc_cmdline(parse_proc_cmdline_item); + r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, true); if (r < 0) { log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); r = EXIT_FAILURE; diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index 9927621ea0..ff5a3f36fb 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -52,6 +52,7 @@ static bool arg_verify = false; static bool arg_discards = false; static bool arg_tcrypt_hidden = false; static bool arg_tcrypt_system = false; +static bool arg_tcrypt_veracrypt = false; static char **arg_tcrypt_keyfiles = NULL; static uint64_t arg_offset = 0; static uint64_t arg_skip = 0; @@ -179,6 +180,14 @@ static int parse_one_option(const char *option) { } else if (streq(option, "tcrypt-system")) { arg_type = CRYPT_TCRYPT; arg_tcrypt_system = true; + } else if (streq(option, "tcrypt-veracrypt")) { +#ifdef CRYPT_TCRYPT_VERA_MODES + arg_type = CRYPT_TCRYPT; + arg_tcrypt_veracrypt = true; +#else + log_error("This version of cryptsetup does not support tcrypt-veracrypt; refusing."); + return -EINVAL; +#endif } else if (STR_IN_SET(option, "plain", "swap", "tmp")) arg_type = CRYPT_PLAIN; else if (startswith(option, "timeout=")) { @@ -441,6 +450,11 @@ static int attach_tcrypt( if (arg_tcrypt_system) params.flags |= CRYPT_TCRYPT_SYSTEM_HEADER; +#ifdef CRYPT_TCRYPT_VERA_MODES + if (arg_tcrypt_veracrypt) + params.flags |= CRYPT_TCRYPT_VERA_MODES; +#endif + if (key_file) { r = read_one_line_file(key_file, &passphrase); if (r < 0) { diff --git a/src/debug-generator/debug-generator.c b/src/debug-generator/debug-generator.c index 7e80af78e7..7f11ec724d 100644 --- a/src/debug-generator/debug-generator.c +++ b/src/debug-generator/debug-generator.c @@ -33,7 +33,7 @@ static char **arg_mask = NULL; static char **arg_wants = NULL; static bool arg_debug_shell = false; -static int parse_proc_cmdline_item(const char *key, const char *value) { +static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { int r; assert(key); @@ -178,7 +178,7 @@ int main(int argc, char *argv[]) { goto finish; } - r = parse_proc_cmdline(parse_proc_cmdline_item); + r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, false); if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); diff --git a/src/detect-virt/detect-virt.c b/src/detect-virt/detect-virt.c index 5d51589a31..4b8956f0ad 100644 --- a/src/detect-virt/detect-virt.c +++ b/src/detect-virt/detect-virt.c @@ -31,6 +31,7 @@ static enum { ONLY_VM, ONLY_CONTAINER, ONLY_CHROOT, + ONLY_PRIVATE_USERS, } arg_mode = ANY_VIRTUALIZATION; static void help(void) { @@ -41,6 +42,7 @@ static void help(void) { " -c --container Only detect whether we are run in a container\n" " -v --vm Only detect whether we are run in a VM\n" " -r --chroot Detect whether we are run in a chroot() environment\n" + " --private-users Only detect whether we are running in a user namespace\n" " -q --quiet Don't output anything, just set return value\n" , program_invocation_short_name); } @@ -48,16 +50,18 @@ static void help(void) { static int parse_argv(int argc, char *argv[]) { enum { - ARG_VERSION = 0x100 + ARG_VERSION = 0x100, + ARG_PRIVATE_USERS, }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "container", no_argument, NULL, 'c' }, - { "vm", no_argument, NULL, 'v' }, - { "chroot", no_argument, NULL, 'r' }, - { "quiet", no_argument, NULL, 'q' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "container", no_argument, NULL, 'c' }, + { "vm", no_argument, NULL, 'v' }, + { "chroot", no_argument, NULL, 'r' }, + { "private-users", no_argument, NULL, ARG_PRIVATE_USERS }, + { "quiet", no_argument, NULL, 'q' }, {} }; @@ -85,6 +89,10 @@ static int parse_argv(int argc, char *argv[]) { arg_mode = ONLY_CONTAINER; break; + case ARG_PRIVATE_USERS: + arg_mode = ONLY_PRIVATE_USERS; + break; + case 'v': arg_mode = ONLY_VM; break; @@ -151,6 +159,15 @@ int main(int argc, char *argv[]) { return r ? EXIT_SUCCESS : EXIT_FAILURE; + case ONLY_PRIVATE_USERS: + r = running_in_userns(); + if (r < 0) { + log_error_errno(r, "Failed to check for user namespace: %m"); + return EXIT_FAILURE; + } + + return r ? EXIT_SUCCESS : EXIT_FAILURE; + case ANY_VIRTUALIZATION: default: r = detect_virtualization(); diff --git a/src/fsck/fsck.c b/src/fsck/fsck.c index d32e1d923e..be25c6a2b2 100644 --- a/src/fsck/fsck.c +++ b/src/fsck/fsck.c @@ -94,7 +94,7 @@ static void start_target(const char *target, const char *mode) { log_error("Failed to start unit: %s", bus_error_message(&error, r)); } -static int parse_proc_cmdline_item(const char *key, const char *value) { +static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { int r; assert(key); @@ -293,7 +293,7 @@ int main(int argc, char *argv[]) { umask(0022); - r = parse_proc_cmdline(parse_proc_cmdline_item); + r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, true); if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c index 33af553d0d..e77bd71a52 100644 --- a/src/fstab-generator/fstab-generator.c +++ b/src/fstab-generator/fstab-generator.c @@ -590,7 +590,7 @@ static int add_sysroot_usr_mount(void) { "/proc/cmdline"); } -static int parse_proc_cmdline_item(const char *key, const char *value) { +static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { int r; /* root=, usr=, usrfstype= and roofstype= may occur more than once, the last @@ -674,7 +674,7 @@ int main(int argc, char *argv[]) { umask(0022); - r = parse_proc_cmdline(parse_proc_cmdline_item); + r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, false); if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index 39355de953..a098b27a8e 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -450,99 +450,101 @@ static int add_automount( } static int add_boot(const char *what) { - _cleanup_blkid_free_probe_ blkid_probe b = NULL; - const char *fstype = NULL, *uuid = NULL; - sd_id128_t id, type_id; + const char *esp; int r; assert(what); - if (!is_efi_boot()) { - log_debug("Not an EFI boot, ignoring /boot."); - return 0; - } - if (in_initrd()) { - log_debug("In initrd, ignoring /boot."); + log_debug("In initrd, ignoring the ESP."); return 0; } if (detect_container() > 0) { - log_debug("In a container, ignoring /boot."); + log_debug("In a container, ignoring the ESP."); return 0; } + /* If /efi exists we'll use that. Otherwise we'll use /boot, as that's usually the better choice */ + esp = access("/efi/", F_OK) >= 0 ? "/efi" : "/boot"; + /* We create an .automount which is not overridden by the .mount from the fstab generator. */ - if (fstab_is_mount_point("/boot")) { - log_debug("/boot specified in fstab, ignoring."); + if (fstab_is_mount_point(esp)) { + log_debug("%s specified in fstab, ignoring.", esp); return 0; } - if (path_is_busy("/boot")) { - log_debug("/boot already populated, ignoring."); + if (path_is_busy(esp)) { + log_debug("%s already populated, ignoring.", esp); return 0; } - r = efi_loader_get_device_part_uuid(&id); - if (r == -ENOENT) { - log_debug("EFI loader partition unknown."); - return 0; - } + if (is_efi_boot()) { + _cleanup_blkid_free_probe_ blkid_probe b = NULL; + const char *fstype = NULL, *uuid_string = NULL; + sd_id128_t loader_uuid, part_uuid; - if (r < 0) - return log_error_errno(r, "Failed to read ESP partition UUID: %m"); + /* If this is an EFI boot, be extra careful, and only mount the ESP if it was the ESP used for booting. */ - errno = 0; - b = blkid_new_probe_from_filename(what); - if (!b) { - if (errno == 0) - return log_oom(); - return log_error_errno(errno, "Failed to allocate prober: %m"); - } - - blkid_probe_enable_partitions(b, 1); - blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); + r = efi_loader_get_device_part_uuid(&loader_uuid); + if (r == -ENOENT) { + log_debug("EFI loader partition unknown."); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to read ESP partition UUID: %m"); - errno = 0; - r = blkid_do_safeprobe(b); - if (r == -2 || r == 1) /* no result or uncertain */ - return 0; - else if (r != 0) - return log_error_errno(errno ?: EIO, "Failed to probe %s: %m", what); + errno = 0; + b = blkid_new_probe_from_filename(what); + if (!b) { + if (errno == 0) + return log_oom(); + return log_error_errno(errno, "Failed to allocate prober: %m"); + } - (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL); - if (!streq_ptr(fstype, "vfat")) { - log_debug("Partition for /boot is not a FAT filesystem, ignoring."); - return 0; - } + blkid_probe_enable_partitions(b, 1); + blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); - errno = 0; - r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &uuid, NULL); - if (r != 0) { - log_debug_errno(errno, "Partition for /boot does not have a UUID, ignoring."); - return 0; - } + errno = 0; + r = blkid_do_safeprobe(b); + if (r == -2 || r == 1) /* no result or uncertain */ + return 0; + else if (r != 0) + return log_error_errno(errno ?: EIO, "Failed to probe %s: %m", what); - if (sd_id128_from_string(uuid, &type_id) < 0) { - log_debug("Partition for /boot does not have a valid UUID, ignoring."); - return 0; - } + (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL); + if (!streq_ptr(fstype, "vfat")) { + log_debug("Partition for %s is not a FAT filesystem, ignoring.", esp); + return 0; + } - if (!sd_id128_equal(type_id, id)) { - log_debug("Partition for /boot does not appear to be the partition we are booted from."); - return 0; - } + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &uuid_string, NULL); + if (r != 0) { + log_debug_errno(errno, "Partition for %s does not have a UUID, ignoring.", esp); + return 0; + } - r = add_automount("boot", - what, - "/boot", - "vfat", - true, - "umask=0077", - "EFI System Partition Automount", - 120 * USEC_PER_SEC); + if (sd_id128_from_string(uuid_string, &part_uuid) < 0) { + log_debug("Partition for %s does not have a valid UUID, ignoring.", esp); + return 0; + } - return r; + if (!sd_id128_equal(part_uuid, loader_uuid)) { + log_debug("Partition for %s does not appear to be the partition we are booted from.", esp); + return 0; + } + } else + log_debug("Not an EFI boot, skipping ESP check."); + + return add_automount("boot", + what, + esp, + "vfat", + true, + "umask=0077", + "EFI System Partition Automount", + 120 * USEC_PER_SEC); } #else static int add_boot(const char *what) { @@ -905,7 +907,7 @@ fallback: return 1; } -static int parse_proc_cmdline_item(const char *key, const char *value) { +static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { int r; assert(key); @@ -1016,7 +1018,7 @@ int main(int argc, char *argv[]) { return EXIT_SUCCESS; } - r = parse_proc_cmdline(parse_proc_cmdline_item); + r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, false); if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); diff --git a/src/hibernate-resume/hibernate-resume-generator.c b/src/hibernate-resume/hibernate-resume-generator.c index d7ee80d58f..17e670604e 100644 --- a/src/hibernate-resume/hibernate-resume-generator.c +++ b/src/hibernate-resume/hibernate-resume-generator.c @@ -33,7 +33,7 @@ static const char *arg_dest = "/tmp"; static char *arg_resume_dev = NULL; -static int parse_proc_cmdline_item(const char *key, const char *value) { +static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { if (streq(key, "resume") && value) { free(arg_resume_dev); @@ -88,7 +88,7 @@ int main(int argc, char *argv[]) { if (!in_initrd()) return EXIT_SUCCESS; - r = parse_proc_cmdline(parse_proc_cmdline_item); + r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, false); if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index c16a324232..07c57fb567 100644 --- a/src/hostname/hostnamectl.c +++ b/src/hostname/hostnamectl.c @@ -251,7 +251,7 @@ static int set_simple_string(sd_bus *bus, const char *method, const char *value) static int set_hostname(sd_bus *bus, char **args, unsigned n) { _cleanup_free_ char *h = NULL; - char *hostname = args[1]; + const char *hostname = args[1]; int r; assert(args); @@ -263,27 +263,29 @@ static int set_hostname(sd_bus *bus, char **args, unsigned n) { if (arg_pretty) { const char *p; - /* If the passed hostname is already valid, then - * assume the user doesn't know anything about pretty - * hostnames, so let's unset the pretty hostname, and - * just set the passed hostname as static/dynamic + /* If the passed hostname is already valid, then assume the user doesn't know anything about pretty + * hostnames, so let's unset the pretty hostname, and just set the passed hostname as static/dynamic * hostname. */ - - if (arg_static && hostname_is_valid(hostname, true)) { - p = ""; - /* maybe get rid of trailing dot */ - hostname = hostname_cleanup(hostname); - } else { - p = h = strdup(hostname); - if (!p) - return log_oom(); - - hostname_cleanup(hostname); - } + if (arg_static && hostname_is_valid(hostname, true)) + p = ""; /* No pretty hostname (as it is redundant), just a static one */ + else + p = hostname; /* Use the passed name as pretty hostname */ r = set_simple_string(bus, "SetPrettyHostname", p); if (r < 0) return r; + + /* Now that we set the pretty hostname, let's clean up the parameter and use that as static + * hostname. If the hostname was already valid as static hostname, this will only chop off the trailing + * dot if there is one. If it was not valid, then it will be made fully valid by truncating, dropping + * multiple dots, and dropping weird chars. Note that we clean the name up only if we also are + * supposed to set the pretty name. If the pretty name is not being set we assume the user knows what + * he does and pass the name as-is. */ + h = strdup(hostname); + if (!h) + return log_oom(); + + hostname = hostname_cleanup(h); /* Use the cleaned up name as static hostname */ } if (arg_static) { diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index fe8bb62752..197f905b7d 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -148,56 +148,61 @@ static bool valid_deployment(const char *deployment) { } static const char* fallback_chassis(void) { - int r; char *type; unsigned t; - int v; + int v, r; v = detect_virtualization(); - if (VIRTUALIZATION_IS_VM(v)) return "vm"; if (VIRTUALIZATION_IS_CONTAINER(v)) return "container"; - r = read_one_line_file("/sys/firmware/acpi/pm_profile", &type); + r = read_one_line_file("/sys/class/dmi/id/chassis_type", &type); if (r < 0) - goto try_dmi; + goto try_acpi; r = safe_atou(type, &t); free(type); if (r < 0) - goto try_dmi; + goto try_acpi; - /* We only list the really obvious cases here as the ACPI data - * is not really super reliable. - * - * See the ACPI 5.0 Spec Section 5.2.9.1 for details: - * - * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf + /* We only list the really obvious cases here. The DMI data is unreliable enough, so let's not do any + additional guesswork on top of that. + + See the SMBIOS Specification 3.0 section 7.4.1 for details about the values listed here: + + https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.0.0.pdf */ - switch(t) { + switch (t) { - case 1: - case 3: - case 6: + case 0x3: /* Desktop */ + case 0x4: /* Low Profile Desktop */ + case 0x6: /* Mini Tower */ + case 0x7: /* Tower */ return "desktop"; - case 2: + case 0x8: /* Portable */ + case 0x9: /* Laptop */ + case 0xA: /* Notebook */ + case 0xE: /* Sub Notebook */ return "laptop"; - case 4: - case 5: - case 7: + case 0xB: /* Hand Held */ + return "handset"; + + case 0x11: /* Main Server Chassis */ + case 0x1C: /* Blade */ + case 0x1D: /* Blade Enclosure */ return "server"; - case 8: + case 0x1E: /* Tablet */ return "tablet"; } -try_dmi: - r = read_one_line_file("/sys/class/dmi/id/chassis_type", &type); +try_acpi: + r = read_one_line_file("/sys/firmware/acpi/pm_profile", &type); if (r < 0) return NULL; @@ -206,39 +211,29 @@ try_dmi: if (r < 0) return NULL; - /* We only list the really obvious cases here. The DMI data is - unreliable enough, so let's not do any additional guesswork - on top of that. - - See the SMBIOS Specification 3.0 section 7.4.1 for - details about the values listed here: - - https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.0.0.pdf + /* We only list the really obvious cases here as the ACPI data is not really super reliable. + * + * See the ACPI 5.0 Spec Section 5.2.9.1 for details: + * + * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf */ - switch (t) { + switch(t) { - case 0x3: - case 0x4: - case 0x6: - case 0x7: + case 1: /* Desktop */ + case 3: /* Workstation */ + case 6: /* Appliance PC */ return "desktop"; - case 0x8: - case 0x9: - case 0xA: - case 0xE: + case 2: /* Mobile */ return "laptop"; - case 0xB: - return "handset"; - - case 0x11: - case 0x1C: - case 0x1D: + case 4: /* Enterprise Server */ + case 5: /* SOHO Server */ + case 7: /* Performance Server */ return "server"; - case 0x1E: + case 8: /* Tablet */ return "tablet"; } @@ -456,7 +451,7 @@ static int method_set_hostname(sd_bus_message *m, void *userdata, sd_bus_error * r = context_update_kernel_hostname(c); if (r < 0) { log_error_errno(r, "Failed to set host name: %m"); - return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r)); + return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %m"); } log_info("Changed host name to '%s'", strna(c->data[PROP_HOSTNAME])); @@ -517,13 +512,13 @@ static int method_set_static_hostname(sd_bus_message *m, void *userdata, sd_bus_ r = context_update_kernel_hostname(c); if (r < 0) { log_error_errno(r, "Failed to set host name: %m"); - return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r)); + return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %m"); } r = context_write_data_static_hostname(c); if (r < 0) { log_error_errno(r, "Failed to write static host name: %m"); - return sd_bus_error_set_errnof(error, r, "Failed to set static hostname: %s", strerror(-r)); + return sd_bus_error_set_errnof(error, r, "Failed to set static hostname: %m"); } log_info("Changed static host name to '%s'", strna(c->data[PROP_STATIC_HOSTNAME])); @@ -598,7 +593,7 @@ static int set_machine_info(Context *c, sd_bus_message *m, int prop, sd_bus_mess r = context_write_data_machine_info(c); if (r < 0) { log_error_errno(r, "Failed to write machine info: %m"); - return sd_bus_error_set_errnof(error, r, "Failed to write machine info: %s", strerror(-r)); + return sd_bus_error_set_errnof(error, r, "Failed to write machine info: %m"); } log_info("Changed %s to '%s'", diff --git a/src/hwdb/hwdb.c b/src/hwdb/hwdb.c index e12cd93d1c..ab1feb435b 100644 --- a/src/hwdb/hwdb.c +++ b/src/hwdb/hwdb.c @@ -85,6 +85,8 @@ struct trie_child_entry { struct trie_value_entry { size_t key_off; size_t value_off; + size_t filename_off; + size_t line_number; }; static int trie_children_cmp(const void *v1, const void *v2) { @@ -157,9 +159,11 @@ static int trie_values_cmp(const void *v1, const void *v2, void *arg) { } static int trie_node_add_value(struct trie *trie, struct trie_node *node, - const char *key, const char *value) { - ssize_t k, v; + const char *key, const char *value, + const char *filename, size_t line_number) { + ssize_t k, v, fn; struct trie_value_entry *val; + int r; k = strbuf_add_string(trie->strings, key, strlen(key)); if (k < 0) @@ -167,6 +171,9 @@ static int trie_node_add_value(struct trie *trie, struct trie_node *node, v = strbuf_add_string(trie->strings, value, strlen(value)); if (v < 0) return v; + fn = strbuf_add_string(trie->strings, filename, strlen(filename)); + if (fn < 0) + return fn; if (node->values_count) { struct trie_value_entry search = { @@ -176,8 +183,20 @@ static int trie_node_add_value(struct trie *trie, struct trie_node *node, val = xbsearch_r(&search, node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie); if (val) { + /* + * At this point we have 2 identical properties on the same match-string. We + * strictly order them by filename+line-number, since we know the dynamic + * runtime lookup does the same for multiple matching nodes. + */ + r = strcmp(filename, trie->strings->buf + val->filename_off); + if (r < 0 || + (r == 0 && line_number < val->line_number)) + return 0; + /* replace existing earlier key with new value */ val->value_off = v; + val->filename_off = fn; + val->line_number = line_number; return 0; } } @@ -190,13 +209,16 @@ static int trie_node_add_value(struct trie *trie, struct trie_node *node, node->values = val; node->values[node->values_count].key_off = k; node->values[node->values_count].value_off = v; + node->values[node->values_count].filename_off = fn; + node->values[node->values_count].line_number = line_number; node->values_count++; qsort_r(node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie); return 0; } static int trie_insert(struct trie *trie, struct trie_node *node, const char *search, - const char *key, const char *value) { + const char *key, const char *value, + const char *filename, uint64_t line_number) { size_t i = 0; int err = 0; @@ -250,7 +272,7 @@ static int trie_insert(struct trie *trie, struct trie_node *node, const char *se c = search[i]; if (c == '\0') - return trie_node_add_value(trie, node, key, value); + return trie_node_add_value(trie, node, key, value, filename, line_number); child = node_lookup(node, c); if (!child) { @@ -274,7 +296,7 @@ static int trie_insert(struct trie *trie, struct trie_node *node, const char *se return err; } - return trie_node_add_value(trie, child, key, value); + return trie_node_add_value(trie, child, key, value, filename, line_number); } node = child; @@ -303,7 +325,7 @@ static void trie_store_nodes_size(struct trie_f *trie, struct trie_node *node) { for (i = 0; i < node->children_count; i++) trie->strings_off += sizeof(struct trie_child_entry_f); for (i = 0; i < node->values_count; i++) - trie->strings_off += sizeof(struct trie_value_entry_f); + trie->strings_off += sizeof(struct trie_value_entry2_f); } static int64_t trie_store_nodes(struct trie_f *trie, struct trie_node *node) { @@ -349,12 +371,14 @@ static int64_t trie_store_nodes(struct trie_f *trie, struct trie_node *node) { /* append values array */ for (i = 0; i < node->values_count; i++) { - struct trie_value_entry_f v = { + struct trie_value_entry2_f v = { .key_off = htole64(trie->strings_off + node->values[i].key_off), .value_off = htole64(trie->strings_off + node->values[i].value_off), + .filename_off = htole64(trie->strings_off + node->values[i].filename_off), + .line_number = htole64(node->values[i].line_number), }; - fwrite(&v, sizeof(struct trie_value_entry_f), 1, trie->f); + fwrite(&v, sizeof(struct trie_value_entry2_f), 1, trie->f); trie->values_count++; } @@ -375,7 +399,7 @@ static int trie_store(struct trie *trie, const char *filename) { .header_size = htole64(sizeof(struct trie_header_f)), .node_size = htole64(sizeof(struct trie_node_f)), .child_entry_size = htole64(sizeof(struct trie_child_entry_f)), - .value_entry_size = htole64(sizeof(struct trie_value_entry_f)), + .value_entry_size = htole64(sizeof(struct trie_value_entry2_f)), }; int err; @@ -431,14 +455,15 @@ static int trie_store(struct trie *trie, const char *filename) { log_debug("child pointers: %8"PRIu64" bytes (%8"PRIu64")", t.children_count * sizeof(struct trie_child_entry_f), t.children_count); log_debug("value pointers: %8"PRIu64" bytes (%8"PRIu64")", - t.values_count * sizeof(struct trie_value_entry_f), t.values_count); + t.values_count * sizeof(struct trie_value_entry2_f), t.values_count); log_debug("string store: %8zu bytes", trie->strings->len); log_debug("strings start: %8"PRIu64, t.strings_off); return 0; } -static int insert_data(struct trie *trie, char **match_list, char *line, const char *filename) { +static int insert_data(struct trie *trie, char **match_list, char *line, + const char *filename, size_t line_number) { char *value, **entry; value = strchr(line, '='); @@ -460,7 +485,7 @@ static int insert_data(struct trie *trie, char **match_list, char *line, const c } STRV_FOREACH(entry, match_list) - trie_insert(trie, trie->root, *entry, line, value); + trie_insert(trie, trie->root, *entry, line, value, filename, line_number); return 0; } @@ -474,6 +499,7 @@ static int import_file(struct trie *trie, const char *filename) { _cleanup_fclose_ FILE *f = NULL; char line[LINE_MAX]; _cleanup_strv_free_ char **match_list = NULL; + size_t line_number = 0; char *match = NULL; int r; @@ -485,6 +511,8 @@ static int import_file(struct trie *trie, const char *filename) { size_t len; char *pos; + ++line_number; + /* comment line */ if (line[0] == '#') continue; @@ -546,7 +574,7 @@ static int import_file(struct trie *trie, const char *filename) { /* first data */ state = HW_DATA; - insert_data(trie, match_list, line, filename); + insert_data(trie, match_list, line, filename, line_number); break; case HW_DATA: @@ -564,7 +592,7 @@ static int import_file(struct trie *trie, const char *filename) { break; } - insert_data(trie, match_list, line, filename); + insert_data(trie, match_list, line, filename, line_number); break; }; } diff --git a/src/import/curl-util.c b/src/import/curl-util.c index 6990c47f48..734e1560e6 100644 --- a/src/import/curl-util.c +++ b/src/import/curl-util.c @@ -235,9 +235,7 @@ CurlGlue *curl_glue_unref(CurlGlue *g) { sd_event_source_unref(g->timer); sd_event_unref(g->event); - free(g); - - return NULL; + return mfree(g); } int curl_glue_new(CurlGlue **glue, sd_event *event) { diff --git a/src/import/export-raw.c b/src/import/export-raw.c index db06e11b87..a3dbce1954 100644 --- a/src/import/export-raw.c +++ b/src/import/export-raw.c @@ -34,6 +34,7 @@ #include "fd-util.h" #include "fileio.h" #include "import-common.h" +#include "missing.h" #include "ratelimit.h" #include "string-util.h" #include "util.h" @@ -86,9 +87,7 @@ RawExport *raw_export_unref(RawExport *e) { free(e->buffer); free(e->path); - free(e); - - return NULL; + return mfree(e); } int raw_export_new( diff --git a/src/import/export-tar.c b/src/import/export-tar.c index d79c27f2d0..3bb6027431 100644 --- a/src/import/export-tar.c +++ b/src/import/export-tar.c @@ -91,9 +91,7 @@ TarExport *tar_export_unref(TarExport *e) { free(e->buffer); free(e->path); - free(e); - - return NULL; + return mfree(e); } int tar_export_new( diff --git a/src/import/import-raw.c b/src/import/import-raw.c index fd6b9f7703..29f3f896e5 100644 --- a/src/import/import-raw.c +++ b/src/import/import-raw.c @@ -100,9 +100,7 @@ RawImport* raw_import_unref(RawImport *i) { free(i->final_path); free(i->image_root); free(i->local); - free(i); - - return NULL; + return mfree(i); } int raw_import_new( diff --git a/src/import/import-tar.c b/src/import/import-tar.c index 8b81324fde..22f9b8c5ea 100644 --- a/src/import/import-tar.c +++ b/src/import/import-tar.c @@ -107,9 +107,7 @@ TarImport* tar_import_unref(TarImport *i) { free(i->final_path); free(i->image_root); free(i->local); - free(i); - - return NULL; + return mfree(i); } int tar_import_new( diff --git a/src/import/importd.c b/src/import/importd.c index 28b4302cb3..9d31a956a5 100644 --- a/src/import/importd.c +++ b/src/import/importd.c @@ -141,8 +141,7 @@ static Transfer *transfer_unref(Transfer *t) { safe_close(t->stdin_fd); safe_close(t->stdout_fd); - free(t); - return NULL; + return mfree(t); } DEFINE_TRIVIAL_CLEANUP_FUNC(Transfer*, transfer_unref); @@ -548,8 +547,7 @@ static Manager *manager_unref(Manager *m) { m->bus = sd_bus_flush_close_unref(m->bus); sd_event_unref(m->event); - free(m); - return NULL; + return mfree(m); } DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_unref); diff --git a/src/import/pull-job.c b/src/import/pull-job.c index 6bcf35ef4e..e550df2c57 100644 --- a/src/import/pull-job.c +++ b/src/import/pull-job.c @@ -50,9 +50,7 @@ PullJob* pull_job_unref(PullJob *j) { free(j->payload); free(j->checksum); - free(j); - - return NULL; + return mfree(j); } static void pull_job_finish(PullJob *j, int ret) { diff --git a/src/import/pull-raw.c b/src/import/pull-raw.c index 8993402821..0cf410a5d9 100644 --- a/src/import/pull-raw.c +++ b/src/import/pull-raw.c @@ -110,9 +110,7 @@ RawPull* raw_pull_unref(RawPull *i) { free(i->settings_path); free(i->image_root); free(i->local); - free(i); - - return NULL; + return mfree(i); } int raw_pull_new( diff --git a/src/import/pull-tar.c b/src/import/pull-tar.c index 8c61c46f73..68e2397b02 100644 --- a/src/import/pull-tar.c +++ b/src/import/pull-tar.c @@ -114,9 +114,7 @@ TarPull* tar_pull_unref(TarPull *i) { free(i->settings_path); free(i->image_root); free(i->local); - free(i); - - return NULL; + return mfree(i); } int tar_pull_new( diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c index e265027a04..6611a355d4 100644 --- a/src/journal-remote/journal-gatewayd.c +++ b/src/journal-remote/journal-gatewayd.c @@ -19,9 +19,6 @@ #include <fcntl.h> #include <getopt.h> -#ifdef HAVE_GNUTLS -#include <gnutls/gnutls.h> -#endif #include <microhttpd.h> #include <stdlib.h> #include <string.h> @@ -48,6 +45,7 @@ static char *arg_key_pem = NULL; static char *arg_cert_pem = NULL; static char *arg_trust_pem = NULL; +static char *arg_directory = NULL; typedef struct RequestMeta { sd_journal *journal; @@ -118,7 +116,10 @@ static int open_journal(RequestMeta *m) { if (m->journal) return 0; - return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM); + if (arg_directory) + return sd_journal_open_directory(&m->journal, arg_directory, 0); + else + return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM); } static int request_meta_ensure_tmp(RequestMeta *m) { @@ -239,6 +240,9 @@ static ssize_t request_reader_entries( m->size = (uint64_t) sz; } + if (m->tmp == NULL && m->follow) + return 0; + if (fseeko(m->tmp, pos, SEEK_SET) < 0) { log_error_errno(errno, "Failed to seek to position: %m"); return MHD_CONTENT_READER_END_WITH_ERROR; @@ -471,20 +475,20 @@ static int request_handler_entries( r = open_journal(m); if (r < 0) - return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r)); + return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %m"); if (request_parse_accept(m, connection) < 0) - return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n"); + return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header."); if (request_parse_range(m, connection) < 0) - return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n"); + return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header."); if (request_parse_arguments(m, connection) < 0) - return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n"); + return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments."); if (m->discrete) { if (!m->cursor) - return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n"); + return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification."); m->n_entries = 1; m->n_entries_set = true; @@ -497,7 +501,7 @@ static int request_handler_entries( else if (m->n_skip < 0) r = sd_journal_seek_tail(m->journal); if (r < 0) - return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n"); + return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal."); response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL); if (!response) @@ -629,14 +633,14 @@ static int request_handler_fields( r = open_journal(m); if (r < 0) - return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r)); + return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %m"); if (request_parse_accept(m, connection) < 0) - return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n"); + return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header."); r = sd_journal_query_unique(m->journal, field); if (r < 0) - return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n"); + return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields."); response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL); if (!response) @@ -695,10 +699,10 @@ static int request_handler_file( fd = open(path, O_RDONLY|O_CLOEXEC); if (fd < 0) - return mhd_respondf(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path); + return mhd_respondf(connection, errno, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m", path); if (fstat(fd, &st) < 0) - return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n"); + return mhd_respondf(connection, errno, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m"); response = MHD_create_response_from_fd_at_offset64(st.st_size, fd, 0); if (!response) @@ -762,15 +766,15 @@ static int request_handler_machine( r = open_journal(m); if (r < 0) - return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r)); + return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %m"); r = sd_id128_get_machine(&mid); if (r < 0) - return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r)); + return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %m"); r = sd_id128_get_boot(&bid); if (r < 0) - return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r)); + return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %m"); hostname = gethostname_malloc(); if (!hostname) @@ -778,11 +782,11 @@ static int request_handler_machine( r = sd_journal_get_usage(m->journal, &usage); if (r < 0) - return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r)); + return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %m"); r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to); if (r < 0) - return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r)); + return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %m"); if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL) == -ENOENT) (void) parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL); @@ -840,8 +844,7 @@ static int request_handler( assert(method); if (!streq(method, "GET")) - return mhd_respond(connection, MHD_HTTP_NOT_ACCEPTABLE, - "Unsupported method.\n"); + return mhd_respond(connection, MHD_HTTP_NOT_ACCEPTABLE, "Unsupported method."); if (!*connection_cls) { @@ -871,7 +874,7 @@ static int request_handler( if (streq(url, "/machine")) return request_handler_machine(connection, *connection_cls); - return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found.\n"); + return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found."); } static void help(void) { @@ -881,7 +884,8 @@ static void help(void) { " --version Show package version\n" " --cert=CERT.PEM Server certificate in PEM format\n" " --key=KEY.PEM Server key in PEM format\n" - " --trust=CERT.PEM Certificat authority certificate in PEM format\n", + " --trust=CERT.PEM Certificate authority certificate in PEM format\n" + " -D --directory=PATH Serve journal files in directory\n", program_invocation_short_name); } @@ -896,11 +900,12 @@ static int parse_argv(int argc, char *argv[]) { int r, c; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "key", required_argument, NULL, ARG_KEY }, - { "cert", required_argument, NULL, ARG_CERT }, - { "trust", required_argument, NULL, ARG_TRUST }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "key", required_argument, NULL, ARG_KEY }, + { "cert", required_argument, NULL, ARG_CERT }, + { "trust", required_argument, NULL, ARG_TRUST }, + { "directory", required_argument, NULL, 'D' }, {} }; @@ -954,6 +959,9 @@ static int parse_argv(int argc, char *argv[]) { #else log_error("Option --trust is not available."); #endif + case 'D': + arg_directory = optarg; + break; case '?': return -EINVAL; diff --git a/src/journal-remote/journal-remote-write.c b/src/journal-remote/journal-remote-write.c index 7bba52566e..8729372aa3 100644 --- a/src/journal-remote/journal-remote-write.c +++ b/src/journal-remote/journal-remote-write.c @@ -75,10 +75,8 @@ Writer* writer_new(RemoteServer *server) { memset(&w->metrics, 0xFF, sizeof(w->metrics)); w->mmap = mmap_cache_new(); - if (!w->mmap) { - free(w); - return NULL; - } + if (!w->mmap) + return mfree(w); w->n_ref = 1; w->server = server; @@ -103,9 +101,7 @@ Writer* writer_free(Writer *w) { if (w->mmap) mmap_cache_unref(w->mmap); - free(w); - - return NULL; + return mfree(w); } Writer* writer_unref(Writer *w) { diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c index 35a1e55f9e..d86c3681b1 100644 --- a/src/journal-remote/journal-remote.c +++ b/src/journal-remote/journal-remote.c @@ -27,10 +27,6 @@ #include <sys/socket.h> #include <unistd.h> -#ifdef HAVE_GNUTLS -#include <gnutls/gnutls.h> -#endif - #include "sd-daemon.h" #include "alloc-util.h" @@ -131,6 +127,10 @@ static int spawn_child(const char* child, char** argv) { if (r < 0) log_warning_errno(errno, "Failed to close write end of pipe: %m"); + r = fd_nonblock(fd[0], true); + if (r < 0) + log_warning_errno(errno, "Failed to set child pipe to non-blocking: %m"); + return fd[0]; } @@ -528,13 +528,12 @@ static int process_http_upload( log_warning("Failed to process data for connection %p", connection); if (r == -E2BIG) return mhd_respondf(connection, - MHD_HTTP_REQUEST_ENTITY_TOO_LARGE, - "Entry is too large, maximum is %u bytes.\n", - DATA_SIZE_MAX); + r, MHD_HTTP_REQUEST_ENTITY_TOO_LARGE, + "Entry is too large, maximum is " STRINGIFY(DATA_SIZE_MAX) " bytes."); else return mhd_respondf(connection, - MHD_HTTP_UNPROCESSABLE_ENTITY, - "Processing failed: %s.", strerror(-r)); + r, MHD_HTTP_UNPROCESSABLE_ENTITY, + "Processing failed: %m."); } } @@ -545,13 +544,14 @@ static int process_http_upload( remaining = source_non_empty(source); if (remaining > 0) { - log_warning("Premature EOFbyte. %zu bytes lost.", remaining); - return mhd_respondf(connection, MHD_HTTP_EXPECTATION_FAILED, + log_warning("Premature EOF byte. %zu bytes lost.", remaining); + return mhd_respondf(connection, + 0, MHD_HTTP_EXPECTATION_FAILED, "Premature EOF. %zu bytes of trailing data not processed.", remaining); } - return mhd_respond(connection, MHD_HTTP_ACCEPTED, "OK.\n"); + return mhd_respond(connection, MHD_HTTP_ACCEPTED, "OK."); }; static int request_handler( @@ -581,19 +581,16 @@ static int request_handler( *connection_cls); if (!streq(method, "POST")) - return mhd_respond(connection, MHD_HTTP_NOT_ACCEPTABLE, - "Unsupported method.\n"); + return mhd_respond(connection, MHD_HTTP_NOT_ACCEPTABLE, "Unsupported method."); if (!streq(url, "/upload")) - return mhd_respond(connection, MHD_HTTP_NOT_FOUND, - "Not found.\n"); + return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found."); header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Content-Type"); if (!header || !streq(header, "application/vnd.fdo.journal")) return mhd_respond(connection, MHD_HTTP_UNSUPPORTED_MEDIA_TYPE, - "Content-Type: application/vnd.fdo.journal" - " is required.\n"); + "Content-Type: application/vnd.fdo.journal is required."); { const union MHD_ConnectionInfo *ci; @@ -603,7 +600,7 @@ static int request_handler( if (!ci) { log_error("MHD_get_connection_info failed: cannot get remote fd"); return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, - "Cannot check remote address"); + "Cannot check remote address."); } fd = ci->connect_fd; @@ -618,7 +615,7 @@ static int request_handler( r = getpeername_pretty(fd, false, &hostname); if (r < 0) return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, - "Cannot check remote hostname"); + "Cannot check remote hostname."); } assert(hostname); @@ -627,8 +624,7 @@ static int request_handler( if (r == -ENOMEM) return respond_oom(connection); else if (r < 0) - return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, - strerror(-r)); + return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "%m"); hostname = NULL; return MHD_YES; @@ -1202,7 +1198,7 @@ static int parse_config(void) { { "Remote", "TrustedCertificateFile", config_parse_path, 0, &arg_trust }, {}}; - return config_parse_many(PKGSYSCONFDIR "/journal-remote.conf", + return config_parse_many_nulstr(PKGSYSCONFDIR "/journal-remote.conf", CONF_PATHS_NULSTR("systemd/journal-remote.conf.d"), "Remote\0", config_item_table_lookup, items, false, NULL); @@ -1564,7 +1560,7 @@ int main(int argc, char **argv) { if (r < 0) log_error_errno(r, "Failed to enable watchdog: %m"); else - log_debug("Watchdog is %s.", r > 0 ? "enabled" : "disabled"); + log_debug("Watchdog is %sd.", enable_disable(r > 0)); log_debug("%s running as pid "PID_FMT, program_invocation_short_name, getpid()); diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c index 4647cfdeb3..61190ff83c 100644 --- a/src/journal-remote/journal-upload.c +++ b/src/journal-remote/journal-upload.c @@ -527,9 +527,7 @@ static int perform_upload(Uploader *u) { log_debug("Upload finished successfully with code %ld: %s", status, strna(u->answer)); - free(u->last_cursor); - u->last_cursor = u->current_cursor; - u->current_cursor = NULL; + free_and_replace(u->last_cursor, u->current_cursor); return update_cursor_state(u); } @@ -542,7 +540,7 @@ static int parse_config(void) { { "Upload", "TrustedCertificateFile", config_parse_path, 0, &arg_trust }, {}}; - return config_parse_many(PKGSYSCONFDIR "/journal-upload.conf", + return config_parse_many_nulstr(PKGSYSCONFDIR "/journal-upload.conf", CONF_PATHS_NULSTR("systemd/journal-upload.conf.d"), "Upload\0", config_item_table_lookup, items, false, NULL); diff --git a/src/journal-remote/microhttpd-util.c b/src/journal-remote/microhttpd-util.c index 2f16b02e9a..cae10203c6 100644 --- a/src/journal-remote/microhttpd-util.c +++ b/src/journal-remote/microhttpd-util.c @@ -48,7 +48,7 @@ void microhttpd_logger(void *arg, const char *fmt, va_list ap) { static int mhd_respond_internal(struct MHD_Connection *connection, enum MHD_RequestTerminationCode code, - char *buffer, + const char *buffer, size_t size, enum MHD_ResponseMemoryMode mode) { struct MHD_Response *response; @@ -56,7 +56,7 @@ static int mhd_respond_internal(struct MHD_Connection *connection, assert(connection); - response = MHD_create_response_from_buffer(size, buffer, mode); + response = MHD_create_response_from_buffer(size, (char*) buffer, mode); if (!response) return MHD_NO; @@ -72,19 +72,25 @@ int mhd_respond(struct MHD_Connection *connection, enum MHD_RequestTerminationCode code, const char *message) { + const char *fmt; + + fmt = strjoina(message, "\n"); + return mhd_respond_internal(connection, code, - (char*) message, strlen(message), + fmt, strlen(message) + 1, MHD_RESPMEM_PERSISTENT); } int mhd_respond_oom(struct MHD_Connection *connection) { - return mhd_respond(connection, MHD_HTTP_SERVICE_UNAVAILABLE, "Out of memory.\n"); + return mhd_respond(connection, MHD_HTTP_SERVICE_UNAVAILABLE, "Out of memory."); } int mhd_respondf(struct MHD_Connection *connection, + int error, enum MHD_RequestTerminationCode code, const char *format, ...) { + const char *fmt; char *m; int r; va_list ap; @@ -92,8 +98,12 @@ int mhd_respondf(struct MHD_Connection *connection, assert(connection); assert(format); + if (error < 0) + error = -error; + errno = -error; + fmt = strjoina(format, "\n"); va_start(ap, format); - r = vasprintf(&m, format, ap); + r = vasprintf(&m, fmt, ap); va_end(ap); if (r < 0) diff --git a/src/journal-remote/microhttpd-util.h b/src/journal-remote/microhttpd-util.h index ea160f212b..af26ab69fe 100644 --- a/src/journal-remote/microhttpd-util.h +++ b/src/journal-remote/microhttpd-util.h @@ -39,8 +39,9 @@ void microhttpd_logger(void *arg, const char *fmt, va_list ap) _printf_(2, 0); #define respond_oom(connection) log_oom(), mhd_respond_oom(connection) int mhd_respondf(struct MHD_Connection *connection, + int error, unsigned code, - const char *format, ...) _printf_(3,4); + const char *format, ...) _printf_(4,5); int mhd_respond(struct MHD_Connection *connection, unsigned code, diff --git a/src/journal/.gitignore b/src/journal/.gitignore index 04d5852547..b93a9462fa 100644 --- a/src/journal/.gitignore +++ b/src/journal/.gitignore @@ -1,4 +1,3 @@ /journald-gperf.c -/libsystemd-journal.pc /audit_type-list.txt /audit_type-*-name.* diff --git a/src/journal/journal-file.c b/src/journal/journal-file.c index 7504326bff..d3e0214731 100644 --- a/src/journal/journal-file.c +++ b/src/journal/journal-file.c @@ -333,8 +333,13 @@ JournalFile* journal_file_close(JournalFile *f) { #ifdef HAVE_GCRYPT /* Write the final tag */ - if (f->seal && f->writable) - journal_file_append_tag(f); + if (f->seal && f->writable) { + int r; + + r = journal_file_append_tag(f); + if (r < 0) + log_error_errno(r, "Failed to append tag when closing journal: %m"); + } #endif if (f->post_change_timer) { @@ -389,8 +394,7 @@ JournalFile* journal_file_close(JournalFile *f) { gcry_md_close(f->hmac); #endif - free(f); - return NULL; + return mfree(f); } void journal_file_close_set(Set *s) { @@ -563,8 +567,8 @@ static int journal_file_verify_header(JournalFile *f) { return -ENODATA; if (f->writable) { - uint8_t state; sd_id128_t machine_id; + uint8_t state; int r; r = sd_id128_get_machine(&machine_id); @@ -585,6 +589,14 @@ static int journal_file_verify_header(JournalFile *f) { log_debug("Journal file %s has unknown state %i.", f->path, state); return -EBUSY; } + + /* Don't permit appending to files from the future. Because otherwise the realtime timestamps wouldn't + * be strictly ordered in the entries in the file anymore, and we can't have that since it breaks + * bisection. */ + if (le64toh(f->header->tail_entry_realtime) > now(CLOCK_REALTIME)) { + log_debug("Journal file %s is from the future, refusing to append new data to it that'd be older.", f->path); + return -ETXTBSY; + } } f->compress_xz = JOURNAL_HEADER_COMPRESSED_XZ(f->header); @@ -742,12 +754,16 @@ int journal_file_move_to_object(JournalFile *f, ObjectType type, uint64_t offset assert(ret); /* Objects may only be located at multiple of 64 bit */ - if (!VALID64(offset)) + if (!VALID64(offset)) { + log_debug("Attempt to move to object at non-64bit boundary: %" PRIu64, offset); return -EBADMSG; + } /* Object may not be located in the file header */ - if (offset < le64toh(f->header->header_size)) + if (offset < le64toh(f->header->header_size)) { + log_debug("Attempt to move to object located in file header: %" PRIu64, offset); return -EBADMSG; + } r = journal_file_move_to(f, type, false, offset, sizeof(ObjectHeader), &t); if (r < 0) @@ -756,17 +772,29 @@ int journal_file_move_to_object(JournalFile *f, ObjectType type, uint64_t offset o = (Object*) t; s = le64toh(o->object.size); - if (s < sizeof(ObjectHeader)) + if (s == 0) { + log_debug("Attempt to move to uninitialized object: %" PRIu64, offset); return -EBADMSG; + } + if (s < sizeof(ObjectHeader)) { + log_debug("Attempt to move to overly short object: %" PRIu64, offset); + return -EBADMSG; + } - if (o->object.type <= OBJECT_UNUSED) + if (o->object.type <= OBJECT_UNUSED) { + log_debug("Attempt to move to object with invalid type: %" PRIu64, offset); return -EBADMSG; + } - if (s < minimum_header_size(o)) + if (s < minimum_header_size(o)) { + log_debug("Attempt to move to truncated object: %" PRIu64, offset); return -EBADMSG; + } - if (type > OBJECT_UNUSED && o->object.type != type) + if (type > OBJECT_UNUSED && o->object.type != type) { + log_debug("Attempt to move to object of unexpected type: %" PRIu64, offset); return -EBADMSG; + } if (s > sizeof(ObjectHeader)) { r = journal_file_move_to(f, type, false, offset, s, &t); @@ -1369,6 +1397,12 @@ static int journal_file_append_data( if (r < 0) return r; +#ifdef HAVE_GCRYPT + r = journal_file_hmac_put_object(f, OBJECT_DATA, o, p); + if (r < 0) + return r; +#endif + /* The linking might have altered the window, so let's * refresh our pointer */ r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); @@ -1393,12 +1427,6 @@ static int journal_file_append_data( fo->field.head_data_offset = le64toh(p); } -#ifdef HAVE_GCRYPT - r = journal_file_hmac_put_object(f, OBJECT_DATA, o, p); - if (r < 0) - return r; -#endif - if (ret) *ret = o; @@ -2467,6 +2495,37 @@ int journal_file_compare_locations(JournalFile *af, JournalFile *bf) { return 0; } +static int bump_array_index(uint64_t *i, direction_t direction, uint64_t n) { + + /* Increase or decrease the specified index, in the right direction. */ + + if (direction == DIRECTION_DOWN) { + if (*i >= n - 1) + return 0; + + (*i) ++; + } else { + if (*i <= 0) + return 0; + + (*i) --; + } + + return 1; +} + +static bool check_properly_ordered(uint64_t new_offset, uint64_t old_offset, direction_t direction) { + + /* Consider it an error if any of the two offsets is uninitialized */ + if (old_offset == 0 || new_offset == 0) + return false; + + /* If we go down, the new offset must be larger than the old one. */ + return direction == DIRECTION_DOWN ? + new_offset > old_offset : + new_offset < old_offset; +} + int journal_file_next_entry( JournalFile *f, uint64_t p, @@ -2497,36 +2556,34 @@ int journal_file_next_entry( if (r <= 0) return r; - if (direction == DIRECTION_DOWN) { - if (i >= n - 1) - return 0; - - i++; - } else { - if (i <= 0) - return 0; - - i--; - } + r = bump_array_index(&i, direction, n); + if (r <= 0) + return r; } /* And jump to it */ - r = generic_array_get(f, - le64toh(f->header->entry_array_offset), - i, - ret, &ofs); - if (r == -EBADMSG && direction == DIRECTION_DOWN) { - /* Special case: when we iterate throught the journal file linearly, and hit an entry we can't read, - * consider this the end of the journal file. */ - log_debug_errno(r, "Encountered entry we can't read while iterating through journal file. Considering this the end of the file."); - return 0; + for (;;) { + r = generic_array_get(f, + le64toh(f->header->entry_array_offset), + i, + ret, &ofs); + if (r > 0) + break; + if (r != -EBADMSG) + return r; + + /* OK, so this entry is borked. Most likely some entry didn't get synced to disk properly, let's see if + * the next one might work for us instead. */ + log_debug_errno(r, "Entry item %" PRIu64 " is bad, skipping over it.", i); + + r = bump_array_index(&i, direction, n); + if (r <= 0) + return r; } - if (r <= 0) - return r; - if (p > 0 && - (direction == DIRECTION_DOWN ? ofs <= p : ofs >= p)) { - log_debug("%s: entry array corrupted at entry %" PRIu64, f->path, i); + /* Ensure our array is properly ordered. */ + if (p > 0 && !check_properly_ordered(ofs, p, direction)) { + log_debug("%s: entry array not properly ordered at entry %" PRIu64, f->path, i); return -EBADMSG; } @@ -2543,9 +2600,9 @@ int journal_file_next_entry_for_data( direction_t direction, Object **ret, uint64_t *offset) { - uint64_t n, i; - int r; + uint64_t i, n, ofs; Object *d; + int r; assert(f); assert(p > 0 || !o); @@ -2577,25 +2634,39 @@ int journal_file_next_entry_for_data( if (r <= 0) return r; - if (direction == DIRECTION_DOWN) { - if (i >= n - 1) - return 0; + r = bump_array_index(&i, direction, n); + if (r <= 0) + return r; + } - i++; - } else { - if (i <= 0) - return 0; + for (;;) { + r = generic_array_get_plus_one(f, + le64toh(d->data.entry_offset), + le64toh(d->data.entry_array_offset), + i, + ret, &ofs); + if (r > 0) + break; + if (r != -EBADMSG) + return r; - i--; - } + log_debug_errno(r, "Data entry item %" PRIu64 " is bad, skipping over it.", i); + + r = bump_array_index(&i, direction, n); + if (r <= 0) + return r; + } + /* Ensure our array is properly ordered. */ + if (p > 0 && check_properly_ordered(ofs, p, direction)) { + log_debug("%s data entry array not properly ordered at entry %" PRIu64, f->path, i); + return -EBADMSG; } - return generic_array_get_plus_one(f, - le64toh(d->data.entry_offset), - le64toh(d->data.entry_array_offset), - i, - ret, offset); + if (offset) + *offset = ofs; + + return 1; } int journal_file_move_to_entry_by_offset_for_data( @@ -3266,7 +3337,8 @@ int journal_file_open_reliably( -EBUSY, /* unclean shutdown */ -ESHUTDOWN, /* already archived */ -EIO, /* IO error, including SIGBUS on mmap */ - -EIDRM /* File has been deleted */)) + -EIDRM, /* File has been deleted */ + -ETXTBSY)) /* File is from the future */ return r; if ((flags & O_ACCMODE) == O_RDONLY) diff --git a/src/journal/journal-vacuum.c b/src/journal/journal-vacuum.c index f09dc66e03..12ce2fd56c 100644 --- a/src/journal/journal-vacuum.c +++ b/src/journal/journal-vacuum.c @@ -343,7 +343,7 @@ finish: free(list[i].filename); free(list); - log_full(verbose ? LOG_INFO : LOG_DEBUG, "Vacuuming done, freed %s of archived journals on disk.", format_bytes(sbytes, sizeof(sbytes), freed)); + log_full(verbose ? LOG_INFO : LOG_DEBUG, "Vacuuming done, freed %s of archived journals from %s.", format_bytes(sbytes, sizeof(sbytes), freed), directory); return r; } diff --git a/src/journal/journal-verify.c b/src/journal/journal-verify.c index f61f158e8a..9e4d8a28a5 100644 --- a/src/journal/journal-verify.c +++ b/src/journal/journal-verify.c @@ -118,6 +118,11 @@ static void flush_progress(void) { log_error(OFSfmt": " _fmt, (uint64_t)_offset, ##__VA_ARGS__); \ } while (0) +#define error_errno(_offset, error, _fmt, ...) do { \ + flush_progress(); \ + log_error_errno(error, OFSfmt": " _fmt, (uint64_t)_offset, ##__VA_ARGS__); \ + } while (0) + static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o) { uint64_t i; @@ -168,8 +173,8 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o le64toh(o->object.size) - offsetof(Object, data.payload), &b, &alloc, &b_size, 0); if (r < 0) { - error(offset, "%s decompression failed: %s", - object_compressed_to_string(compression), strerror(-r)); + error_errno(offset, r, "%s decompression failed: %m", + object_compressed_to_string(compression)); return r; } @@ -826,7 +831,7 @@ int journal_file_verify( int data_fd = -1, entry_fd = -1, entry_array_fd = -1; unsigned i; bool found_last = false; - _cleanup_free_ char *tmp_dir = NULL; + const char *tmp_dir = NULL; #ifdef HAVE_GCRYPT uint64_t last_tag = 0; @@ -846,7 +851,7 @@ int journal_file_verify( } else if (f->seal) return -ENOKEY; - r = var_tmp(&tmp_dir); + r = var_tmp_dir(&tmp_dir); if (r < 0) { log_error_errno(r, "Failed to determine temporary directory: %m"); goto fail; @@ -912,7 +917,7 @@ int journal_file_verify( r = journal_file_object_verify(f, p, o); if (r < 0) { - error(p, "Invalid object contents: %s", strerror(-r)); + error_errno(p, r, "Invalid object contents: %m"); goto fail; } diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index 53c6180864..7f997487b4 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -297,9 +297,9 @@ static void help(void) { " -n --lines[=INTEGER] Number of journal entries to show\n" " --no-tail Show all lines, even in follow mode\n" " -r --reverse Show the newest entries first\n" - " -o --output=STRING Change journal output mode (short, short-iso,\n" - " short-precise, short-monotonic, verbose,\n" - " export, json, json-pretty, json-sse, cat)\n" + " -o --output=STRING Change journal output mode (short, short-precise,\n" + " short-iso, short-full, short-monotonic, short-unix,\n" + " verbose, export, json, json-pretty, json-sse, cat)\n" " --utc Express time in Coordinated Universal Time (UTC)\n" " -x --catalog Add message explanations where available\n" " --no-full Ellipsize fields\n" @@ -310,7 +310,7 @@ static void help(void) { " -m --merge Show entries from all available journals\n" " -D --directory=PATH Show journal files from directory\n" " --file=PATH Show journal file\n" - " --root=ROOT Operate on catalog files below a root directory\n" + " --root=ROOT Operate on files below a root directory\n" #ifdef HAVE_GCRYPT " --interval=TIME Time interval for changing the FSS sealing key\n" " --verify-key=KEY Specify FSS verification key\n" @@ -848,8 +848,8 @@ static int parse_argv(int argc, char *argv[]) { if (arg_follow && !arg_no_tail && !arg_since && arg_lines == ARG_LINES_DEFAULT) arg_lines = 10; - if (!!arg_directory + !!arg_file + !!arg_machine > 1) { - log_error("Please specify either -D/--directory= or --file= or -M/--machine=, not more than one."); + if (!!arg_directory + !!arg_file + !!arg_machine + !!arg_root > 1) { + log_error("Please specify at most one of -D/--directory=, --file=, -M/--machine=, --root."); return -EINVAL; } @@ -1091,8 +1091,10 @@ static int discover_next_boot(sd_journal *j, r = sd_journal_previous(j); if (r < 0) return r; - else if (r == 0) + else if (r == 0) { + log_debug("Whoopsie! We found a boot ID but can't read its last entry."); return -ENODATA; /* This shouldn't happen. We just came from this very boot ID. */ + } r = sd_journal_get_realtime_usec(j, &next_boot->last); if (r < 0) @@ -1112,7 +1114,7 @@ static int get_boots( bool skip_once; int r, count = 0; - BootId *head = NULL, *tail = NULL; + BootId *head = NULL, *tail = NULL, *id; const bool advance_older = boot_id && offset <= 0; sd_id128_t previous_boot_id; @@ -1203,6 +1205,13 @@ static int get_boots( break; } } else { + LIST_FOREACH(boot_list, id, head) { + if (sd_id128_equal(id->id, current->id)) { + /* boot id already stored, something wrong with the journal files */ + /* exiting as otherwise this problem would cause forever loop */ + goto finish; + } + } LIST_INSERT_AFTER(boot_list, head, tail, current); tail = current; current = NULL; @@ -1267,7 +1276,7 @@ static int add_boot(sd_journal *j) { * We can do this only when we logs are coming from the current machine, * so take the slow path if log location is specified. */ if (arg_boot_offset == 0 && sd_id128_is_null(arg_boot_id) && - !arg_directory && !arg_file) + !arg_directory && !arg_file && !arg_root) return add_match_this_boot(j, arg_machine); @@ -1632,7 +1641,7 @@ static int setup_keys(void) { n /= arg_interval; safe_close(fd); - fd = mkostemp_safe(k, O_WRONLY|O_CLOEXEC); + fd = mkostemp_safe(k); if (fd < 0) { r = log_error_errno(fd, "Failed to open %s: %m", k); goto finish; @@ -1684,9 +1693,9 @@ static int setup_keys(void) { "at a safe location and should not be saved locally on disk.\n" "\n\t%s", ansi_highlight(), ansi_normal(), + p, ansi_highlight(), ansi_normal(), - ansi_highlight_red(), - p); + ansi_highlight_red()); fflush(stderr); } for (i = 0; i < seed_size; i++) { @@ -2161,6 +2170,8 @@ int main(int argc, char *argv[]) { if (arg_directory) r = sd_journal_open_directory(&j, arg_directory, arg_journal_type); + else if (arg_root) + r = sd_journal_open_directory(&j, arg_root, arg_journal_type | SD_JOURNAL_OS_ROOT); else if (arg_file_stdin) { int ifd = STDIN_FILENO; r = sd_journal_open_files_fd(&j, &ifd, 1, 0); @@ -2255,7 +2266,7 @@ int main(int argc, char *argv[]) { if (r < 0) goto finish; - printf("Archived and active journals take up %s on disk.\n", + printf("Archived and active journals take up %s in the file system.\n", format_bytes(sbytes, sizeof(sbytes), bytes)); goto finish; } diff --git a/src/journal/journald-console.c b/src/journal/journald-console.c index fcc9f25814..3a9fba42a3 100644 --- a/src/journal/journald-console.c +++ b/src/journal/journald-console.c @@ -102,6 +102,11 @@ void server_forward_console( tty = s->tty_path ? s->tty_path : "/dev/console"; + /* Before you ask: yes, on purpose we open/close the console for each log line we write individually. This is a + * good strategy to avoid journald getting killed by the kernel's SAK concept (it doesn't fix this entirely, + * but minimizes the time window the kernel might end up killing journald due to SAK). It also makes things + * easier for us so that we don't have to recover from hangups and suchlike triggered on the console. */ + fd = open_terminal(tty, O_WRONLY|O_NOCTTY|O_CLOEXEC); if (fd < 0) { log_debug_errno(fd, "Failed to open %s for logging: %m", tty); diff --git a/src/journal/journald-gperf.gperf b/src/journal/journald-gperf.gperf index 7fecd7a964..654fd76a4b 100644 --- a/src/journal/journald-gperf.gperf +++ b/src/journal/journald-gperf.gperf @@ -23,14 +23,14 @@ Journal.SyncIntervalSec, config_parse_sec, 0, offsetof(Server, sync_in Journal.RateLimitInterval, config_parse_sec, 0, offsetof(Server, rate_limit_interval) Journal.RateLimitIntervalSec,config_parse_sec, 0, offsetof(Server, rate_limit_interval) Journal.RateLimitBurst, config_parse_unsigned, 0, offsetof(Server, rate_limit_burst) -Journal.SystemMaxUse, config_parse_iec_uint64, 0, offsetof(Server, system_metrics.max_use) -Journal.SystemMaxFileSize, config_parse_iec_uint64, 0, offsetof(Server, system_metrics.max_size) -Journal.SystemKeepFree, config_parse_iec_uint64, 0, offsetof(Server, system_metrics.keep_free) -Journal.SystemMaxFiles, config_parse_uint64, 0, offsetof(Server, system_metrics.n_max_files) -Journal.RuntimeMaxUse, config_parse_iec_uint64, 0, offsetof(Server, runtime_metrics.max_use) -Journal.RuntimeMaxFileSize, config_parse_iec_uint64, 0, offsetof(Server, runtime_metrics.max_size) -Journal.RuntimeKeepFree, config_parse_iec_uint64, 0, offsetof(Server, runtime_metrics.keep_free) -Journal.RuntimeMaxFiles, config_parse_uint64, 0, offsetof(Server, runtime_metrics.n_max_files) +Journal.SystemMaxUse, config_parse_iec_uint64, 0, offsetof(Server, system_storage.metrics.max_use) +Journal.SystemMaxFileSize, config_parse_iec_uint64, 0, offsetof(Server, system_storage.metrics.max_size) +Journal.SystemKeepFree, config_parse_iec_uint64, 0, offsetof(Server, system_storage.metrics.keep_free) +Journal.SystemMaxFiles, config_parse_uint64, 0, offsetof(Server, system_storage.metrics.n_max_files) +Journal.RuntimeMaxUse, config_parse_iec_uint64, 0, offsetof(Server, runtime_storage.metrics.max_use) +Journal.RuntimeMaxFileSize, config_parse_iec_uint64, 0, offsetof(Server, runtime_storage.metrics.max_size) +Journal.RuntimeKeepFree, config_parse_iec_uint64, 0, offsetof(Server, runtime_storage.metrics.keep_free) +Journal.RuntimeMaxFiles, config_parse_uint64, 0, offsetof(Server, runtime_storage.metrics.n_max_files) Journal.MaxRetentionSec, config_parse_sec, 0, offsetof(Server, max_retention_usec) Journal.MaxFileSec, config_parse_sec, 0, offsetof(Server, max_file_usec) Journal.ForwardToSyslog, config_parse_bool, 0, offsetof(Server, forward_to_syslog) diff --git a/src/journal/journald-rate-limit.c b/src/journal/journald-rate-limit.c index fce799a6ce..f48639cf58 100644 --- a/src/journal/journald-rate-limit.c +++ b/src/journal/journald-rate-limit.c @@ -190,7 +190,7 @@ static unsigned burst_modulate(unsigned burst, uint64_t available) { if (k <= 20) return burst; - burst = (burst * (k-20)) / 4; + burst = (burst * (k-16)) / 4; /* * Example: @@ -261,7 +261,7 @@ int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, u return 1 + s; } - if (p->num <= burst) { + if (p->num < burst) { p->num++; return 1; } diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c index 587c343b31..908c7b8eeb 100644 --- a/src/journal/journald-server.c +++ b/src/journal/journald-server.c @@ -44,6 +44,7 @@ #include "fs-util.h" #include "hashmap.h" #include "hostname-util.h" +#include "id128-util.h" #include "io-util.h" #include "journal-authenticate.h" #include "journal-file.h" @@ -56,6 +57,7 @@ #include "journald-server.h" #include "journald-stream.h" #include "journald-syslog.h" +#include "log.h" #include "missing.h" #include "mkdir.h" #include "parse-util.h" @@ -69,7 +71,7 @@ #include "string-table.h" #include "string-util.h" #include "user-util.h" -#include "log.h" +#include "syslog-util.h" #define USER_JOURNALS_MAX 1024 @@ -85,48 +87,24 @@ /* The period to insert between posting changes for coalescing */ #define POST_CHANGE_TIMER_INTERVAL_USEC (250*USEC_PER_MSEC) -static int determine_space_for( - Server *s, - JournalMetrics *metrics, - const char *path, - const char *name, - bool verbose, - bool patch_min_use, - uint64_t *available, - uint64_t *limit) { - - uint64_t sum = 0, ss_avail, avail; +static int determine_path_usage(Server *s, const char *path, uint64_t *ret_used, uint64_t *ret_free) { _cleanup_closedir_ DIR *d = NULL; struct dirent *de; struct statvfs ss; - const char *p; - usec_t ts; - - assert(s); - assert(metrics); - assert(path); - assert(name); - - ts = now(CLOCK_MONOTONIC); - - if (!verbose && s->cached_space_timestamp + RECHECK_SPACE_USEC > ts) { - if (available) - *available = s->cached_space_available; - if (limit) - *limit = s->cached_space_limit; - - return 0; - } + assert(ret_used); + assert(ret_free); - p = strjoina(path, SERVER_MACHINE_ID(s)); - d = opendir(p); + d = opendir(path); if (!d) - return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, "Failed to open %s: %m", p); + return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, + errno, "Failed to open %s: %m", path); if (fstatvfs(dirfd(d), &ss) < 0) - return log_error_errno(errno, "Failed to fstatvfs(%s): %m", p); + return log_error_errno(errno, "Failed to fstatvfs(%s): %m", path); + *ret_free = ss.f_bsize * ss.f_bavail; + *ret_used = 0; FOREACH_DIRENT_ALL(de, d, break) { struct stat st; @@ -135,88 +113,125 @@ static int determine_space_for( continue; if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { - log_debug_errno(errno, "Failed to stat %s/%s, ignoring: %m", p, de->d_name); + log_debug_errno(errno, "Failed to stat %s/%s, ignoring: %m", path, de->d_name); continue; } if (!S_ISREG(st.st_mode)) continue; - sum += (uint64_t) st.st_blocks * 512UL; + *ret_used += (uint64_t) st.st_blocks * 512UL; } - /* If requested, then let's bump the min_use limit to the - * current usage on disk. We do this when starting up and - * first opening the journal files. This way sudden spikes in - * disk usage will not cause journald to vacuum files without - * bounds. Note that this means that only a restart of - * journald will make it reset this value. */ - - if (patch_min_use) - metrics->min_use = MAX(metrics->min_use, sum); - - ss_avail = ss.f_bsize * ss.f_bavail; - avail = LESS_BY(ss_avail, metrics->keep_free); - - s->cached_space_limit = MIN(MAX(sum + avail, metrics->min_use), metrics->max_use); - s->cached_space_available = LESS_BY(s->cached_space_limit, sum); - s->cached_space_timestamp = ts; - - if (verbose) { - char fb1[FORMAT_BYTES_MAX], fb2[FORMAT_BYTES_MAX], fb3[FORMAT_BYTES_MAX], - fb4[FORMAT_BYTES_MAX], fb5[FORMAT_BYTES_MAX], fb6[FORMAT_BYTES_MAX]; - format_bytes(fb1, sizeof(fb1), sum); - format_bytes(fb2, sizeof(fb2), metrics->max_use); - format_bytes(fb3, sizeof(fb3), metrics->keep_free); - format_bytes(fb4, sizeof(fb4), ss_avail); - format_bytes(fb5, sizeof(fb5), s->cached_space_limit); - format_bytes(fb6, sizeof(fb6), s->cached_space_available); - - server_driver_message(s, SD_MESSAGE_JOURNAL_USAGE, - LOG_MESSAGE("%s (%s) is %s, max %s, %s free.", - name, path, fb1, fb5, fb6), - "JOURNAL_NAME=%s", name, - "JOURNAL_PATH=%s", path, - "CURRENT_USE=%"PRIu64, sum, - "CURRENT_USE_PRETTY=%s", fb1, - "MAX_USE=%"PRIu64, metrics->max_use, - "MAX_USE_PRETTY=%s", fb2, - "DISK_KEEP_FREE=%"PRIu64, metrics->keep_free, - "DISK_KEEP_FREE_PRETTY=%s", fb3, - "DISK_AVAILABLE=%"PRIu64, ss_avail, - "DISK_AVAILABLE_PRETTY=%s", fb4, - "LIMIT=%"PRIu64, s->cached_space_limit, - "LIMIT_PRETTY=%s", fb5, - "AVAILABLE=%"PRIu64, s->cached_space_available, - "AVAILABLE_PRETTY=%s", fb6, - NULL); - } + return 0; +} - if (available) - *available = s->cached_space_available; - if (limit) - *limit = s->cached_space_limit; +static void cache_space_invalidate(JournalStorageSpace *space) { + memset(space, 0, sizeof(*space)); +} +static int cache_space_refresh(Server *s, JournalStorage *storage) { + JournalStorageSpace *space; + JournalMetrics *metrics; + uint64_t vfs_used, vfs_avail, avail; + usec_t ts; + int r; + + assert(s); + + metrics = &storage->metrics; + space = &storage->space; + + ts = now(CLOCK_MONOTONIC); + + if (space->timestamp + RECHECK_SPACE_USEC > ts) + return 0; + + r = determine_path_usage(s, storage->path, &vfs_used, &vfs_avail); + if (r < 0) + return r; + + space->vfs_used = vfs_used; + space->vfs_available = vfs_avail; + + avail = LESS_BY(vfs_avail, metrics->keep_free); + + space->limit = MIN(MAX(vfs_used + avail, metrics->min_use), metrics->max_use); + space->available = LESS_BY(space->limit, vfs_used); + space->timestamp = ts; return 1; } -static int determine_space(Server *s, bool verbose, bool patch_min_use, uint64_t *available, uint64_t *limit) { - JournalMetrics *metrics; - const char *path, *name; +static void patch_min_use(JournalStorage *storage) { + assert(storage); + + /* Let's bump the min_use limit to the current usage on disk. We do + * this when starting up and first opening the journal files. This way + * sudden spikes in disk usage will not cause journald to vacuum files + * without bounds. Note that this means that only a restart of journald + * will make it reset this value. */ + + storage->metrics.min_use = MAX(storage->metrics.min_use, storage->space.vfs_used); +} + + +static int determine_space(Server *s, uint64_t *available, uint64_t *limit) { + JournalStorage *js; + int r; assert(s); - if (s->system_journal) { - path = "/var/log/journal/"; - metrics = &s->system_metrics; - name = "System journal"; - } else { - path = "/run/log/journal/"; - metrics = &s->runtime_metrics; - name = "Runtime journal"; + js = s->system_journal ? &s->system_storage : &s->runtime_storage; + + r = cache_space_refresh(s, js); + if (r >= 0) { + if (available) + *available = js->space.available; + if (limit) + *limit = js->space.limit; } + return r; +} + +void server_space_usage_message(Server *s, JournalStorage *storage) { + char fb1[FORMAT_BYTES_MAX], fb2[FORMAT_BYTES_MAX], fb3[FORMAT_BYTES_MAX], + fb4[FORMAT_BYTES_MAX], fb5[FORMAT_BYTES_MAX], fb6[FORMAT_BYTES_MAX]; + JournalMetrics *metrics; + + assert(s); + + if (!storage) + storage = s->system_journal ? &s->system_storage : &s->runtime_storage; + + if (cache_space_refresh(s, storage) < 0) + return; - return determine_space_for(s, metrics, path, name, verbose, patch_min_use, available, limit); + metrics = &storage->metrics; + format_bytes(fb1, sizeof(fb1), storage->space.vfs_used); + format_bytes(fb2, sizeof(fb2), metrics->max_use); + format_bytes(fb3, sizeof(fb3), metrics->keep_free); + format_bytes(fb4, sizeof(fb4), storage->space.vfs_available); + format_bytes(fb5, sizeof(fb5), storage->space.limit); + format_bytes(fb6, sizeof(fb6), storage->space.available); + + server_driver_message(s, SD_MESSAGE_JOURNAL_USAGE, + LOG_MESSAGE("%s (%s) is %s, max %s, %s free.", + storage->name, storage->path, fb1, fb5, fb6), + "JOURNAL_NAME=%s", storage->name, + "JOURNAL_PATH=%s", storage->path, + "CURRENT_USE=%"PRIu64, storage->space.vfs_used, + "CURRENT_USE_PRETTY=%s", fb1, + "MAX_USE=%"PRIu64, metrics->max_use, + "MAX_USE_PRETTY=%s", fb2, + "DISK_KEEP_FREE=%"PRIu64, metrics->keep_free, + "DISK_KEEP_FREE_PRETTY=%s", fb3, + "DISK_AVAILABLE=%"PRIu64, storage->space.vfs_available, + "DISK_AVAILABLE_PRETTY=%s", fb4, + "LIMIT=%"PRIu64, storage->space.limit, + "LIMIT_PRETTY=%s", fb5, + "AVAILABLE=%"PRIu64, storage->space.available, + "AVAILABLE_PRETTY=%s", fb6, + NULL); } static void server_add_acls(JournalFile *f, uid_t uid) { @@ -267,6 +282,97 @@ static int open_journal( return r; } +static bool flushed_flag_is_set(void) { + return (access("/run/systemd/journal/flushed", F_OK) >= 0); +} + +static int system_journal_open(Server *s, bool flush_requested) { + bool flushed = false; + const char *fn; + int r = 0; + + if (!s->system_journal && + (s->storage == STORAGE_PERSISTENT || s->storage == STORAGE_AUTO) && + (flush_requested || (flushed = flushed_flag_is_set()))) { + + /* If in auto mode: first try to create the machine + * path, but not the prefix. + * + * If in persistent mode: create /var/log/journal and + * the machine path */ + + if (s->storage == STORAGE_PERSISTENT) + (void) mkdir_p("/var/log/journal/", 0755); + + (void) mkdir(s->system_storage.path, 0755); + + fn = strjoina(s->system_storage.path, "/system.journal"); + r = open_journal(s, true, fn, O_RDWR|O_CREAT, s->seal, &s->system_storage.metrics, &s->system_journal); + if (r >= 0) { + server_add_acls(s->system_journal, 0); + (void) cache_space_refresh(s, &s->system_storage); + patch_min_use(&s->system_storage); + } else if (r < 0) { + if (r != -ENOENT && r != -EROFS) + log_warning_errno(r, "Failed to open system journal: %m"); + + r = 0; + } + + /* If the runtime journal is open, and we're post-flush, we're + * recovering from a failed system journal rotate (ENOSPC) + * for which the runtime journal was reopened. + * + * Perform an implicit flush to var, leaving the runtime + * journal closed, now that the system journal is back. + */ + if (s->runtime_journal && flushed) + (void) server_flush_to_var(s); + } + + if (!s->runtime_journal && + (s->storage != STORAGE_NONE)) { + + fn = strjoina(s->runtime_storage.path, "/system.journal"); + + if (s->system_journal) { + + /* Try to open the runtime journal, but only + * if it already exists, so that we can flush + * it into the system journal */ + + r = open_journal(s, false, fn, O_RDWR, false, &s->runtime_storage.metrics, &s->runtime_journal); + if (r < 0) { + if (r != -ENOENT) + log_warning_errno(r, "Failed to open runtime journal: %m"); + + r = 0; + } + + } else { + + /* OK, we really need the runtime journal, so create + * it if necessary. */ + + (void) mkdir("/run/log", 0755); + (void) mkdir("/run/log/journal", 0755); + (void) mkdir_parents(fn, 0750); + + r = open_journal(s, true, fn, O_RDWR|O_CREAT, false, &s->runtime_storage.metrics, &s->runtime_journal); + if (r < 0) + return log_error_errno(r, "Failed to open runtime journal: %m"); + } + + if (s->runtime_journal) { + server_add_acls(s->runtime_journal, 0); + (void) cache_space_refresh(s, &s->runtime_storage); + patch_min_use(&s->runtime_storage); + } + } + + return r; +} + static JournalFile* find_journal(Server *s, uid_t uid) { _cleanup_free_ char *p = NULL; int r; @@ -275,6 +381,17 @@ static JournalFile* find_journal(Server *s, uid_t uid) { assert(s); + /* A rotate that fails to create the new journal (ENOSPC) leaves the + * rotated journal as NULL. Unless we revisit opening, even after + * space is made available we'll continue to return NULL indefinitely. + * + * system_journal_open() is a noop if the journals are already open, so + * we can just call it here to recover from failed rotates (or anything + * else that's left the journals as NULL). + * + * Fixes https://github.com/systemd/systemd/issues/3968 */ + (void) system_journal_open(s, false); + /* We split up user logs only on /var, not on /run. If the * runtime file is open, we write to it exclusively, in order * to guarantee proper order as soon as we flush /run to @@ -283,7 +400,7 @@ static JournalFile* find_journal(Server *s, uid_t uid) { if (s->runtime_journal) return s->runtime_journal; - if (uid <= SYSTEM_UID_MAX) + if (uid <= SYSTEM_UID_MAX || uid_is_dynamic(uid)) return s->system_journal; r = sd_id128_get_machine(&machine); @@ -305,7 +422,7 @@ static JournalFile* find_journal(Server *s, uid_t uid) { (void) journal_file_close(f); } - r = open_journal(s, true, p, O_RDWR|O_CREAT, s->seal, &s->system_metrics, &f); + r = open_journal(s, true, p, O_RDWR|O_CREAT, s->seal, &s->system_storage.metrics, &f); if (r < 0) return s->system_journal; @@ -399,50 +516,38 @@ void server_sync(Server *s) { s->sync_scheduled = false; } -static void do_vacuum( - Server *s, - JournalFile *f, - JournalMetrics *metrics, - const char *path, - const char *name, - bool verbose, - bool patch_min_use) { +static void do_vacuum(Server *s, JournalStorage *storage, bool verbose) { - const char *p; - uint64_t limit; int r; assert(s); - assert(metrics); - assert(path); - assert(name); + assert(storage); - if (!f) - return; - - p = strjoina(path, SERVER_MACHINE_ID(s)); + (void) cache_space_refresh(s, storage); - limit = metrics->max_use; - (void) determine_space_for(s, metrics, path, name, verbose, patch_min_use, NULL, &limit); + if (verbose) + server_space_usage_message(s, storage); - r = journal_directory_vacuum(p, limit, metrics->n_max_files, s->max_retention_usec, &s->oldest_file_usec, verbose); + r = journal_directory_vacuum(storage->path, storage->space.limit, + storage->metrics.n_max_files, s->max_retention_usec, + &s->oldest_file_usec, verbose); if (r < 0 && r != -ENOENT) - log_warning_errno(r, "Failed to vacuum %s, ignoring: %m", p); + log_warning_errno(r, "Failed to vacuum %s, ignoring: %m", storage->path); + + cache_space_invalidate(&storage->space); } -int server_vacuum(Server *s, bool verbose, bool patch_min_use) { +int server_vacuum(Server *s, bool verbose) { assert(s); log_debug("Vacuuming..."); s->oldest_file_usec = 0; - do_vacuum(s, s->system_journal, &s->system_metrics, "/var/log/journal/", "System journal", verbose, patch_min_use); - do_vacuum(s, s->runtime_journal, &s->runtime_metrics, "/run/log/journal/", "Runtime journal", verbose, patch_min_use); - - s->cached_space_limit = 0; - s->cached_space_available = 0; - s->cached_space_timestamp = 0; + if (s->system_journal) + do_vacuum(s, &s->system_storage, verbose); + if (s->runtime_journal) + do_vacuum(s, &s->runtime_storage, verbose); return 0; } @@ -493,54 +598,88 @@ static void server_cache_hostname(Server *s) { static bool shall_try_append_again(JournalFile *f, int r) { switch(r) { + case -E2BIG: /* Hit configured limit */ case -EFBIG: /* Hit fs limit */ case -EDQUOT: /* Quota limit hit */ case -ENOSPC: /* Disk full */ log_debug("%s: Allocation limit reached, rotating.", f->path); return true; + case -EIO: /* I/O error of some kind (mmap) */ log_warning("%s: IO error, rotating.", f->path); return true; + case -EHOSTDOWN: /* Other machine */ log_info("%s: Journal file from other machine, rotating.", f->path); return true; + case -EBUSY: /* Unclean shutdown */ log_info("%s: Unclean shutdown, rotating.", f->path); return true; + case -EPROTONOSUPPORT: /* Unsupported feature */ log_info("%s: Unsupported feature, rotating.", f->path); return true; + case -EBADMSG: /* Corrupted */ case -ENODATA: /* Truncated */ case -ESHUTDOWN: /* Already archived */ log_warning("%s: Journal file corrupted, rotating.", f->path); return true; + case -EIDRM: /* Journal file has been deleted */ log_warning("%s: Journal file has been deleted, rotating.", f->path); return true; + + case -ETXTBSY: /* Journal file is from the future */ + log_warning("%s: Journal file is from the future, rotating.", f->path); + return true; + default: return false; } } static void write_to_journal(Server *s, uid_t uid, struct iovec *iovec, unsigned n, int priority) { + bool vacuumed = false, rotate = false; + struct dual_timestamp ts; JournalFile *f; - bool vacuumed = false; int r; assert(s); assert(iovec); assert(n > 0); - f = find_journal(s, uid); - if (!f) - return; + /* Get the closest, linearized time we have for this log event from the event loop. (Note that we do not use + * the source time, and not even the time the event was originally seen, but instead simply the time we started + * processing it, as we want strictly linear ordering in what we write out.) */ + assert_se(sd_event_now(s->event, CLOCK_REALTIME, &ts.realtime) >= 0); + assert_se(sd_event_now(s->event, CLOCK_MONOTONIC, &ts.monotonic) >= 0); + + if (ts.realtime < s->last_realtime_clock) { + /* When the time jumps backwards, let's immediately rotate. Of course, this should not happen during + * regular operation. However, when it does happen, then we should make sure that we start fresh files + * to ensure that the entries in the journal files are strictly ordered by time, in order to ensure + * bisection works correctly. */ + + log_debug("Time jumped backwards, rotating."); + rotate = true; + } else { + + f = find_journal(s, uid); + if (!f) + return; + + if (journal_file_rotate_suggested(f, s->max_file_usec)) { + log_debug("%s: Journal header limits reached or header out-of-date, rotating.", f->path); + rotate = true; + } + } - if (journal_file_rotate_suggested(f, s->max_file_usec)) { - log_debug("%s: Journal header limits reached or header out-of-date, rotating.", f->path); + if (rotate) { server_rotate(s); - server_vacuum(s, false, false); + server_vacuum(s, false); vacuumed = true; f = find_journal(s, uid); @@ -548,7 +687,9 @@ static void write_to_journal(Server *s, uid_t uid, struct iovec *iovec, unsigned return; } - r = journal_file_append_entry(f, NULL, iovec, n, &s->seqnum, NULL, NULL); + s->last_realtime_clock = ts.realtime; + + r = journal_file_append_entry(f, &ts, iovec, n, &s->seqnum, NULL, NULL); if (r >= 0) { server_schedule_sync(s, priority); return; @@ -560,20 +701,58 @@ static void write_to_journal(Server *s, uid_t uid, struct iovec *iovec, unsigned } server_rotate(s); - server_vacuum(s, false, false); + server_vacuum(s, false); f = find_journal(s, uid); if (!f) return; log_debug("Retrying write."); - r = journal_file_append_entry(f, NULL, iovec, n, &s->seqnum, NULL, NULL); + r = journal_file_append_entry(f, &ts, iovec, n, &s->seqnum, NULL, NULL); if (r < 0) log_error_errno(r, "Failed to write entry (%d items, %zu bytes) despite vacuuming, ignoring: %m", n, IOVEC_TOTAL_SIZE(iovec, n)); else server_schedule_sync(s, priority); } +static int get_invocation_id(const char *cgroup_root, const char *slice, const char *unit, char **ret) { + _cleanup_free_ char *escaped = NULL, *slice_path = NULL, *p = NULL; + char *copy, ids[SD_ID128_STRING_MAX]; + int r; + + /* Read the invocation ID of a unit off a unit. It's stored in the "trusted.invocation_id" extended attribute + * on the cgroup path. */ + + r = cg_slice_to_path(slice, &slice_path); + if (r < 0) + return r; + + escaped = cg_escape(unit); + if (!escaped) + return -ENOMEM; + + p = strjoin(cgroup_root, "/", slice_path, "/", escaped, NULL); + if (!p) + return -ENOMEM; + + r = cg_get_xattr(SYSTEMD_CGROUP_CONTROLLER, p, "trusted.invocation_id", ids, 32); + if (r < 0) + return r; + if (r != 32) + return -EINVAL; + ids[32] = 0; + + if (!id128_is_valid(ids)) + return -EINVAL; + + copy = strdup(ids); + if (!copy) + return -ENOMEM; + + *ret = copy; + return 0; +} + static void dispatch_message_real( Server *s, struct iovec *iovec, unsigned n, unsigned m, @@ -612,7 +791,7 @@ static void dispatch_message_real( assert(s); assert(iovec); assert(n > 0); - assert(n + N_IOVEC_META_FIELDS + (object_pid ? N_IOVEC_OBJECT_FIELDS : 0) <= m); + assert(n + N_IOVEC_META_FIELDS + (object_pid > 0 ? N_IOVEC_OBJECT_FIELDS : 0) <= m); if (ucred) { realuid = ucred->uid; @@ -670,6 +849,7 @@ static void dispatch_message_real( r = cg_pid_get_path_shifted(ucred->pid, s->cgroup_root, &c); if (r >= 0) { + _cleanup_free_ char *raw_unit = NULL, *raw_slice = NULL; char *session = NULL; x = strjoina("_SYSTEMD_CGROUP=", c); @@ -689,9 +869,8 @@ static void dispatch_message_real( IOVEC_SET_STRING(iovec[n++], owner_uid); } - if (cg_path_get_unit(c, &t) >= 0) { - x = strjoina("_SYSTEMD_UNIT=", t); - free(t); + if (cg_path_get_unit(c, &raw_unit) >= 0) { + x = strjoina("_SYSTEMD_UNIT=", raw_unit); IOVEC_SET_STRING(iovec[n++], x); } else if (unit_id && !session) { x = strjoina("_SYSTEMD_UNIT=", unit_id); @@ -707,12 +886,25 @@ static void dispatch_message_real( IOVEC_SET_STRING(iovec[n++], x); } - if (cg_path_get_slice(c, &t) >= 0) { - x = strjoina("_SYSTEMD_SLICE=", t); + if (cg_path_get_slice(c, &raw_slice) >= 0) { + x = strjoina("_SYSTEMD_SLICE=", raw_slice); + IOVEC_SET_STRING(iovec[n++], x); + } + + if (cg_path_get_user_slice(c, &t) >= 0) { + x = strjoina("_SYSTEMD_USER_SLICE=", t); free(t); IOVEC_SET_STRING(iovec[n++], x); } + if (raw_slice && raw_unit) { + if (get_invocation_id(s->cgroup_root, raw_slice, raw_unit, &t) >= 0) { + x = strjoina("_SYSTEMD_INVOCATION_ID=", t); + free(t); + IOVEC_SET_STRING(iovec[n++], x); + } + } + free(c); } else if (unit_id) { x = strjoina("_SYSTEMD_UNIT=", unit_id); @@ -818,13 +1010,25 @@ static void dispatch_message_real( IOVEC_SET_STRING(iovec[n++], x); } + if (cg_path_get_slice(c, &t) >= 0) { + x = strjoina("OBJECT_SYSTEMD_SLICE=", t); + free(t); + IOVEC_SET_STRING(iovec[n++], x); + } + + if (cg_path_get_user_slice(c, &t) >= 0) { + x = strjoina("OBJECT_SYSTEMD_USER_SLICE=", t); + free(t); + IOVEC_SET_STRING(iovec[n++], x); + } + free(c); } } assert(n <= m); if (tv) { - sprintf(source_time, "_SOURCE_REALTIME_TIMESTAMP=%llu", (unsigned long long) timeval_load(tv)); + sprintf(source_time, "_SOURCE_REALTIME_TIMESTAMP=" USEC_FMT, timeval_load(tv)); IOVEC_SET_STRING(iovec[n++], source_time); } @@ -964,7 +1168,7 @@ void server_dispatch_message( } } - (void) determine_space(s, false, false, &available, NULL); + (void) determine_space(s, &available, NULL); rl = journal_rate_limit_test(s->rate_limit, path, priority & LOG_PRIMASK, available); if (rl == 0) return; @@ -979,83 +1183,6 @@ finish: dispatch_message_real(s, iovec, n, m, ucred, tv, label, label_len, unit_id, priority, object_pid); } - -static int system_journal_open(Server *s, bool flush_requested) { - const char *fn; - int r = 0; - - if (!s->system_journal && - (s->storage == STORAGE_PERSISTENT || s->storage == STORAGE_AUTO) && - (flush_requested - || access("/run/systemd/journal/flushed", F_OK) >= 0)) { - - /* If in auto mode: first try to create the machine - * path, but not the prefix. - * - * If in persistent mode: create /var/log/journal and - * the machine path */ - - if (s->storage == STORAGE_PERSISTENT) - (void) mkdir_p("/var/log/journal/", 0755); - - fn = strjoina("/var/log/journal/", SERVER_MACHINE_ID(s)); - (void) mkdir(fn, 0755); - - fn = strjoina(fn, "/system.journal"); - r = open_journal(s, true, fn, O_RDWR|O_CREAT, s->seal, &s->system_metrics, &s->system_journal); - if (r >= 0) { - server_add_acls(s->system_journal, 0); - (void) determine_space_for(s, &s->system_metrics, "/var/log/journal/", "System journal", true, true, NULL, NULL); - } else if (r < 0) { - if (r != -ENOENT && r != -EROFS) - log_warning_errno(r, "Failed to open system journal: %m"); - - r = 0; - } - } - - if (!s->runtime_journal && - (s->storage != STORAGE_NONE)) { - - fn = strjoina("/run/log/journal/", SERVER_MACHINE_ID(s), "/system.journal"); - - if (s->system_journal) { - - /* Try to open the runtime journal, but only - * if it already exists, so that we can flush - * it into the system journal */ - - r = open_journal(s, false, fn, O_RDWR, false, &s->runtime_metrics, &s->runtime_journal); - if (r < 0) { - if (r != -ENOENT) - log_warning_errno(r, "Failed to open runtime journal: %m"); - - r = 0; - } - - } else { - - /* OK, we really need the runtime journal, so create - * it if necessary. */ - - (void) mkdir("/run/log", 0755); - (void) mkdir("/run/log/journal", 0755); - (void) mkdir_parents(fn, 0750); - - r = open_journal(s, true, fn, O_RDWR|O_CREAT, false, &s->runtime_metrics, &s->runtime_journal); - if (r < 0) - return log_error_errno(r, "Failed to open runtime journal: %m"); - } - - if (s->runtime_journal) { - server_add_acls(s->runtime_journal, 0); - (void) determine_space_for(s, &s->runtime_metrics, "/run/log/journal/", "Runtime journal", true, true, NULL, NULL); - } - } - - return r; -} - int server_flush_to_var(Server *s) { sd_id128_t machine; sd_journal *j = NULL; @@ -1117,7 +1244,7 @@ int server_flush_to_var(Server *s) { } server_rotate(s); - server_vacuum(s, false, false); + server_vacuum(s, false); if (!s->system_journal) { log_notice("Didn't flush runtime journal since rotation of system journal wasn't successful."); @@ -1284,14 +1411,15 @@ static int dispatch_sigusr1(sd_event_source *es, const struct signalfd_siginfo * log_info("Received request to flush runtime journal from PID " PID_FMT, si->ssi_pid); - server_flush_to_var(s); + (void) server_flush_to_var(s); server_sync(s); - server_vacuum(s, false, false); + server_vacuum(s, false); r = touch("/run/systemd/journal/flushed"); if (r < 0) log_warning_errno(r, "Failed to touch /run/systemd/journal/flushed, ignoring: %m"); + server_space_usage_message(s, NULL); return 0; } @@ -1303,7 +1431,12 @@ static int dispatch_sigusr2(sd_event_source *es, const struct signalfd_siginfo * log_info("Received request to rotate journal from PID " PID_FMT, si->ssi_pid); server_rotate(s); - server_vacuum(s, true, true); + server_vacuum(s, true); + + if (s->system_journal) + patch_min_use(&s->system_storage); + if (s->runtime_journal) + patch_min_use(&s->runtime_storage); /* Let clients know when the most recent rotation happened. */ r = write_timestamp_file_atomic("/run/systemd/journal/rotated", now(CLOCK_MONOTONIC)); @@ -1393,55 +1526,68 @@ static int setup_signals(Server *s) { return 0; } -static int server_parse_proc_cmdline(Server *s) { - _cleanup_free_ char *line = NULL; - const char *p; +static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { + Server *s = data; int r; - r = proc_cmdline(&line); - if (r < 0) { - log_warning_errno(r, "Failed to read /proc/cmdline, ignoring: %m"); - return 0; - } - - p = line; - for (;;) { - _cleanup_free_ char *word = NULL; + assert(s); - r = extract_first_word(&p, &word, NULL, 0); + if (streq(key, "systemd.journald.forward_to_syslog")) { + r = value ? parse_boolean(value) : true; if (r < 0) - return log_error_errno(r, "Failed to parse journald syntax \"%s\": %m", line); - - if (r == 0) - break; - - if (startswith(word, "systemd.journald.forward_to_syslog=")) { - r = parse_boolean(word + 35); - if (r < 0) - log_warning("Failed to parse forward to syslog switch %s. Ignoring.", word + 35); - else - s->forward_to_syslog = r; - } else if (startswith(word, "systemd.journald.forward_to_kmsg=")) { - r = parse_boolean(word + 33); - if (r < 0) - log_warning("Failed to parse forward to kmsg switch %s. Ignoring.", word + 33); - else - s->forward_to_kmsg = r; - } else if (startswith(word, "systemd.journald.forward_to_console=")) { - r = parse_boolean(word + 36); - if (r < 0) - log_warning("Failed to parse forward to console switch %s. Ignoring.", word + 36); - else - s->forward_to_console = r; - } else if (startswith(word, "systemd.journald.forward_to_wall=")) { - r = parse_boolean(word + 33); - if (r < 0) - log_warning("Failed to parse forward to wall switch %s. Ignoring.", word + 33); - else - s->forward_to_wall = r; - } else if (startswith(word, "systemd.journald")) - log_warning("Invalid systemd.journald parameter. Ignoring."); - } + log_warning("Failed to parse forward to syslog switch \"%s\". Ignoring.", value); + else + s->forward_to_syslog = r; + } else if (streq(key, "systemd.journald.forward_to_kmsg")) { + r = value ? parse_boolean(value) : true; + if (r < 0) + log_warning("Failed to parse forward to kmsg switch \"%s\". Ignoring.", value); + else + s->forward_to_kmsg = r; + } else if (streq(key, "systemd.journald.forward_to_console")) { + r = value ? parse_boolean(value) : true; + if (r < 0) + log_warning("Failed to parse forward to console switch \"%s\". Ignoring.", value); + else + s->forward_to_console = r; + } else if (streq(key, "systemd.journald.forward_to_wall")) { + r = value ? parse_boolean(value) : true; + if (r < 0) + log_warning("Failed to parse forward to wall switch \"%s\". Ignoring.", value); + else + s->forward_to_wall = r; + } else if (streq(key, "systemd.journald.max_level_console") && value) { + r = log_level_from_string(value); + if (r < 0) + log_warning("Failed to parse max level console value \"%s\". Ignoring.", value); + else + s->max_level_console = r; + } else if (streq(key, "systemd.journald.max_level_store") && value) { + r = log_level_from_string(value); + if (r < 0) + log_warning("Failed to parse max level store value \"%s\". Ignoring.", value); + else + s->max_level_store = r; + } else if (streq(key, "systemd.journald.max_level_syslog") && value) { + r = log_level_from_string(value); + if (r < 0) + log_warning("Failed to parse max level syslog value \"%s\". Ignoring.", value); + else + s->max_level_syslog = r; + } else if (streq(key, "systemd.journald.max_level_kmsg") && value) { + r = log_level_from_string(value); + if (r < 0) + log_warning("Failed to parse max level kmsg value \"%s\". Ignoring.", value); + else + s->max_level_kmsg = r; + } else if (streq(key, "systemd.journald.max_level_wall") && value) { + r = log_level_from_string(value); + if (r < 0) + log_warning("Failed to parse max level wall value \"%s\". Ignoring.", value); + else + s->max_level_wall = r; + } else if (startswith(key, "systemd.journald")) + log_warning("Unknown journald kernel command line option \"%s\". Ignoring.", key); /* do not warn about state here, since probably systemd already did */ return 0; @@ -1450,7 +1596,7 @@ static int server_parse_proc_cmdline(Server *s) { static int server_parse_config_file(Server *s) { assert(s); - return config_parse_many(PKGSYSCONFDIR "/journald.conf", + return config_parse_many_nulstr(PKGSYSCONFDIR "/journald.conf", CONF_PATHS_NULSTR("systemd/journald.conf.d"), "Journal\0", config_item_perf_lookup, journald_gperf_lookup, @@ -1563,7 +1709,7 @@ static int dispatch_notify_event(sd_event_source *es, int fd, uint32_t revents, assert(s->notify_fd == fd); /* The $NOTIFY_SOCKET is writable again, now send exactly one - * message on it. Either it's the wtachdog event, the initial + * message on it. Either it's the watchdog event, the initial * READY=1 event or an stdout stream event. If there's nothing * to write anymore, turn our event source off. The next time * there's something to send it will be turned on again. */ @@ -1748,11 +1894,11 @@ int server_init(Server *s) { s->max_level_console = LOG_INFO; s->max_level_wall = LOG_EMERG; - journal_reset_metrics(&s->system_metrics); - journal_reset_metrics(&s->runtime_metrics); + journal_reset_metrics(&s->system_storage.metrics); + journal_reset_metrics(&s->runtime_storage.metrics); server_parse_config_file(s); - server_parse_proc_cmdline(s); + parse_proc_cmdline(parse_proc_cmdline_item, s, true); if (!!s->rate_limit_interval ^ !!s->rate_limit_burst) { log_debug("Setting both rate limit interval and burst from "USEC_FMT",%u to 0,0", @@ -1902,6 +2048,14 @@ int server_init(Server *s) { server_cache_boot_id(s); server_cache_machine_id(s); + s->runtime_storage.name = "Runtime journal"; + s->system_storage.name = "System journal"; + + s->runtime_storage.path = strjoin("/run/log/journal/", SERVER_MACHINE_ID(s), NULL); + s->system_storage.path = strjoin("/var/log/journal/", SERVER_MACHINE_ID(s), NULL); + if (!s->runtime_storage.path || !s->system_storage.path) + return -ENOMEM; + (void) server_connect_notify(s); return system_journal_open(s, false); diff --git a/src/journal/journald-server.h b/src/journal/journald-server.h index e025a4cf90..99d91496be 100644 --- a/src/journal/journald-server.h +++ b/src/journal/journald-server.h @@ -43,12 +43,30 @@ typedef enum Storage { typedef enum SplitMode { SPLIT_UID, - SPLIT_LOGIN, + SPLIT_LOGIN, /* deprecated */ SPLIT_NONE, _SPLIT_MAX, _SPLIT_INVALID = -1 } SplitMode; +typedef struct JournalStorageSpace { + usec_t timestamp; + + uint64_t available; + uint64_t limit; + + uint64_t vfs_used; /* space used by journal files */ + uint64_t vfs_available; +} JournalStorageSpace; + +typedef struct JournalStorage { + const char *name; + const char *path; + + JournalMetrics metrics; + JournalStorageSpace space; +} JournalStorage; + struct Server { int syslog_fd; int native_fd; @@ -89,8 +107,8 @@ struct Server { usec_t rate_limit_interval; unsigned rate_limit_burst; - JournalMetrics runtime_metrics; - JournalMetrics system_metrics; + JournalStorage runtime_storage; + JournalStorage system_storage; bool compress; bool seal; @@ -103,10 +121,6 @@ struct Server { unsigned n_forward_syslog_missed; usec_t last_warn_forward_syslog_missed; - uint64_t cached_space_available; - uint64_t cached_space_limit; - usec_t cached_space_timestamp; - uint64_t var_available_timestamp; usec_t max_retention_usec; @@ -149,14 +163,16 @@ struct Server { char *cgroup_root; usec_t watchdog_usec; + + usec_t last_realtime_clock; }; #define SERVER_MACHINE_ID(s) ((s)->machine_id_field + strlen("_MACHINE_ID=")) -#define N_IOVEC_META_FIELDS 20 +#define N_IOVEC_META_FIELDS 22 #define N_IOVEC_KERNEL_FIELDS 64 #define N_IOVEC_UDEV_FIELDS 32 -#define N_IOVEC_OBJECT_FIELDS 12 +#define N_IOVEC_OBJECT_FIELDS 14 #define N_IOVEC_PAYLOAD_FIELDS 15 void server_dispatch_message(Server *s, struct iovec *iovec, unsigned n, unsigned m, const struct ucred *ucred, const struct timeval *tv, const char *label, size_t label_len, const char *unit_id, int priority, pid_t object_pid); @@ -178,9 +194,10 @@ SplitMode split_mode_from_string(const char *s) _pure_; int server_init(Server *s); void server_done(Server *s); void server_sync(Server *s); -int server_vacuum(Server *s, bool verbose, bool patch_min_use); +int server_vacuum(Server *s, bool verbose); void server_rotate(Server *s); int server_schedule_sync(Server *s, int priority); int server_flush_to_var(Server *s); void server_maybe_append_tags(Server *s); int server_process_datagram(sd_event_source *es, int fd, uint32_t revents, void *userdata); +void server_space_usage_message(Server *s, JournalStorage *storage); diff --git a/src/journal/journald-stream.c b/src/journal/journald-stream.c index 4ad16ee41c..bc092f3c12 100644 --- a/src/journal/journald-stream.c +++ b/src/journal/journald-stream.c @@ -393,6 +393,9 @@ static int stdout_stream_scan(StdoutStream *s, bool force_flush) { p = s->buffer; remaining = s->length; + + /* XXX: This function does nothing if (s->length == 0) */ + for (;;) { char *end; size_t skip; diff --git a/src/journal/journald.c b/src/journal/journald.c index 272acb71c4..7f47ca22dd 100644 --- a/src/journal/journald.c +++ b/src/journal/journald.c @@ -51,7 +51,7 @@ int main(int argc, char *argv[]) { if (r < 0) goto finish; - server_vacuum(&server, false, false); + server_vacuum(&server, false); server_flush_to_var(&server); server_flush_dev_kmsg(&server); @@ -60,6 +60,11 @@ int main(int argc, char *argv[]) { LOG_MESSAGE("Journal started"), NULL); + /* Make sure to send the usage message *after* flushing the + * journal so entries from the runtime journals are ordered + * before this message. See #4190 for some details. */ + server_space_usage_message(&server, NULL); + for (;;) { usec_t t = USEC_INFINITY, n; @@ -77,7 +82,7 @@ int main(int argc, char *argv[]) { if (server.oldest_file_usec + server.max_retention_usec < n) { log_info("Retention time reached."); server_rotate(&server); - server_vacuum(&server, false, false); + server_vacuum(&server, false); continue; } diff --git a/src/journal/lookup3.c b/src/journal/lookup3.c index 3d791234f4..d8f1a4977d 100644 --- a/src/journal/lookup3.c +++ b/src/journal/lookup3.c @@ -317,7 +317,7 @@ uint32_t jenkins_hashlittle( const void *key, size_t length, uint32_t initval) * still catch it and complain. The masking trick does make the hash * noticeably faster for short strings (like English words). */ -#ifndef VALGRIND +#if !defined(VALGRIND) && !defined(__SANITIZE_ADDRESS__) switch(length) { @@ -503,7 +503,7 @@ void jenkins_hashlittle2( * still catch it and complain. The masking trick does make the hash * noticeably faster for short strings (like English words). */ -#ifndef VALGRIND +#if !defined(VALGRIND) && !defined(__SANITIZE_ADDRESS__) switch(length) { @@ -681,7 +681,7 @@ uint32_t jenkins_hashbig( const void *key, size_t length, uint32_t initval) * still catch it and complain. The masking trick does make the hash * noticeably faster for short strings (like English words). */ -#ifndef VALGRIND +#if !defined(VALGRIND) && !defined(__SANITIZE_ADDRESS__) switch(length) { diff --git a/src/journal/mmap-cache.c b/src/journal/mmap-cache.c index 293d27053a..d91247b524 100644 --- a/src/journal/mmap-cache.c +++ b/src/journal/mmap-cache.c @@ -325,10 +325,8 @@ static FileDescriptor* fd_add(MMapCache *m, int fd) { f->fd = fd; r = hashmap_put(m->fds, FD_TO_PTR(fd), f); - if (r < 0) { - free(f); - return NULL; - } + if (r < 0) + return mfree(f); return f; } diff --git a/src/journal/sd-journal.c b/src/journal/sd-journal.c index 75a0ffb49b..f2f8546086 100644 --- a/src/journal/sd-journal.c +++ b/src/journal/sd-journal.c @@ -387,7 +387,7 @@ _public_ int sd_journal_add_disjunction(sd_journal *j) { } static char *match_make_string(Match *m) { - char *p, *r; + char *p = NULL, *r; Match *i; bool enclose = false; @@ -397,15 +397,12 @@ static char *match_make_string(Match *m) { if (m->type == MATCH_DISCRETE) return strndup(m->data, m->size); - p = NULL; LIST_FOREACH(matches, i, m->matches) { char *t, *k; t = match_make_string(i); - if (!t) { - free(p); - return NULL; - } + if (!t) + return mfree(p); if (p) { k = strjoin(p, m->type == MATCH_OR_TERM ? " OR " : " AND ", t, NULL); @@ -1719,9 +1716,16 @@ static sd_journal *journal_new(int flags, const char *path) { j->data_threshold = DEFAULT_DATA_THRESHOLD; if (path) { - j->path = strdup(path); - if (!j->path) + char *t; + + t = strdup(path); + if (!t) goto fail; + + if (flags & SD_JOURNAL_OS_ROOT) + j->prefix = t; + else + j->path = t; } j->files = ordered_hashmap_new(&string_hash_ops); @@ -1737,12 +1741,17 @@ fail: return NULL; } +#define OPEN_ALLOWED_FLAGS \ + (SD_JOURNAL_LOCAL_ONLY | \ + SD_JOURNAL_RUNTIME_ONLY | \ + SD_JOURNAL_SYSTEM | SD_JOURNAL_CURRENT_USER) + _public_ int sd_journal_open(sd_journal **ret, int flags) { sd_journal *j; int r; assert_return(ret, -EINVAL); - assert_return((flags & ~(SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_RUNTIME_ONLY|SD_JOURNAL_SYSTEM|SD_JOURNAL_CURRENT_USER)) == 0, -EINVAL); + assert_return((flags & ~OPEN_ALLOWED_FLAGS) == 0, -EINVAL); j = journal_new(flags, NULL); if (!j) @@ -1761,6 +1770,9 @@ fail: return r; } +#define OPEN_CONTAINER_ALLOWED_FLAGS \ + (SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_SYSTEM) + _public_ int sd_journal_open_container(sd_journal **ret, const char *machine, int flags) { _cleanup_free_ char *root = NULL, *class = NULL; sd_journal *j; @@ -1772,7 +1784,7 @@ _public_ int sd_journal_open_container(sd_journal **ret, const char *machine, in assert_return(machine, -EINVAL); assert_return(ret, -EINVAL); - assert_return((flags & ~(SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM)) == 0, -EINVAL); + assert_return((flags & ~OPEN_CONTAINER_ALLOWED_FLAGS) == 0, -EINVAL); assert_return(machine_name_is_valid(machine), -EINVAL); p = strjoina("/run/systemd/machines/", machine); @@ -1787,13 +1799,10 @@ _public_ int sd_journal_open_container(sd_journal **ret, const char *machine, in if (!streq_ptr(class, "container")) return -EIO; - j = journal_new(flags, NULL); + j = journal_new(flags, root); if (!j) return -ENOMEM; - j->prefix = root; - root = NULL; - r = add_search_paths(j); if (r < 0) goto fail; @@ -1806,13 +1815,17 @@ fail: return r; } +#define OPEN_DIRECTORY_ALLOWED_FLAGS \ + (SD_JOURNAL_OS_ROOT | \ + SD_JOURNAL_SYSTEM | SD_JOURNAL_CURRENT_USER ) + _public_ int sd_journal_open_directory(sd_journal **ret, const char *path, int flags) { sd_journal *j; int r; assert_return(ret, -EINVAL); assert_return(path, -EINVAL); - assert_return((flags & ~SD_JOURNAL_OS_ROOT) == 0, -EINVAL); + assert_return((flags & ~OPEN_DIRECTORY_ALLOWED_FLAGS) == 0, -EINVAL); j = journal_new(flags, path); if (!j) @@ -1861,6 +1874,10 @@ fail: return r; } +#define OPEN_DIRECTORY_FD_ALLOWED_FLAGS \ + (SD_JOURNAL_OS_ROOT | \ + SD_JOURNAL_SYSTEM | SD_JOURNAL_CURRENT_USER ) + _public_ int sd_journal_open_directory_fd(sd_journal **ret, int fd, int flags) { sd_journal *j; struct stat st; @@ -1868,7 +1885,7 @@ _public_ int sd_journal_open_directory_fd(sd_journal **ret, int fd, int flags) { assert_return(ret, -EINVAL); assert_return(fd >= 0, -EBADF); - assert_return((flags & ~SD_JOURNAL_OS_ROOT) == 0, -EINVAL); + assert_return((flags & ~OPEN_DIRECTORY_FD_ALLOWED_FLAGS) == 0, -EINVAL); if (fstat(fd, &st) < 0) return -errno; @@ -2290,6 +2307,8 @@ _public_ int sd_journal_get_fd(sd_journal *j) { * inotify */ if (j->no_new_files) r = add_current_paths(j); + else if (j->flags & SD_JOURNAL_OS_ROOT) + r = add_search_paths(j); else if (j->toplevel_fd >= 0) r = add_root_directory(j, NULL, false); else if (j->path) diff --git a/src/journal/test-catalog.c b/src/journal/test-catalog.c index 898c876450..b7d9e7bffa 100644 --- a/src/journal/test-catalog.c +++ b/src/journal/test-catalog.c @@ -55,7 +55,7 @@ static Hashmap * test_import(const char* contents, ssize_t size, int code) { assert_se(h = hashmap_new(&catalog_hash_ops)); - fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(name); assert_se(fd >= 0); assert_se(write(fd, contents, size) == size); @@ -182,7 +182,7 @@ static void test_catalog_update(void) { static char name[] = "/tmp/test-catalog.XXXXXX"; int r; - r = mkostemp_safe(name, O_RDWR|O_CLOEXEC); + r = mkostemp_safe(name); assert_se(r >= 0); database = name; diff --git a/src/journal/test-compress.c b/src/journal/test-compress.c index 68c9a4d76c..72cadf1771 100644 --- a/src/journal/test-compress.c +++ b/src/journal/test-compress.c @@ -167,7 +167,7 @@ static void test_compress_stream(int compression, log_debug("/* test compression */"); - assert_se((dst = mkostemp_safe(pattern, O_RDWR|O_CLOEXEC)) >= 0); + assert_se((dst = mkostemp_safe(pattern)) >= 0); assert_se(compress(src, dst, -1) == 0); @@ -178,7 +178,7 @@ static void test_compress_stream(int compression, log_debug("/* test decompression */"); - assert_se((dst2 = mkostemp_safe(pattern2, O_RDWR|O_CLOEXEC)) >= 0); + assert_se((dst2 = mkostemp_safe(pattern2)) >= 0); assert_se(stat(srcfile, &st) == 0); @@ -247,6 +247,9 @@ int main(int argc, char *argv[]) { "text\0foofoofoofoo AAAA aaaaaaaaa ghost busters barbarbar FFF" "foofoofoofoo AAAA aaaaaaaaa ghost busters barbarbar FFF"; + /* The file to test compression on can be specified as the first argument */ + const char *srcfile = argc > 1 ? argv[1] : argv[0]; + char data[512] = "random\0"; char huge[4096*1024]; @@ -275,7 +278,7 @@ int main(int argc, char *argv[]) { huge, sizeof(huge), true); test_compress_stream(OBJECT_COMPRESSED_XZ, "xzcat", - compress_stream_xz, decompress_stream_xz, argv[0]); + compress_stream_xz, decompress_stream_xz, srcfile); #else log_info("/* XZ test skipped */"); #endif @@ -297,7 +300,7 @@ int main(int argc, char *argv[]) { huge, sizeof(huge), true); test_compress_stream(OBJECT_COMPRESSED_LZ4, "lz4cat", - compress_stream_lz4, decompress_stream_lz4, argv[0]); + compress_stream_lz4, decompress_stream_lz4, srcfile); test_lz4_decompress_partial(); #else diff --git a/src/journal/test-journal-interleaving.c b/src/journal/test-journal-interleaving.c index 5e063f4d04..35cae23bf8 100644 --- a/src/journal/test-journal-interleaving.c +++ b/src/journal/test-journal-interleaving.c @@ -36,10 +36,9 @@ static bool arg_keep = false; -noreturn static void log_assert_errno(const char *text, int eno, const char *file, int line, const char *func) { - log_internal(LOG_CRIT, 0, file, line, func, - "'%s' failed at %s:%u (%s): %s.", - text, file, line, func, strerror(eno)); +noreturn static void log_assert_errno(const char *text, int error, const char *file, int line, const char *func) { + log_internal(LOG_CRIT, error, file, line, func, + "'%s' failed at %s:%u (%s): %m", text, file, line, func); abort(); } diff --git a/src/journal/test-mmap-cache.c b/src/journal/test-mmap-cache.c index 009aabf55e..0ad49aeb5f 100644 --- a/src/journal/test-mmap-cache.c +++ b/src/journal/test-mmap-cache.c @@ -36,15 +36,15 @@ int main(int argc, char *argv[]) { assert_se(m = mmap_cache_new()); - x = mkostemp_safe(px, O_RDWR|O_CLOEXEC); + x = mkostemp_safe(px); assert_se(x >= 0); unlink(px); - y = mkostemp_safe(py, O_RDWR|O_CLOEXEC); + y = mkostemp_safe(py); assert_se(y >= 0); unlink(py); - z = mkostemp_safe(pz, O_RDWR|O_CLOEXEC); + z = mkostemp_safe(pz); assert_se(z >= 0); unlink(pz); diff --git a/src/kernel-install/kernel-install b/src/kernel-install/kernel-install index 1159dc384d..0c0ee718ac 100644 --- a/src/kernel-install/kernel-install +++ b/src/kernel-install/kernel-install @@ -19,6 +19,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with systemd; If not, see <http://www.gnu.org/licenses/>. +SKIP_REMAINING=77 + usage() { echo "Usage:" @@ -86,10 +88,15 @@ if [[ ! $COMMAND ]] || [[ ! $KERNEL_VERSION ]]; then exit 1 fi -if [[ -d /boot/loader/entries ]] || [[ -d /boot/$MACHINE_ID ]]; then +if [[ -d /efi/loader/entries ]] || [[ -d /efi/$MACHINE_ID ]]; then + BOOT_DIR_ABS="/efi/$MACHINE_ID/$KERNEL_VERSION" +elif [[ -d /boot/loader/entries ]] || [[ -d /boot/$MACHINE_ID ]]; then BOOT_DIR_ABS="/boot/$MACHINE_ID/$KERNEL_VERSION" -elif [[ -d /boot/efi/loader/entries ]] || [[ -d /boot/efi/$MACHINE_ID ]] \ - || mountpoint -q /boot/efi; then +elif [[ -d /boot/efi/loader/entries ]] || [[ -d /boot/efi/$MACHINE_ID ]]; then + BOOT_DIR_ABS="/boot/efi/$MACHINE_ID/$KERNEL_VERSION" +elif mountpoint -q /efi; then + BOOT_DIR_ABS="/efi/$MACHINE_ID/$KERNEL_VERSION" +elif mountpoint -q /boot/efi; then BOOT_DIR_ABS="/boot/efi/$MACHINE_ID/$KERNEL_VERSION" else BOOT_DIR_ABS="/boot/$MACHINE_ID/$KERNEL_VERSION" @@ -118,7 +125,11 @@ case $COMMAND in for f in "${PLUGINS[@]}"; do if [[ -x $f ]]; then "$f" add "$KERNEL_VERSION" "$BOOT_DIR_ABS" "$KERNEL_IMAGE" - ((ret+=$?)) + x=$? + if [[ $x == $SKIP_REMAINING ]]; then + return 0 + fi + ((ret+=$x)) fi done ;; @@ -127,7 +138,11 @@ case $COMMAND in for f in "${PLUGINS[@]}"; do if [[ -x $f ]]; then "$f" remove "$KERNEL_VERSION" "$BOOT_DIR_ABS" - ((ret+=$?)) + x=$? + if [[ $x == $SKIP_REMAINING ]]; then + return 0 + fi + ((ret+=$x)) fi done diff --git a/src/libsystemd-network/ndisc-router.c b/src/libsystemd-network/ndisc-router.c index d9950b638c..41ff2b353a 100644 --- a/src/libsystemd-network/ndisc-router.c +++ b/src/libsystemd-network/ndisc-router.c @@ -49,8 +49,7 @@ _public_ sd_ndisc_router* sd_ndisc_router_unref(sd_ndisc_router *rt) { if (rt->n_ref > 0) return NULL; - free(rt); - return NULL; + return mfree(rt); } sd_ndisc_router *ndisc_router_new(size_t raw_size) { diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 179e5950bd..5ccb23922c 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -1873,9 +1873,7 @@ sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client) { free(client->req_opts); free(client->hostname); free(client->vendor_class_identifier); - free(client); - - return NULL; + return mfree(client); } int sd_dhcp_client_new(sd_dhcp_client **ret) { diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index ef50ed17a1..8387b185c0 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -282,9 +282,7 @@ sd_dhcp_lease *sd_dhcp_lease_unref(sd_dhcp_lease *lease) { free(lease->static_route); free(lease->client_id); free(lease->vendor_specific); - free(lease); - - return NULL; + return mfree(lease); } static int lease_parse_u32(const uint8_t *option, size_t len, uint32_t *ret, uint32_t min) { diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index 11ee2e252e..f16314a37f 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -178,9 +178,7 @@ sd_dhcp_server *sd_dhcp_server_unref(sd_dhcp_server *server) { hashmap_free(server->leases_by_client_id); free(server->bound_leases); - free(server); - - return NULL; + return mfree(server); } int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) { diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index 463fde401c..e81215f7d7 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -1300,9 +1300,7 @@ sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client) { sd_dhcp6_client_detach_event(client); free(client->req_opts); - free(client); - - return NULL; + return mfree(client); } int sd_dhcp6_client_new(sd_dhcp6_client **ret) { diff --git a/src/libsystemd-network/sd-dhcp6-lease.c b/src/libsystemd-network/sd-dhcp6-lease.c index 5c10a6326a..ab59977a3f 100644 --- a/src/libsystemd-network/sd-dhcp6-lease.c +++ b/src/libsystemd-network/sd-dhcp6-lease.c @@ -389,9 +389,7 @@ sd_dhcp6_lease *sd_dhcp6_lease_unref(sd_dhcp6_lease *lease) { free(lease->ntp); lease->ntp_fqdn = strv_free(lease->ntp_fqdn); - free(lease); - - return NULL; + return mfree(lease); } int dhcp6_lease_new(sd_dhcp6_lease **ret) { diff --git a/src/libsystemd-network/sd-ipv4acd.c b/src/libsystemd-network/sd-ipv4acd.c index 662885840f..4dd343c101 100644 --- a/src/libsystemd-network/sd-ipv4acd.c +++ b/src/libsystemd-network/sd-ipv4acd.c @@ -135,9 +135,7 @@ sd_ipv4acd *sd_ipv4acd_unref(sd_ipv4acd *acd) { ipv4acd_reset(acd); sd_ipv4acd_detach_event(acd); - free(acd); - - return NULL; + return mfree(acd); } int sd_ipv4acd_new(sd_ipv4acd **ret) { diff --git a/src/libsystemd-network/sd-ipv4ll.c b/src/libsystemd-network/sd-ipv4ll.c index 5603a533a5..13209261f9 100644 --- a/src/libsystemd-network/sd-ipv4ll.c +++ b/src/libsystemd-network/sd-ipv4ll.c @@ -90,9 +90,7 @@ sd_ipv4ll *sd_ipv4ll_unref(sd_ipv4ll *ll) { return NULL; sd_ipv4acd_unref(ll->acd); - free(ll); - - return NULL; + return mfree(ll); } int sd_ipv4ll_new(sd_ipv4ll **ret) { diff --git a/src/libsystemd-network/sd-lldp.c b/src/libsystemd-network/sd-lldp.c index 0bd1e66aa0..0702241506 100644 --- a/src/libsystemd-network/sd-lldp.c +++ b/src/libsystemd-network/sd-lldp.c @@ -374,9 +374,7 @@ _public_ sd_lldp* sd_lldp_unref(sd_lldp *lldp) { hashmap_free(lldp->neighbor_by_id); prioq_free(lldp->neighbor_by_expiry); - free(lldp); - - return NULL; + return mfree(lldp); } _public_ int sd_lldp_new(sd_lldp **ret) { diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c index 07b0d7f704..1d3be9b862 100644 --- a/src/libsystemd-network/sd-ndisc.c +++ b/src/libsystemd-network/sd-ndisc.c @@ -148,9 +148,7 @@ _public_ sd_ndisc *sd_ndisc_unref(sd_ndisc *nd) { ndisc_reset(nd); sd_ndisc_detach_event(nd); - free(nd); - - return NULL; + return mfree(nd); } _public_ int sd_ndisc_new(sd_ndisc **ret) { diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym index 542254295c..d48ef6bbe2 100644 --- a/src/libsystemd/libsystemd.sym +++ b/src/libsystemd/libsystemd.sym @@ -500,3 +500,14 @@ LIBSYSTEMD_231 { global: sd_event_get_iteration; } LIBSYSTEMD_230; + +LIBSYSTEMD_232 { +global: + sd_bus_track_set_recursive; + sd_bus_track_get_recursive; + sd_bus_track_count_name; + sd_bus_track_count_sender; + sd_bus_set_exit_on_disconnect; + sd_bus_get_exit_on_disconnect; + sd_id128_get_invocation; +} LIBSYSTEMD_231; diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c index 02e3bf904c..d2a826bf6e 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.c +++ b/src/libsystemd/sd-bus/bus-common-errors.c @@ -27,6 +27,7 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = { SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_UNIT, ENOENT), SD_BUS_ERROR_MAP(BUS_ERROR_NO_UNIT_FOR_PID, ESRCH), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_UNIT_FOR_INVOCATION_ID, ENOENT), SD_BUS_ERROR_MAP(BUS_ERROR_UNIT_EXISTS, EEXIST), SD_BUS_ERROR_MAP(BUS_ERROR_LOAD_FAILED, EIO), SD_BUS_ERROR_MAP(BUS_ERROR_JOB_FAILED, EREMOTEIO), @@ -44,12 +45,16 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = { SD_BUS_ERROR_MAP(BUS_ERROR_NO_ISOLATION, EPERM), SD_BUS_ERROR_MAP(BUS_ERROR_SHUTTING_DOWN, ECANCELED), SD_BUS_ERROR_MAP(BUS_ERROR_SCOPE_NOT_RUNNING, EHOSTDOWN), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_DYNAMIC_USER, ESRCH), + SD_BUS_ERROR_MAP(BUS_ERROR_NOT_REFERENCED, EUNATCH), SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_MACHINE, ENXIO), SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_IMAGE, ENOENT), SD_BUS_ERROR_MAP(BUS_ERROR_NO_MACHINE_FOR_PID, ENXIO), SD_BUS_ERROR_MAP(BUS_ERROR_MACHINE_EXISTS, EEXIST), SD_BUS_ERROR_MAP(BUS_ERROR_NO_PRIVATE_NETWORKING, ENOSYS), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_USER_MAPPING, ENXIO), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_GROUP_MAPPING, ENXIO), SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_SESSION, ENXIO), SD_BUS_ERROR_MAP(BUS_ERROR_NO_SESSION_FOR_PID, ENXIO), @@ -62,6 +67,7 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = { SD_BUS_ERROR_MAP(BUS_ERROR_DEVICE_NOT_TAKEN, EINVAL), SD_BUS_ERROR_MAP(BUS_ERROR_OPERATION_IN_PROGRESS, EINPROGRESS), SD_BUS_ERROR_MAP(BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, EOPNOTSUPP), + SD_BUS_ERROR_MAP(BUS_ERROR_SESSION_BUSY, EBUSY), SD_BUS_ERROR_MAP(BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, EALREADY), @@ -80,6 +86,25 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = { SD_BUS_ERROR_MAP(BUS_ERROR_LINK_BUSY, EBUSY), SD_BUS_ERROR_MAP(BUS_ERROR_NETWORK_DOWN, ENETDOWN), + SD_BUS_ERROR_MAP(_BUS_ERROR_DNS "FORMERR", EBADMSG), + SD_BUS_ERROR_MAP(_BUS_ERROR_DNS "SERVFAIL", EHOSTDOWN), + SD_BUS_ERROR_MAP(_BUS_ERROR_DNS "NXDOMAIN", ENXIO), + SD_BUS_ERROR_MAP(_BUS_ERROR_DNS "NOTIMP", ENOSYS), + SD_BUS_ERROR_MAP(_BUS_ERROR_DNS "REFUSED", EACCES), + SD_BUS_ERROR_MAP(_BUS_ERROR_DNS "YXDOMAIN", EEXIST), + SD_BUS_ERROR_MAP(_BUS_ERROR_DNS "YRRSET", EEXIST), + SD_BUS_ERROR_MAP(_BUS_ERROR_DNS "NXRRSET", ENOENT), + SD_BUS_ERROR_MAP(_BUS_ERROR_DNS "NOTAUTH", EACCES), + SD_BUS_ERROR_MAP(_BUS_ERROR_DNS "NOTZONE", EREMOTE), + SD_BUS_ERROR_MAP(_BUS_ERROR_DNS "BADVERS", EBADMSG), + SD_BUS_ERROR_MAP(_BUS_ERROR_DNS "BADKEY", EKEYREJECTED), + SD_BUS_ERROR_MAP(_BUS_ERROR_DNS "BADTIME", EBADMSG), + SD_BUS_ERROR_MAP(_BUS_ERROR_DNS "BADMODE", EBADMSG), + SD_BUS_ERROR_MAP(_BUS_ERROR_DNS "BADNAME", EBADMSG), + SD_BUS_ERROR_MAP(_BUS_ERROR_DNS "BADALG", EBADMSG), + SD_BUS_ERROR_MAP(_BUS_ERROR_DNS "BADTRUNC", EBADMSG), + SD_BUS_ERROR_MAP(_BUS_ERROR_DNS "BADCOOKIE", EBADR), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_TRANSFER, ENXIO), SD_BUS_ERROR_MAP(BUS_ERROR_TRANSFER_IN_PROGRESS, EBUSY), diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h index c8f369cb78..525b79fa77 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.h +++ b/src/libsystemd/sd-bus/bus-common-errors.h @@ -23,6 +23,7 @@ #define BUS_ERROR_NO_SUCH_UNIT "org.freedesktop.systemd1.NoSuchUnit" #define BUS_ERROR_NO_UNIT_FOR_PID "org.freedesktop.systemd1.NoUnitForPID" +#define BUS_ERROR_NO_UNIT_FOR_INVOCATION_ID "org.freedesktop.systemd1.NoUnitForInvocationID" #define BUS_ERROR_UNIT_EXISTS "org.freedesktop.systemd1.UnitExists" #define BUS_ERROR_LOAD_FAILED "org.freedesktop.systemd1.LoadFailed" #define BUS_ERROR_JOB_FAILED "org.freedesktop.systemd1.JobFailed" @@ -40,6 +41,8 @@ #define BUS_ERROR_NO_ISOLATION "org.freedesktop.systemd1.NoIsolation" #define BUS_ERROR_SHUTTING_DOWN "org.freedesktop.systemd1.ShuttingDown" #define BUS_ERROR_SCOPE_NOT_RUNNING "org.freedesktop.systemd1.ScopeNotRunning" +#define BUS_ERROR_NO_SUCH_DYNAMIC_USER "org.freedesktop.systemd1.NoSuchDynamicUser" +#define BUS_ERROR_NOT_REFERENCED "org.freedesktop.systemd1.NotReferenced" #define BUS_ERROR_NO_SUCH_MACHINE "org.freedesktop.machine1.NoSuchMachine" #define BUS_ERROR_NO_SUCH_IMAGE "org.freedesktop.machine1.NoSuchImage" diff --git a/src/libsystemd/sd-bus/bus-error.c b/src/libsystemd/sd-bus/bus-error.c index 26219bdeed..378f7a377a 100644 --- a/src/libsystemd/sd-bus/bus-error.c +++ b/src/libsystemd/sd-bus/bus-error.c @@ -70,11 +70,9 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_standard_errors[] = { SD_BUS_ERROR_MAP_END }; -/* GCC maps this magically to the beginning and end of the BUS_ERROR_MAP section. - * Hide them; for currently unknown reasons they get exported to the shared libries - * even without being listed in the sym file. */ -extern const sd_bus_error_map __start_BUS_ERROR_MAP[] _hidden_; -extern const sd_bus_error_map __stop_BUS_ERROR_MAP[] _hidden_; +/* GCC maps this magically to the beginning and end of the BUS_ERROR_MAP section */ +extern const sd_bus_error_map __start_BUS_ERROR_MAP[]; +extern const sd_bus_error_map __stop_BUS_ERROR_MAP[]; /* Additional maps registered with sd_bus_error_add_map() are in this * NULL terminated array */ diff --git a/src/libsystemd/sd-bus/bus-internal.h b/src/libsystemd/sd-bus/bus-internal.h index 216d9f62bc..bb0414c4d6 100644 --- a/src/libsystemd/sd-bus/bus-internal.h +++ b/src/libsystemd/sd-bus/bus-internal.h @@ -209,6 +209,9 @@ struct sd_bus { bool is_system:1; bool is_user:1; bool allow_interactive_authorization:1; + bool exit_on_disconnect:1; + bool exited:1; + bool exit_triggered:1; int use_memfd; @@ -320,12 +323,13 @@ struct sd_bus { sd_bus_track *track_queue; LIST_HEAD(sd_bus_slot, slots); + LIST_HEAD(sd_bus_track, tracks); }; #define BUS_DEFAULT_TIMEOUT ((usec_t) (25 * USEC_PER_SEC)) -#define BUS_WQUEUE_MAX 1024 -#define BUS_RQUEUE_MAX 64*1024 +#define BUS_WQUEUE_MAX (192*1024) +#define BUS_RQUEUE_MAX (192*1024) #define BUS_MESSAGE_SIZE_MAX (64*1024*1024) #define BUS_AUTH_SIZE_MAX (64*1024) diff --git a/src/libsystemd/sd-bus/bus-slot.c b/src/libsystemd/sd-bus/bus-slot.c index 8e9074c7df..33590c31ac 100644 --- a/src/libsystemd/sd-bus/bus-slot.c +++ b/src/libsystemd/sd-bus/bus-slot.c @@ -212,9 +212,7 @@ _public_ sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot) { bus_slot_disconnect(slot); free(slot->description); - free(slot); - - return NULL; + return mfree(slot); } _public_ sd_bus* sd_bus_slot_get_bus(sd_bus_slot *slot) { diff --git a/src/libsystemd/sd-bus/bus-track.c b/src/libsystemd/sd-bus/bus-track.c index 1f436fe560..4acaf24793 100644 --- a/src/libsystemd/sd-bus/bus-track.c +++ b/src/libsystemd/sd-bus/bus-track.c @@ -24,16 +24,27 @@ #include "bus-track.h" #include "bus-util.h" +struct track_item { + unsigned n_ref; + char *name; + sd_bus_slot *slot; +}; + struct sd_bus_track { unsigned n_ref; + unsigned n_adding; /* are we in the process of adding a new name? */ sd_bus *bus; sd_bus_track_handler_t handler; void *userdata; Hashmap *names; LIST_FIELDS(sd_bus_track, queue); Iterator iterator; - bool in_queue; - bool modified; + bool in_list:1; /* In bus->tracks? */ + bool in_queue:1; /* In bus->track_queue? */ + bool modified:1; + bool recursive:1; + + LIST_FIELDS(sd_bus_track, tracks); }; #define MATCH_PREFIX \ @@ -56,15 +67,45 @@ struct sd_bus_track { _x; \ }) +static struct track_item* track_item_free(struct track_item *i) { + + if (!i) + return NULL; + + sd_bus_slot_unref(i->slot); + free(i->name); + return mfree(i); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct track_item*, track_item_free); + static void bus_track_add_to_queue(sd_bus_track *track) { assert(track); + /* Adds the bus track object to the queue of objects we should dispatch next, subject to a number of + * conditions. */ + + /* Already in the queue? */ if (track->in_queue) return; + /* if we are currently in the process of adding a new name, then let's not enqueue this just yet, let's wait + * until the addition is complete. */ + if (track->n_adding > 0) + return; + + /* still referenced? */ + if (hashmap_size(track->names) > 0) + return; + + /* Nothing to call? */ if (!track->handler) return; + /* Already closed? */ + if (!track->in_list) + return; + LIST_PREPEND(queue, track->bus->track_queue, track); track->in_queue = true; } @@ -79,6 +120,24 @@ static void bus_track_remove_from_queue(sd_bus_track *track) { track->in_queue = false; } +static int bus_track_remove_name_fully(sd_bus_track *track, const char *name) { + struct track_item *i; + + assert(track); + assert(name); + + i = hashmap_remove(track->names, name); + if (!i) + return 0; + + track_item_free(i); + + bus_track_add_to_queue(track); + + track->modified = true; + return 1; +} + _public_ int sd_bus_track_new( sd_bus *bus, sd_bus_track **track, @@ -102,6 +161,9 @@ _public_ int sd_bus_track_new( t->userdata = userdata; t->bus = sd_bus_ref(bus); + LIST_PREPEND(tracks, bus->tracks, t); + t->in_list = true; + bus_track_add_to_queue(t); *track = t; @@ -121,7 +183,7 @@ _public_ sd_bus_track* sd_bus_track_ref(sd_bus_track *track) { } _public_ sd_bus_track* sd_bus_track_unref(sd_bus_track *track) { - const char *n; + struct track_item *i; if (!track) return NULL; @@ -133,15 +195,16 @@ _public_ sd_bus_track* sd_bus_track_unref(sd_bus_track *track) { return NULL; } - while ((n = hashmap_first_key(track->names))) - sd_bus_track_remove_name(track, n); + while ((i = hashmap_steal_first(track->names))) + track_item_free(i); + + if (track->in_list) + LIST_REMOVE(tracks, track->bus->tracks, track); bus_track_remove_from_queue(track); hashmap_free(track->names); sd_bus_unref(track->bus); - free(track); - - return NULL; + return mfree(track); } static int on_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) { @@ -156,49 +219,76 @@ static int on_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus if (r < 0) return 0; - sd_bus_track_remove_name(track, name); + bus_track_remove_name_fully(track, name); return 0; } _public_ int sd_bus_track_add_name(sd_bus_track *track, const char *name) { - _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; - _cleanup_free_ char *n = NULL; + _cleanup_(track_item_freep) struct track_item *n = NULL; + struct track_item *i; const char *match; int r; assert_return(track, -EINVAL); assert_return(service_name_is_valid(name), -EINVAL); + i = hashmap_get(track->names, name); + if (i) { + if (track->recursive) { + unsigned k = track->n_ref + 1; + + if (k < track->n_ref) /* Check for overflow */ + return -EOVERFLOW; + + track->n_ref = k; + } + + bus_track_remove_from_queue(track); + return 0; + } + r = hashmap_ensure_allocated(&track->names, &string_hash_ops); if (r < 0) return r; - n = strdup(name); + n = new0(struct track_item, 1); if (!n) return -ENOMEM; + n->name = strdup(name); + if (!n->name) + return -ENOMEM; /* First, subscribe to this name */ - match = MATCH_FOR_NAME(n); - r = sd_bus_add_match(track->bus, &slot, match, on_name_owner_changed, track); - if (r < 0) + match = MATCH_FOR_NAME(name); + + bus_track_remove_from_queue(track); /* don't dispatch this while we work in it */ + + track->n_adding++; /* make sure we aren't dispatched while we synchronously add this match */ + r = sd_bus_add_match(track->bus, &n->slot, match, on_name_owner_changed, track); + track->n_adding--; + if (r < 0) { + bus_track_add_to_queue(track); return r; + } - r = hashmap_put(track->names, n, slot); - if (r == -EEXIST) - return 0; - if (r < 0) + r = hashmap_put(track->names, n->name, n); + if (r < 0) { + bus_track_add_to_queue(track); return r; + } - /* Second, check if it is currently existing, or maybe - * doesn't, or maybe disappeared already. */ - r = sd_bus_get_name_creds(track->bus, n, 0, NULL); + /* Second, check if it is currently existing, or maybe doesn't, or maybe disappeared already. */ + track->n_adding++; /* again, make sure this isn't dispatch while we are working in it */ + r = sd_bus_get_name_creds(track->bus, name, 0, NULL); + track->n_adding--; if (r < 0) { - hashmap_remove(track->names, n); + hashmap_remove(track->names, name); + bus_track_add_to_queue(track); return r; } + n->n_ref = 1; n = NULL; - slot = NULL; bus_track_remove_from_queue(track); track->modified = true; @@ -207,37 +297,48 @@ _public_ int sd_bus_track_add_name(sd_bus_track *track, const char *name) { } _public_ int sd_bus_track_remove_name(sd_bus_track *track, const char *name) { - _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; - _cleanup_free_ char *n = NULL; + struct track_item *i; assert_return(name, -EINVAL); - if (!track) + if (!track) /* Treat a NULL track object as an empty track object */ return 0; - slot = hashmap_remove2(track->names, (char*) name, (void**) &n); - if (!slot) - return 0; + if (!track->recursive) + return bus_track_remove_name_fully(track, name); - if (hashmap_isempty(track->names)) - bus_track_add_to_queue(track); + i = hashmap_get(track->names, name); + if (!i) + return -EUNATCH; + if (i->n_ref <= 0) + return -EUNATCH; - track->modified = true; + i->n_ref--; + + if (i->n_ref <= 0) + return bus_track_remove_name_fully(track, name); return 1; } _public_ unsigned sd_bus_track_count(sd_bus_track *track) { - if (!track) + + if (!track) /* Let's consider a NULL object equivalent to an empty object */ return 0; + /* This signature really should have returned an int, so that we can propagate errors. But well, ... Also, note + * that this returns the number of names being watched, and multiple references to the same name are not + * counted. */ + return hashmap_size(track->names); } _public_ const char* sd_bus_track_contains(sd_bus_track *track, const char *name) { - assert_return(track, NULL); assert_return(name, NULL); + if (!track) /* Let's consider a NULL object equivalent to an empty object */ + return NULL; + return hashmap_get(track->names, (void*) name) ? name : NULL; } @@ -273,6 +374,9 @@ _public_ int sd_bus_track_add_sender(sd_bus_track *track, sd_bus_message *m) { assert_return(track, -EINVAL); assert_return(m, -EINVAL); + if (sd_bus_message_get_bus(m) != track->bus) + return -EINVAL; + sender = sd_bus_message_get_sender(m); if (!sender) return -EINVAL; @@ -283,9 +387,14 @@ _public_ int sd_bus_track_add_sender(sd_bus_track *track, sd_bus_message *m) { _public_ int sd_bus_track_remove_sender(sd_bus_track *track, sd_bus_message *m) { const char *sender; - assert_return(track, -EINVAL); assert_return(m, -EINVAL); + if (!track) /* Treat a NULL track object as an empty track object */ + return 0; + + if (sd_bus_message_get_bus(m) != track->bus) + return -EINVAL; + sender = sd_bus_message_get_sender(m); if (!sender) return -EINVAL; @@ -303,7 +412,6 @@ void bus_track_dispatch(sd_bus_track *track) { int r; assert(track); - assert(track->in_queue); assert(track->handler); bus_track_remove_from_queue(track); @@ -319,6 +427,34 @@ void bus_track_dispatch(sd_bus_track *track) { sd_bus_track_unref(track); } +void bus_track_close(sd_bus_track *track) { + struct track_item *i; + + assert(track); + + /* Called whenever our bus connected is closed. If so, and our track object is non-empty, dispatch it + * immediately, as we are closing now, but first flush out all names. */ + + if (!track->in_list) + return; /* We already closed this one, don't close it again. */ + + /* Remember that this one is closed now */ + LIST_REMOVE(tracks, track->bus->tracks, track); + track->in_list = false; + + /* If there's no name in this one anyway, we don't have to dispatch */ + if (hashmap_isempty(track->names)) + return; + + /* Let's flush out all names */ + while ((i = hashmap_steal_first(track->names))) + track_item_free(i); + + /* Invoke handler */ + if (track->handler) + bus_track_dispatch(track); +} + _public_ void *sd_bus_track_get_userdata(sd_bus_track *track) { assert_return(track, NULL); @@ -335,3 +471,55 @@ _public_ void *sd_bus_track_set_userdata(sd_bus_track *track, void *userdata) { return ret; } + +_public_ int sd_bus_track_set_recursive(sd_bus_track *track, int b) { + assert_return(track, -EINVAL); + + if (track->recursive == !!b) + return 0; + + if (!hashmap_isempty(track->names)) + return -EBUSY; + + track->recursive = b; + return 0; +} + +_public_ int sd_bus_track_get_recursive(sd_bus_track *track) { + assert_return(track, -EINVAL); + + return track->recursive; +} + +_public_ int sd_bus_track_count_sender(sd_bus_track *track, sd_bus_message *m) { + const char *sender; + + assert_return(m, -EINVAL); + + if (!track) /* Let's consider a NULL object equivalent to an empty object */ + return 0; + + if (sd_bus_message_get_bus(m) != track->bus) + return -EINVAL; + + sender = sd_bus_message_get_sender(m); + if (!sender) + return -EINVAL; + + return sd_bus_track_count_name(track, sender); +} + +_public_ int sd_bus_track_count_name(sd_bus_track *track, const char *name) { + struct track_item *i; + + assert_return(service_name_is_valid(name), -EINVAL); + + if (!track) /* Let's consider a NULL object equivalent to an empty object */ + return 0; + + i = hashmap_get(track->names, name); + if (!i) + return 0; + + return i->n_ref; +} diff --git a/src/libsystemd/sd-bus/bus-track.h b/src/libsystemd/sd-bus/bus-track.h index 7d93a727d6..26bd05f5c7 100644 --- a/src/libsystemd/sd-bus/bus-track.h +++ b/src/libsystemd/sd-bus/bus-track.h @@ -20,3 +20,4 @@ ***/ void bus_track_dispatch(sd_bus_track *track); +void bus_track_close(sd_bus_track *track); diff --git a/src/libsystemd/sd-bus/busctl.c b/src/libsystemd/sd-bus/busctl.c index eb042e9c81..2c3f591053 100644 --- a/src/libsystemd/sd-bus/busctl.c +++ b/src/libsystemd/sd-bus/busctl.c @@ -2003,8 +2003,7 @@ int main(int argc, char *argv[]) { goto finish; } - if (streq_ptr(argv[optind], "monitor") || - streq_ptr(argv[optind], "capture")) { + if (STRPTR_IN_SET(argv[optind], "monitor", "capture")) { r = sd_bus_set_monitor(bus, true); if (r < 0) { diff --git a/src/libsystemd/sd-bus/sd-bus.c b/src/libsystemd/sd-bus/sd-bus.c index ed5f94e136..d746348544 100644 --- a/src/libsystemd/sd-bus/sd-bus.c +++ b/src/libsystemd/sd-bus/sd-bus.c @@ -107,6 +107,7 @@ static void bus_free(sd_bus *b) { assert(b); assert(!b->track_queue); + assert(!b->tracks); b->state = BUS_CLOSED; @@ -2640,62 +2641,101 @@ null_message: return r; } -static int process_closing(sd_bus *bus, sd_bus_message **ret) { +static int bus_exit_now(sd_bus *bus) { + assert(bus); + + /* Exit due to close, if this is requested. If this is bus object is attached to an event source, invokes + * sd_event_exit(), otherwise invokes libc exit(). */ + + if (bus->exited) /* did we already exit? */ + return 0; + if (!bus->exit_triggered) /* was the exit condition triggered? */ + return 0; + if (!bus->exit_on_disconnect) /* Shall we actually exit on disconnection? */ + return 0; + + bus->exited = true; /* never exit more than once */ + + log_debug("Bus connection disconnected, exiting."); + + if (bus->event) + return sd_event_exit(bus->event, EXIT_FAILURE); + else + exit(EXIT_FAILURE); + + assert_not_reached("exit() didn't exit?"); +} + +static int process_closing_reply_callback(sd_bus *bus, struct reply_callback *c) { + _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - struct reply_callback *c; + sd_bus_slot *slot; int r; assert(bus); - assert(bus->state == BUS_CLOSING); + assert(c); - c = ordered_hashmap_first(bus->reply_callbacks); - if (c) { - _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; - sd_bus_slot *slot; + r = bus_message_new_synthetic_error( + bus, + c->cookie, + &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NO_REPLY, "Connection terminated"), + &m); + if (r < 0) + return r; - /* First, fail all outstanding method calls */ - r = bus_message_new_synthetic_error( - bus, - c->cookie, - &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NO_REPLY, "Connection terminated"), - &m); - if (r < 0) - return r; + r = bus_seal_synthetic_message(bus, m); + if (r < 0) + return r; - r = bus_seal_synthetic_message(bus, m); - if (r < 0) - return r; + if (c->timeout != 0) { + prioq_remove(bus->reply_callbacks_prioq, c, &c->prioq_idx); + c->timeout = 0; + } - if (c->timeout != 0) { - prioq_remove(bus->reply_callbacks_prioq, c, &c->prioq_idx); - c->timeout = 0; - } + ordered_hashmap_remove(bus->reply_callbacks, &c->cookie); + c->cookie = 0; - ordered_hashmap_remove(bus->reply_callbacks, &c->cookie); - c->cookie = 0; + slot = container_of(c, sd_bus_slot, reply_callback); - slot = container_of(c, sd_bus_slot, reply_callback); + bus->iteration_counter++; - bus->iteration_counter++; + bus->current_message = m; + bus->current_slot = sd_bus_slot_ref(slot); + bus->current_handler = c->callback; + bus->current_userdata = slot->userdata; + r = c->callback(m, slot->userdata, &error_buffer); + bus->current_userdata = NULL; + bus->current_handler = NULL; + bus->current_slot = NULL; + bus->current_message = NULL; - bus->current_message = m; - bus->current_slot = sd_bus_slot_ref(slot); - bus->current_handler = c->callback; - bus->current_userdata = slot->userdata; - r = c->callback(m, slot->userdata, &error_buffer); - bus->current_userdata = NULL; - bus->current_handler = NULL; - bus->current_slot = NULL; - bus->current_message = NULL; + if (slot->floating) { + bus_slot_disconnect(slot); + sd_bus_slot_unref(slot); + } - if (slot->floating) { - bus_slot_disconnect(slot); - sd_bus_slot_unref(slot); - } + sd_bus_slot_unref(slot); - sd_bus_slot_unref(slot); + return bus_maybe_reply_error(m, r, &error_buffer); +} - return bus_maybe_reply_error(m, r, &error_buffer); +static int process_closing(sd_bus *bus, sd_bus_message **ret) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + struct reply_callback *c; + int r; + + assert(bus); + assert(bus->state == BUS_CLOSING); + + /* First, fail all outstanding method calls */ + c = ordered_hashmap_first(bus->reply_callbacks); + if (c) + return process_closing_reply_callback(bus, c); + + /* Then, fake-drop all remaining bus tracking references */ + if (bus->tracks) { + bus_track_close(bus->tracks); + return 1; } /* Then, synthesize a Disconnected message */ @@ -2727,6 +2767,10 @@ static int process_closing(sd_bus *bus, sd_bus_message **ret) { if (r != 0) goto finish; + /* Nothing else to do, exit now, if the condition holds */ + bus->exit_triggered = true; + (void) bus_exit_now(bus); + if (ret) { *ret = m; m = NULL; @@ -3789,3 +3833,21 @@ _public_ void sd_bus_default_flush_close(void) { flush_close(default_user_bus); flush_close(default_system_bus); } + +_public_ int sd_bus_set_exit_on_disconnect(sd_bus *bus, int b) { + assert_return(bus, -EINVAL); + + /* Turns on exit-on-disconnect, and triggers it immediately if the bus connection was already + * disconnected. Note that this is triggered exclusively on disconnections triggered by the server side, never + * from the client side. */ + bus->exit_on_disconnect = b; + + /* If the exit condition was triggered already, exit immediately. */ + return bus_exit_now(bus); +} + +_public_ int sd_bus_get_exit_on_disconnect(sd_bus *bus) { + assert_return(bus, -EINVAL); + + return bus->exit_on_disconnect; +} diff --git a/src/libsystemd/sd-bus/test-bus-chat.c b/src/libsystemd/sd-bus/test-bus-chat.c index 048c0d19e2..fc60830059 100644 --- a/src/libsystemd/sd-bus/test-bus-chat.c +++ b/src/libsystemd/sd-bus/test-bus-chat.c @@ -351,7 +351,7 @@ finish: static int quit_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { bool *x = userdata; - log_error("Quit callback: %s", strerror(sd_bus_message_get_errno(m))); + log_error_errno(sd_bus_message_get_errno(m), "Quit callback: %m"); *x = 1; return 1; diff --git a/src/libsystemd/sd-bus/test-bus-creds.c b/src/libsystemd/sd-bus/test-bus-creds.c index e9ef483bdd..6fdcfa4128 100644 --- a/src/libsystemd/sd-bus/test-bus-creds.c +++ b/src/libsystemd/sd-bus/test-bus-creds.c @@ -27,12 +27,17 @@ int main(int argc, char *argv[]) { _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; int r; - if (cg_unified() == -ENOMEDIUM) { - puts("Skipping test: /sys/fs/cgroup/ not available"); + log_set_max_level(LOG_DEBUG); + log_parse_environment(); + log_open(); + + if (cg_all_unified() == -ENOMEDIUM) { + log_info("Skipping test: /sys/fs/cgroup/ not available"); return EXIT_TEST_SKIP; } r = sd_bus_creds_new_from_pid(&creds, 0, _SD_BUS_CREDS_ALL); + log_full_errno(r < 0 ? LOG_ERR : LOG_DEBUG, r, "sd_bus_creds_new_from_pid: %m"); assert_se(r >= 0); bus_creds_dump(creds, NULL, true); diff --git a/src/libsystemd/sd-bus/test-bus-track.c b/src/libsystemd/sd-bus/test-bus-track.c new file mode 100644 index 0000000000..4beb61f05a --- /dev/null +++ b/src/libsystemd/sd-bus/test-bus-track.c @@ -0,0 +1,113 @@ +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sd-bus.h> + +#include "macro.h" + +static bool track_cb_called_x = false; +static bool track_cb_called_y = false; + +static int track_cb_x(sd_bus_track *t, void *userdata) { + + log_error("TRACK CB X"); + + assert_se(!track_cb_called_x); + track_cb_called_x = true; + + /* This means b's name disappeared. Let's now disconnect, to make sure the track handling on disconnect works + * as it should. */ + + assert_se(shutdown(sd_bus_get_fd(sd_bus_track_get_bus(t)), SHUT_RDWR) >= 0); + return 1; +} + +static int track_cb_y(sd_bus_track *t, void *userdata) { + int r; + + log_error("TRACK CB Y"); + + assert_se(!track_cb_called_y); + track_cb_called_y = true; + + /* We got disconnected, let's close everything */ + + r = sd_event_exit(sd_bus_get_event(sd_bus_track_get_bus(t)), EXIT_SUCCESS); + assert_se(r >= 0); + + return 0; +} + +int main(int argc, char *argv[]) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(sd_bus_track_unrefp) sd_bus_track *x = NULL, *y = NULL; + _cleanup_(sd_bus_unrefp) sd_bus *a = NULL, *b = NULL; + const char *unique; + int r; + + r = sd_event_default(&event); + assert_se(r >= 0); + + r = sd_bus_open_system(&a); + if (IN_SET(r, -ECONNREFUSED, -ENOENT)) { + log_info("Failed to connect to bus, skipping tests."); + return EXIT_TEST_SKIP; + } + assert_se(r >= 0); + + r = sd_bus_attach_event(a, event, SD_EVENT_PRIORITY_NORMAL); + assert_se(r >= 0); + + r = sd_bus_open_system(&b); + assert_se(r >= 0); + + r = sd_bus_attach_event(b, event, SD_EVENT_PRIORITY_NORMAL); + assert_se(r >= 0); + + /* Watch b's name from a */ + r = sd_bus_track_new(a, &x, track_cb_x, NULL); + assert_se(r >= 0); + + r = sd_bus_get_unique_name(b, &unique); + assert_se(r >= 0); + + r = sd_bus_track_add_name(x, unique); + assert_se(r >= 0); + + /* Watch's a's own name from a */ + r = sd_bus_track_new(a, &y, track_cb_y, NULL); + assert_se(r >= 0); + + r = sd_bus_get_unique_name(a, &unique); + assert_se(r >= 0); + + r = sd_bus_track_add_name(y, unique); + assert_se(r >= 0); + + /* Now make b's name disappear */ + sd_bus_close(b); + + r = sd_event_loop(event); + assert_se(r >= 0); + + assert_se(track_cb_called_x); + assert_se(track_cb_called_y); + + return 0; +} diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c index 0c4ad966bd..411453e08d 100644 --- a/src/libsystemd/sd-device/sd-device.c +++ b/src/libsystemd/sd-device/sd-device.c @@ -36,6 +36,7 @@ #include "parse-util.h" #include "path-util.h" #include "set.h" +#include "socket-util.h" #include "stat-util.h" #include "string-util.h" #include "strv.h" @@ -629,9 +630,9 @@ _public_ int sd_device_new_from_device_id(sd_device **ret, const char *id) { if (r < 0) return r; - sk = socket(PF_INET, SOCK_DGRAM, 0); + sk = socket_ioctl_fd(); if (sk < 0) - return -errno; + return sk; r = ioctl(sk, SIOCGIFNAME, &ifr); if (r < 0) diff --git a/src/libsystemd/sd-hwdb/hwdb-internal.h b/src/libsystemd/sd-hwdb/hwdb-internal.h index 8ffb5e5c74..4fff94ec76 100644 --- a/src/libsystemd/sd-hwdb/hwdb-internal.h +++ b/src/libsystemd/sd-hwdb/hwdb-internal.h @@ -70,3 +70,11 @@ struct trie_value_entry_f { le64_t key_off; le64_t value_off; } _packed_; + +/* v2 extends v1 with filename and line-number */ +struct trie_value_entry2_f { + le64_t key_off; + le64_t value_off; + le64_t filename_off; + le64_t line_number; +} _packed_; diff --git a/src/libsystemd/sd-hwdb/sd-hwdb.c b/src/libsystemd/sd-hwdb/sd-hwdb.c index 062fa97b17..488e101ea8 100644 --- a/src/libsystemd/sd-hwdb/sd-hwdb.c +++ b/src/libsystemd/sd-hwdb/sd-hwdb.c @@ -97,15 +97,20 @@ static void linebuf_rem_char(struct linebuf *buf) { linebuf_rem(buf, 1); } -static const struct trie_child_entry_f *trie_node_children(sd_hwdb *hwdb, const struct trie_node_f *node) { - return (const struct trie_child_entry_f *)((const char *)node + le64toh(hwdb->head->node_size)); +static const struct trie_child_entry_f *trie_node_child(sd_hwdb *hwdb, const struct trie_node_f *node, size_t idx) { + const char *base = (const char *)node; + + base += le64toh(hwdb->head->node_size); + base += idx * le64toh(hwdb->head->child_entry_size); + return (const struct trie_child_entry_f *)base; } -static const struct trie_value_entry_f *trie_node_values(sd_hwdb *hwdb, const struct trie_node_f *node) { +static const struct trie_value_entry_f *trie_node_value(sd_hwdb *hwdb, const struct trie_node_f *node, size_t idx) { const char *base = (const char *)node; base += le64toh(hwdb->head->node_size); base += node->children_count * le64toh(hwdb->head->child_entry_size); + base += idx * le64toh(hwdb->head->value_entry_size); return (const struct trie_value_entry_f *)base; } @@ -129,19 +134,20 @@ static const struct trie_node_f *node_lookup_f(sd_hwdb *hwdb, const struct trie_ struct trie_child_entry_f search; search.c = c; - child = bsearch(&search, trie_node_children(hwdb, node), node->children_count, + child = bsearch(&search, (const char *)node + le64toh(hwdb->head->node_size), node->children_count, le64toh(hwdb->head->child_entry_size), trie_children_cmp_f); if (child) return trie_node_from_off(hwdb, child->child_off); return NULL; } -static int hwdb_add_property(sd_hwdb *hwdb, const char *key, const char *value) { +static int hwdb_add_property(sd_hwdb *hwdb, const struct trie_value_entry_f *entry) { + const char *key; int r; assert(hwdb); - assert(key); - assert(value); + + key = trie_string(hwdb, entry->key_off); /* * Silently ignore all properties which do not start with a @@ -152,11 +158,25 @@ static int hwdb_add_property(sd_hwdb *hwdb, const char *key, const char *value) key++; + if (le64toh(hwdb->head->value_entry_size) >= sizeof(struct trie_value_entry2_f)) { + const struct trie_value_entry2_f *old, *entry2; + + entry2 = (const struct trie_value_entry2_f *)entry; + old = ordered_hashmap_get(hwdb->properties, key); + if (old) { + /* on duplicates, we order by filename and line-number */ + r = strcmp(trie_string(hwdb, entry2->filename_off), trie_string(hwdb, old->filename_off)); + if (r < 0 || + (r == 0 && entry2->line_number < old->line_number)) + return 0; + } + } + r = ordered_hashmap_ensure_allocated(&hwdb->properties, &string_hash_ops); if (r < 0) return r; - r = ordered_hashmap_replace(hwdb->properties, key, (char*)value); + r = ordered_hashmap_replace(hwdb->properties, key, (void *)entry); if (r < 0) return r; @@ -177,7 +197,7 @@ static int trie_fnmatch_f(sd_hwdb *hwdb, const struct trie_node_f *node, size_t linebuf_add(buf, prefix + p, len); for (i = 0; i < node->children_count; i++) { - const struct trie_child_entry_f *child = &trie_node_children(hwdb, node)[i]; + const struct trie_child_entry_f *child = trie_node_child(hwdb, node, i); linebuf_add_char(buf, child->c); err = trie_fnmatch_f(hwdb, trie_node_from_off(hwdb, child->child_off), 0, buf, search); @@ -188,8 +208,7 @@ static int trie_fnmatch_f(sd_hwdb *hwdb, const struct trie_node_f *node, size_t if (le64toh(node->values_count) && fnmatch(linebuf_get(buf), search, 0) == 0) for (i = 0; i < le64toh(node->values_count); i++) { - err = hwdb_add_property(hwdb, trie_string(hwdb, trie_node_values(hwdb, node)[i].key_off), - trie_string(hwdb, trie_node_values(hwdb, node)[i].value_off)); + err = hwdb_add_property(hwdb, trie_node_value(hwdb, node, i)); if (err < 0) return err; } @@ -254,8 +273,7 @@ static int trie_search_f(sd_hwdb *hwdb, const char *search) { size_t n; for (n = 0; n < le64toh(node->values_count); n++) { - err = hwdb_add_property(hwdb, trie_string(hwdb, trie_node_values(hwdb, node)[n].key_off), - trie_string(hwdb, trie_node_values(hwdb, node)[n].value_off)); + err = hwdb_add_property(hwdb, trie_node_value(hwdb, node, n)); if (err < 0) return err; } @@ -410,7 +428,7 @@ static int properties_prepare(sd_hwdb *hwdb, const char *modalias) { } _public_ int sd_hwdb_get(sd_hwdb *hwdb, const char *modalias, const char *key, const char **_value) { - const char *value; + const struct trie_value_entry_f *entry; int r; assert_return(hwdb, -EINVAL); @@ -422,11 +440,11 @@ _public_ int sd_hwdb_get(sd_hwdb *hwdb, const char *modalias, const char *key, c if (r < 0) return r; - value = ordered_hashmap_get(hwdb->properties, key); - if (!value) + entry = ordered_hashmap_get(hwdb->properties, key); + if (!entry) return -ENOENT; - *_value = value; + *_value = trie_string(hwdb, entry->value_off); return 0; } @@ -449,8 +467,8 @@ _public_ int sd_hwdb_seek(sd_hwdb *hwdb, const char *modalias) { } _public_ int sd_hwdb_enumerate(sd_hwdb *hwdb, const char **key, const char **value) { + const struct trie_value_entry_f *entry; const void *k; - void *v; assert_return(hwdb, -EINVAL); assert_return(key, -EINVAL); @@ -459,12 +477,12 @@ _public_ int sd_hwdb_enumerate(sd_hwdb *hwdb, const char **key, const char **val if (hwdb->properties_modified) return -EAGAIN; - ordered_hashmap_iterate(hwdb->properties, &hwdb->properties_iterator, &v, &k); + ordered_hashmap_iterate(hwdb->properties, &hwdb->properties_iterator, (void **)&entry, &k); if (!k) return 0; *key = k; - *value = v; + *value = trie_string(hwdb, entry->value_off); return 1; } diff --git a/src/libsystemd/sd-id128/id128-util.c b/src/libsystemd/sd-id128/id128-util.c index c3f527d657..337eae24b4 100644 --- a/src/libsystemd/sd-id128/id128-util.c +++ b/src/libsystemd/sd-id128/id128-util.c @@ -192,3 +192,16 @@ int id128_write(const char *p, Id128Format f, sd_id128_t id, bool do_sync) { return id128_write_fd(fd, f, id, do_sync); } + +void id128_hash_func(const void *p, struct siphash *state) { + siphash24_compress(p, 16, state); +} + +int id128_compare_func(const void *a, const void *b) { + return memcmp(a, b, 16); +} + +const struct hash_ops id128_hash_ops = { + .hash = id128_hash_func, + .compare = id128_compare_func, +}; diff --git a/src/libsystemd/sd-id128/id128-util.h b/src/libsystemd/sd-id128/id128-util.h index 3ba59acbca..6b3855acbb 100644 --- a/src/libsystemd/sd-id128/id128-util.h +++ b/src/libsystemd/sd-id128/id128-util.h @@ -22,6 +22,8 @@ #include <stdbool.h> #include "sd-id128.h" + +#include "hash-funcs.h" #include "macro.h" char *id128_to_uuid_string(sd_id128_t id, char s[37]); @@ -43,3 +45,7 @@ int id128_read(const char *p, Id128Format f, sd_id128_t *ret); int id128_write_fd(int fd, Id128Format f, sd_id128_t id, bool do_sync); int id128_write(const char *p, Id128Format f, sd_id128_t id, bool do_sync); + +void id128_hash_func(const void *p, struct siphash *state); +int id128_compare_func(const void *a, const void *b) _pure_; +extern const struct hash_ops id128_hash_ops; diff --git a/src/libsystemd/sd-id128/sd-id128.c b/src/libsystemd/sd-id128/sd-id128.c index 9f47d04e61..d4450c70a0 100644 --- a/src/libsystemd/sd-id128/sd-id128.c +++ b/src/libsystemd/sd-id128/sd-id128.c @@ -129,6 +129,28 @@ _public_ int sd_id128_get_boot(sd_id128_t *ret) { return 0; } +_public_ int sd_id128_get_invocation(sd_id128_t *ret) { + static thread_local sd_id128_t saved_invocation_id = {}; + int r; + + assert_return(ret, -EINVAL); + + if (sd_id128_is_null(saved_invocation_id)) { + const char *e; + + e = secure_getenv("INVOCATION_ID"); + if (!e) + return -ENXIO; + + r = sd_id128_from_string(e, &saved_invocation_id); + if (r < 0) + return r; + } + + *ret = saved_invocation_id; + return 0; +} + static sd_id128_t make_v4_uuid(sd_id128_t id) { /* Stolen from generate_random_uuid() of drivers/char/random.c * in the kernel sources */ diff --git a/src/libsystemd/sd-netlink/netlink-types.c b/src/libsystemd/sd-netlink/netlink-types.c index 566a050432..1c10dd55a7 100644 --- a/src/libsystemd/sd-netlink/netlink-types.c +++ b/src/libsystemd/sd-netlink/netlink-types.c @@ -21,6 +21,7 @@ #include <sys/socket.h> #include <linux/netlink.h> #include <linux/rtnetlink.h> +#include <linux/can/netlink.h> #include <linux/in6.h> #include <linux/veth.h> #include <linux/if_bridge.h> @@ -303,49 +304,48 @@ static const char* const nl_union_link_info_data_table[] = { [NL_UNION_LINK_INFO_DATA_VTI6_TUNNEL] = "vti6", [NL_UNION_LINK_INFO_DATA_IP6TNL_TUNNEL] = "ip6tnl", [NL_UNION_LINK_INFO_DATA_VRF] = "vrf", + [NL_UNION_LINK_INFO_DATA_VCAN] = "vcan", }; DEFINE_STRING_TABLE_LOOKUP(nl_union_link_info_data, NLUnionLinkInfoData); static const NLTypeSystem rtnl_link_info_data_type_systems[] = { - [NL_UNION_LINK_INFO_DATA_BOND] = { .count = ELEMENTSOF(rtnl_link_info_data_bond_types), - .types = rtnl_link_info_data_bond_types }, - [NL_UNION_LINK_INFO_DATA_BRIDGE] = { .count = ELEMENTSOF(rtnl_link_info_data_bridge_types), - .types = rtnl_link_info_data_bridge_types }, - [NL_UNION_LINK_INFO_DATA_VLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_vlan_types), - .types = rtnl_link_info_data_vlan_types }, - [NL_UNION_LINK_INFO_DATA_VETH] = { .count = ELEMENTSOF(rtnl_link_info_data_veth_types), - .types = rtnl_link_info_data_veth_types }, - [NL_UNION_LINK_INFO_DATA_MACVLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_macvlan_types), - .types = rtnl_link_info_data_macvlan_types }, - [NL_UNION_LINK_INFO_DATA_MACVTAP] = { .count = ELEMENTSOF(rtnl_link_info_data_macvlan_types), - .types = rtnl_link_info_data_macvlan_types }, - [NL_UNION_LINK_INFO_DATA_IPVLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_ipvlan_types), - .types = rtnl_link_info_data_ipvlan_types }, - [NL_UNION_LINK_INFO_DATA_VXLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_vxlan_types), - .types = rtnl_link_info_data_vxlan_types }, - [NL_UNION_LINK_INFO_DATA_IPIP_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_iptun_types), - .types = rtnl_link_info_data_iptun_types }, - [NL_UNION_LINK_INFO_DATA_IPGRE_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), - .types = rtnl_link_info_data_ipgre_types }, + [NL_UNION_LINK_INFO_DATA_BOND] = { .count = ELEMENTSOF(rtnl_link_info_data_bond_types), + .types = rtnl_link_info_data_bond_types }, + [NL_UNION_LINK_INFO_DATA_BRIDGE] = { .count = ELEMENTSOF(rtnl_link_info_data_bridge_types), + .types = rtnl_link_info_data_bridge_types }, + [NL_UNION_LINK_INFO_DATA_VLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_vlan_types), + .types = rtnl_link_info_data_vlan_types }, + [NL_UNION_LINK_INFO_DATA_VETH] = { .count = ELEMENTSOF(rtnl_link_info_data_veth_types), + .types = rtnl_link_info_data_veth_types }, + [NL_UNION_LINK_INFO_DATA_MACVLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_macvlan_types), + .types = rtnl_link_info_data_macvlan_types }, + [NL_UNION_LINK_INFO_DATA_MACVTAP] = { .count = ELEMENTSOF(rtnl_link_info_data_macvlan_types), + .types = rtnl_link_info_data_macvlan_types }, + [NL_UNION_LINK_INFO_DATA_IPVLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_ipvlan_types), + .types = rtnl_link_info_data_ipvlan_types }, + [NL_UNION_LINK_INFO_DATA_VXLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_vxlan_types), + .types = rtnl_link_info_data_vxlan_types }, + [NL_UNION_LINK_INFO_DATA_IPIP_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_iptun_types), + .types = rtnl_link_info_data_iptun_types }, + [NL_UNION_LINK_INFO_DATA_IPGRE_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), + .types = rtnl_link_info_data_ipgre_types }, [NL_UNION_LINK_INFO_DATA_IPGRETAP_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), - .types = rtnl_link_info_data_ipgre_types }, - [NL_UNION_LINK_INFO_DATA_IP6GRE_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), - .types = rtnl_link_info_data_ipgre_types }, - [NL_UNION_LINK_INFO_DATA_IP6GRETAP_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), - .types = rtnl_link_info_data_ipgre_types }, - [NL_UNION_LINK_INFO_DATA_SIT_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_iptun_types), - .types = rtnl_link_info_data_iptun_types }, - [NL_UNION_LINK_INFO_DATA_VTI_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipvti_types), - .types = rtnl_link_info_data_ipvti_types }, - [NL_UNION_LINK_INFO_DATA_VTI6_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipvti_types), - .types = rtnl_link_info_data_ipvti_types }, - [NL_UNION_LINK_INFO_DATA_IP6TNL_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ip6tnl_types), - .types = rtnl_link_info_data_ip6tnl_types }, - - [NL_UNION_LINK_INFO_DATA_VRF] = { .count = ELEMENTSOF(rtnl_link_info_data_vrf_types), - .types = rtnl_link_info_data_vrf_types }, - + .types = rtnl_link_info_data_ipgre_types }, + [NL_UNION_LINK_INFO_DATA_IP6GRE_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), + .types = rtnl_link_info_data_ipgre_types }, + [NL_UNION_LINK_INFO_DATA_IP6GRETAP_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), + .types = rtnl_link_info_data_ipgre_types }, + [NL_UNION_LINK_INFO_DATA_SIT_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_iptun_types), + .types = rtnl_link_info_data_iptun_types }, + [NL_UNION_LINK_INFO_DATA_VTI_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipvti_types), + .types = rtnl_link_info_data_ipvti_types }, + [NL_UNION_LINK_INFO_DATA_VTI6_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipvti_types), + .types = rtnl_link_info_data_ipvti_types }, + [NL_UNION_LINK_INFO_DATA_IP6TNL_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ip6tnl_types), + .types = rtnl_link_info_data_ip6tnl_types }, + [NL_UNION_LINK_INFO_DATA_VRF] = { .count = ELEMENTSOF(rtnl_link_info_data_vrf_types), + .types = rtnl_link_info_data_vrf_types }, }; static const NLTypeSystemUnion rtnl_link_info_data_type_system_union = { diff --git a/src/libsystemd/sd-netlink/netlink-types.h b/src/libsystemd/sd-netlink/netlink-types.h index 7c0e598b26..42e96173de 100644 --- a/src/libsystemd/sd-netlink/netlink-types.h +++ b/src/libsystemd/sd-netlink/netlink-types.h @@ -87,6 +87,7 @@ typedef enum NLUnionLinkInfoData { NL_UNION_LINK_INFO_DATA_VTI6_TUNNEL, NL_UNION_LINK_INFO_DATA_IP6TNL_TUNNEL, NL_UNION_LINK_INFO_DATA_VRF, + NL_UNION_LINK_INFO_DATA_VCAN, _NL_UNION_LINK_INFO_DATA_MAX, _NL_UNION_LINK_INFO_DATA_INVALID = -1 } NLUnionLinkInfoData; diff --git a/src/libudev/libudev-device.c b/src/libudev/libudev-device.c index 995bf56586..c5f36725dc 100644 --- a/src/libudev/libudev-device.c +++ b/src/libudev/libudev-device.c @@ -495,7 +495,7 @@ _public_ struct udev_device *udev_device_get_parent_with_subsystem_devtype(struc return NULL; } - /* then walk the chain of udev_device parents until the correspanding + /* then walk the chain of udev_device parents until the corresponding one is found */ while ((udev_device = udev_device_get_parent(udev_device))) { if (udev_device->device == parent) diff --git a/src/libudev/libudev-list.c b/src/libudev/libudev-list.c index da496ed456..0d51322a15 100644 --- a/src/libudev/libudev-list.c +++ b/src/libudev/libudev-list.c @@ -166,17 +166,16 @@ struct udev_list_entry *udev_list_entry_add(struct udev_list *list, const char * entry = new0(struct udev_list_entry, 1); if (entry == NULL) return NULL; + entry->name = strdup(name); - if (entry->name == NULL) { - free(entry); - return NULL; - } + if (entry->name == NULL) + return mfree(entry); + if (value != NULL) { entry->value = strdup(value); if (entry->value == NULL) { free(entry->name); - free(entry); - return NULL; + return mfree(entry); } } @@ -193,8 +192,7 @@ struct udev_list_entry *udev_list_entry_add(struct udev_list *list, const char * if (entries == NULL) { free(entry->name); free(entry->value); - free(entry); - return NULL; + return mfree(entry); } list->entries = entries; list->entries_max += add; diff --git a/src/libudev/libudev-monitor.c b/src/libudev/libudev-monitor.c index 1f9d16c450..a1f2b33ad5 100644 --- a/src/libudev/libudev-monitor.c +++ b/src/libudev/libudev-monitor.c @@ -207,8 +207,7 @@ struct udev_monitor *udev_monitor_new_from_netlink_fd(struct udev *udev, const c udev_monitor->sock = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_KOBJECT_UEVENT); if (udev_monitor->sock < 0) { log_debug_errno(errno, "error getting socket: %m"); - free(udev_monitor); - return NULL; + return mfree(udev_monitor); } } else { udev_monitor->bound = true; diff --git a/src/locale/localed.c b/src/locale/localed.c index 298f176e40..1cb049e74a 100644 --- a/src/locale/localed.c +++ b/src/locale/localed.c @@ -334,7 +334,7 @@ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *er r = locale_write_data(c, &settings); if (r < 0) { log_error_errno(r, "Failed to set locale: %m"); - return sd_bus_error_set_errnof(error, r, "Failed to set locale: %s", strerror(-r)); + return sd_bus_error_set_errnof(error, r, "Failed to set locale: %m"); } locale_update_system_manager(c, sd_bus_message_get_bus(m)); @@ -403,7 +403,7 @@ static int method_set_vc_keyboard(sd_bus_message *m, void *userdata, sd_bus_erro r = vconsole_write_data(c); if (r < 0) { log_error_errno(r, "Failed to set virtual console keymap: %m"); - return sd_bus_error_set_errnof(error, r, "Failed to set virtual console keymap: %s", strerror(-r)); + return sd_bus_error_set_errnof(error, r, "Failed to set virtual console keymap: %m"); } log_info("Changed virtual console keymap to '%s' toggle '%s'", @@ -592,7 +592,7 @@ static int method_set_x11_keyboard(sd_bus_message *m, void *userdata, sd_bus_err r = x11_write_data(c); if (r < 0) { log_error_errno(r, "Failed to set X11 keyboard layout: %m"); - return sd_bus_error_set_errnof(error, r, "Failed to set X11 keyboard layout: %s", strerror(-r)); + return sd_bus_error_set_errnof(error, r, "Failed to set X11 keyboard layout: %m"); } log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'", diff --git a/src/login/loginctl.c b/src/login/loginctl.c index 0fc33cf541..4c618ed19e 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -83,6 +83,34 @@ static OutputFlags get_output_flags(void) { colors_enabled() * OUTPUT_COLOR; } +static int get_session_path(sd_bus *bus, const char *session_id, sd_bus_error *error, char **path) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; + char *ans; + + r = sd_bus_call_method( + bus, + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "GetSession", + error, &reply, + "s", session_id); + if (r < 0) + return r; + + r = sd_bus_message_read(reply, "o", &ans); + if (r < 0) + return r; + + ans = strdup(ans); + if (!ans) + return -ENOMEM; + + *path = ans; + return 0; +} + static int list_sessions(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; @@ -115,10 +143,38 @@ static int list_sessions(int argc, char *argv[], void *userdata) { return bus_log_parse_error(r); if (arg_legend) - printf("%10s %10s %-16s %-16s\n", "SESSION", "UID", "USER", "SEAT"); + printf("%10s %10s %-16s %-16s %-16s\n", "SESSION", "UID", "USER", "SEAT", "TTY"); while ((r = sd_bus_message_read(reply, "(susso)", &id, &uid, &user, &seat, &object)) > 0) { - printf("%10s %10u %-16s %-16s\n", id, (unsigned) uid, user, seat); + _cleanup_(sd_bus_error_free) sd_bus_error error2 = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply2 = NULL; + _cleanup_free_ char *path = NULL; + const char *tty = NULL; + + r = get_session_path(bus, id, &error2, &path); + if (r < 0) + log_warning("Failed to get session path: %s", bus_error_message(&error, r)); + else { + r = sd_bus_get_property( + bus, + "org.freedesktop.login1", + path, + "org.freedesktop.login1.Session", + "TTY", + &error2, + &reply2, + "s"); + if (r < 0) + log_warning("Failed to get TTY for session %s: %s", + id, bus_error_message(&error2, r)); + else { + r = sd_bus_message_read(reply2, "s", &tty); + if (r < 0) + return bus_log_parse_error(r); + } + } + + printf("%10s %10"PRIu32" %-16s %-16s %-16s\n", id, uid, user, seat, strna(tty)); k++; } if (r < 0) @@ -165,7 +221,7 @@ static int list_users(int argc, char *argv[], void *userdata) { printf("%10s %-16s\n", "UID", "USER"); while ((r = sd_bus_message_read(reply, "(uso)", &uid, &user, &object)) > 0) { - printf("%10u %-16s\n", (unsigned) uid, user); + printf("%10"PRIu32" %-16s\n", uid, user); k++; } if (r < 0) @@ -462,9 +518,9 @@ static int print_session_status_info(sd_bus *bus, const char *path, bool *new_li printf("%s - ", strna(i.id)); if (i.name) - printf("%s (%u)\n", i.name, (unsigned) i.uid); + printf("%s (%"PRIu32")\n", i.name, i.uid); else - printf("%u\n", (unsigned) i.uid); + printf("%"PRIu32"\n", i.uid); s1 = format_timestamp_relative(since1, sizeof(since1), i.timestamp.realtime); s2 = format_timestamp(since2, sizeof(since2), i.timestamp.realtime); @@ -477,7 +533,7 @@ static int print_session_status_info(sd_bus *bus, const char *path, bool *new_li if (i.leader > 0) { _cleanup_free_ char *t = NULL; - printf("\t Leader: %u", (unsigned) i.leader); + printf("\t Leader: %"PRIu32, i.leader); get_process_comm(i.leader, &t); if (t) @@ -589,9 +645,9 @@ static int print_user_status_info(sd_bus *bus, const char *path, bool *new_line) *new_line = true; if (i.name) - printf("%s (%u)\n", i.name, (unsigned) i.uid); + printf("%s (%"PRIu32")\n", i.name, i.uid); else - printf("%u\n", (unsigned) i.uid); + printf("%"PRIu32"\n", i.uid); s1 = format_timestamp_relative(since1, sizeof(since1), i.timestamp.realtime); s2 = format_timestamp(since2, sizeof(since2), i.timestamp.realtime); @@ -887,26 +943,14 @@ static int show_session(int argc, char *argv[], void *userdata) { for (i = 1; i < argc; i++) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message * reply = NULL; - const char *path = NULL; + _cleanup_free_ char *path = NULL; - r = sd_bus_call_method( - bus, - "org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - "GetSession", - &error, &reply, - "s", argv[i]); + r = get_session_path(bus, argv[1], &error, &path); if (r < 0) { - log_error("Failed to get session: %s", bus_error_message(&error, r)); + log_error("Failed to get session path: %s", bus_error_message(&error, r)); return r; } - r = sd_bus_message_read(reply, "o", &path); - if (r < 0) - return bus_log_parse_error(r); - if (properties) r = show_properties(bus, path, &new_line); else diff --git a/src/login/logind-action.c b/src/login/logind-action.c index 8ef48dbaa1..a950409254 100644 --- a/src/login/logind-action.c +++ b/src/login/logind-action.c @@ -85,7 +85,7 @@ int manager_handle_action( } /* If the key handling is inhibited, don't do anything */ - if (!ignore_inhibited && inhibit_key > 0) { + if (inhibit_key > 0) { if (manager_is_inhibited(m, inhibit_key, INHIBIT_BLOCK, NULL, true, false, 0, NULL)) { log_debug("Refusing operation, %s is inhibited.", inhibit_what_to_string(inhibit_key)); return 0; diff --git a/src/login/logind-button.c b/src/login/logind-button.c index baa6b7113c..90fb93bbaf 100644 --- a/src/login/logind-button.c +++ b/src/login/logind-button.c @@ -43,15 +43,12 @@ Button* button_new(Manager *m, const char *name) { return NULL; b->name = strdup(name); - if (!b->name) { - free(b); - return NULL; - } + if (!b->name) + return mfree(b); if (hashmap_put(m->buttons, b->name, b) < 0) { free(b->name); - free(b); - return NULL; + return mfree(b); } b->manager = m; diff --git a/src/login/logind-device.c b/src/login/logind-device.c index eb5edd1cd5..6537fa04bf 100644 --- a/src/login/logind-device.c +++ b/src/login/logind-device.c @@ -34,15 +34,12 @@ Device* device_new(Manager *m, const char *sysfs, bool master) { return NULL; d->sysfs = strdup(sysfs); - if (!d->sysfs) { - free(d); - return NULL; - } + if (!d->sysfs) + return mfree(d); if (hashmap_put(m->devices, d->sysfs, d) < 0) { free(d->sysfs); - free(d); - return NULL; + return mfree(d); } d->manager = m; diff --git a/src/login/logind-inhibit.c b/src/login/logind-inhibit.c index 6c78e0dddc..c93b24009b 100644 --- a/src/login/logind-inhibit.c +++ b/src/login/logind-inhibit.c @@ -45,17 +45,14 @@ Inhibitor* inhibitor_new(Manager *m, const char* id) { return NULL; i->state_file = strappend("/run/systemd/inhibit/", id); - if (!i->state_file) { - free(i); - return NULL; - } + if (!i->state_file) + return mfree(i); i->id = basename(i->state_file); if (hashmap_put(m->inhibitors, i->id, i) < 0) { free(i->state_file); - free(i); - return NULL; + return mfree(i); } i->manager = m; diff --git a/src/login/logind-seat.c b/src/login/logind-seat.c index b5192320e4..ecc7bd2e5b 100644 --- a/src/login/logind-seat.c +++ b/src/login/logind-seat.c @@ -48,18 +48,15 @@ Seat *seat_new(Manager *m, const char *id) { return NULL; s->state_file = strappend("/run/systemd/seats/", id); - if (!s->state_file) { - free(s); - return NULL; - } + if (!s->state_file) + return mfree(s); s->id = basename(s->state_file); s->manager = m; if (hashmap_put(m->seats, s->id, s) < 0) { free(s->state_file); - free(s); - return NULL; + return mfree(s); } return s; diff --git a/src/login/logind-session.c b/src/login/logind-session.c index b6da237397..cbf035f706 100644 --- a/src/login/logind-session.c +++ b/src/login/logind-session.c @@ -62,16 +62,13 @@ Session* session_new(Manager *m, const char *id) { return NULL; s->state_file = strappend("/run/systemd/sessions/", id); - if (!s->state_file) { - free(s); - return NULL; - } + if (!s->state_file) + return mfree(s); s->devices = hashmap_new(&devt_hash_ops); if (!s->devices) { free(s->state_file); - free(s); - return NULL; + return mfree(s); } s->id = basename(s->state_file); @@ -79,8 +76,7 @@ Session* session_new(Manager *m, const char *id) { if (hashmap_put(m->sessions, s->id, s) < 0) { hashmap_free(s->devices); free(s->state_file); - free(s); - return NULL; + return mfree(s); } s->manager = m; @@ -611,7 +607,7 @@ static int session_stop_scope(Session *s, bool force) { return 0; /* Let's always abandon the scope first. This tells systemd that we are not interested anymore, and everything - * that is left in in the scope is "left-over". Informing systemd about this has the benefit that it will log + * that is left in the scope is "left-over". Informing systemd about this has the benefit that it will log * when killing any processes left after this point. */ r = manager_abandon_scope(s->manager, s->scope, &error); if (r < 0) diff --git a/src/login/logind-user.c b/src/login/logind-user.c index 348e396292..2dc5fa7665 100644 --- a/src/login/logind-user.c +++ b/src/login/logind-user.c @@ -26,6 +26,7 @@ #include "bus-common-errors.h" #include "bus-error.h" #include "bus-util.h" +#include "cgroup-util.h" #include "clean-ipc.h" #include "conf-parser.h" #include "escape.h" @@ -353,14 +354,12 @@ static int user_mkdir_runtime_path(User *u) { r = mount("tmpfs", u->runtime_path, "tmpfs", MS_NODEV|MS_NOSUID, t); if (r < 0) { - if (errno != EPERM) { + if (errno != EPERM && errno != EACCES) { r = log_error_errno(errno, "Failed to mount per-user tmpfs directory %s: %m", u->runtime_path); goto fail; } - /* Lacking permissions, maybe - * CAP_SYS_ADMIN-less container? In this case, - * just use a normal directory. */ + log_debug_errno(errno, "Failed to mount per-user tmpfs directory %s, assuming containerized execution, ignoring: %m", u->runtime_path); r = chmod_and_chown(u->runtime_path, 0700, u->uid, u->gid); if (r < 0) { @@ -612,9 +611,14 @@ int user_finalize(User *u) { if (k < 0) r = k; - /* Clean SysV + POSIX IPC objects */ - if (u->manager->remove_ipc) { - k = clean_ipc(u->uid); + /* Clean SysV + POSIX IPC objects, but only if this is not a system user. Background: in many setups cronjobs + * are run in full PAM and thus logind sessions, even if the code run doesn't belong to actual users but to + * system components. Since enable RemoveIPC= globally for all users, we need to be a bit careful with such + * cases, as we shouldn't accidentally remove a system service's IPC objects while it is running, just because + * a cronjob running as the same user just finished. Hence: exclude system users generally from IPC clean-up, + * and do it only for normal users. */ + if (u->manager->remove_ipc && u->uid > SYSTEM_UID_MAX) { + k = clean_ipc_by_uid(u->uid); if (k < 0) r = k; } @@ -891,9 +895,19 @@ int config_parse_user_tasks_max( assert(rvalue); assert(data); - /* First, try to parse as percentage */ + if (isempty(rvalue)) { + *m = system_tasks_max_scale(DEFAULT_USER_TASKS_MAX_PERCENTAGE, 100U); + return 0; + } + + if (streq(rvalue, "infinity")) { + *m = CGROUP_LIMIT_MAX; + return 0; + } + + /* Try to parse as percentage */ r = parse_percent(rvalue); - if (r > 0 && r < 100) + if (r >= 0) k = system_tasks_max_scale(r, 100U); else { diff --git a/src/login/logind.c b/src/login/logind.c index 5ce36d28c7..a9841a3832 100644 --- a/src/login/logind.c +++ b/src/login/logind.c @@ -38,6 +38,7 @@ #include "signal-util.h" #include "strv.h" #include "udev-util.h" +#include "cgroup-util.h" static void manager_free(Manager *m); @@ -62,7 +63,7 @@ static void manager_reset_config(Manager *m) { m->idle_action = HANDLE_IGNORE; m->runtime_dir_size = physical_memory_scale(10U, 100U); /* 10% */ - m->user_tasks_max = system_tasks_max_scale(33U, 100U); /* 33% */ + m->user_tasks_max = system_tasks_max_scale(DEFAULT_USER_TASKS_MAX_PERCENTAGE, 100U); /* 33% */ m->sessions_max = 8192; m->inhibitors_max = 8192; @@ -125,7 +126,8 @@ static void manager_free(Manager *m) { Inhibitor *i; Button *b; - assert(m); + if (!m) + return; while ((session = hashmap_first(m->sessions))) session_free(session); @@ -1001,7 +1003,7 @@ static int manager_dispatch_idle_action(sd_event_source *s, uint64_t t, void *us static int manager_parse_config_file(Manager *m) { assert(m); - return config_parse_many(PKGSYSCONFDIR "/logind.conf", + return config_parse_many_nulstr(PKGSYSCONFDIR "/logind.conf", CONF_PATHS_NULSTR("systemd/logind.conf.d"), "Login\0", config_item_perf_lookup, logind_gperf_lookup, diff --git a/src/login/systemd-user.m4 b/src/login/systemd-user.m4 index f188a8e548..e33963b125 100644 --- a/src/login/systemd-user.m4 +++ b/src/login/systemd-user.m4 @@ -2,11 +2,11 @@ # # Used by systemd --user instances. -account include system-auth +account required pam_unix.so m4_ifdef(`HAVE_SELINUX', session required pam_selinux.so close session required pam_selinux.so nottys open )m4_dnl session required pam_loginuid.so -session include system-auth +session optional pam_systemd.so diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c index ba7ac04b56..5ca18ff87e 100644 --- a/src/machine/machine-dbus.c +++ b/src/machine/machine-dbus.c @@ -51,24 +51,6 @@ #include "terminal-util.h" #include "user-util.h" -static int property_get_id( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Machine *m = userdata; - - assert(bus); - assert(reply); - assert(m); - - return sd_bus_message_append_array(reply, 'y', &m->id, 16); -} - static int property_get_state( sd_bus *bus, const char *path, @@ -1311,7 +1293,7 @@ int bus_machine_method_open_root_directory(sd_bus_message *message, void *userda const sd_bus_vtable machine_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Machine, name), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Id", "ay", property_get_id, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Id", "ay", bus_property_get_id128, offsetof(Machine, id), SD_BUS_VTABLE_PROPERTY_CONST), BUS_PROPERTY_DUAL_TIMESTAMP("Timestamp", offsetof(Machine, timestamp), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Service", "s", NULL, offsetof(Machine, service), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Unit", "s", NULL, offsetof(Machine, unit), SD_BUS_VTABLE_PROPERTY_CONST), diff --git a/src/machine/machine.c b/src/machine/machine.c index dd046d6563..a02b9d7575 100644 --- a/src/machine/machine.c +++ b/src/machine/machine.c @@ -80,9 +80,7 @@ Machine* machine_new(Manager *manager, MachineClass class, const char *name) { fail: free(m->state_file); free(m->name); - free(m); - - return NULL; + return mfree(m); } void machine_free(Machine *m) { diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index ddec6cb4d6..7b9be3b425 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -60,6 +60,9 @@ #include "util.h" #include "verbs.h" #include "web-util.h" +#include "stdio-util.h" + +#define ALL_IP_ADDRESSES -1 static char **arg_property = NULL; static bool arg_all = false; @@ -82,6 +85,9 @@ static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE; static const char* arg_format = NULL; static const char *arg_uid = NULL; static char **arg_setenv = NULL; +static int arg_addrs = 1; + +static int print_addresses(sd_bus *bus, const char *name, int, const char *pr1, const char *pr2, int n_addr); static void polkit_agent_open_if_enabled(void) { @@ -109,6 +115,8 @@ typedef struct MachineInfo { const char *name; const char *class; const char *service; + char *os; + char *version_id; } MachineInfo; static int compare_machine_info(const void *a, const void *b) { @@ -117,12 +125,92 @@ static int compare_machine_info(const void *a, const void *b) { return strcmp(x->name, y->name); } +static void clean_machine_info(MachineInfo *machines, size_t n_machines) { + size_t i; + + if (!machines || n_machines == 0) + return; + + for (i = 0; i < n_machines; i++) { + free(machines[i].os); + free(machines[i].version_id); + } + free(machines); +} + +static int get_os_release_property(sd_bus *bus, const char *name, const char *query, ...) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + const char *k, *v, *iter, **query_res = NULL; + size_t count = 0, awaited_args = 0; + va_list ap; + int r; + + assert(bus); + assert(name); + assert(query); + + NULSTR_FOREACH(iter, query) + awaited_args++; + query_res = newa0(const char *, awaited_args); + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetMachineOSRelease", + NULL, &reply, "s", name); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(reply, 'a', "{ss}"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "{ss}", &k, &v)) > 0) { + count = 0; + NULSTR_FOREACH(iter, query) { + if (streq(k, iter)) { + query_res[count] = v; + break; + } + count++; + } + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + va_start(ap, query); + for (count = 0; count < awaited_args; count++) { + char *val, **out; + + out = va_arg(ap, char **); + assert(out); + if (query_res[count]) { + val = strdup(query_res[count]); + if (!val) { + va_end(ap); + return log_oom(); + } + *out = val; + } + } + va_end(ap); + + return 0; +} + static int list_machines(int argc, char *argv[], void *userdata) { - size_t max_name = strlen("MACHINE"), max_class = strlen("CLASS"), max_service = strlen("SERVICE"); + size_t max_name = strlen("MACHINE"), max_class = strlen("CLASS"), + max_service = strlen("SERVICE"), max_os = strlen("OS"), max_version_id = strlen("VERSION"); _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ MachineInfo *machines = NULL; + _cleanup_free_ char *prefix = NULL; + MachineInfo *machines = NULL; const char *name, *class, *service, *object; size_t n_machines = 0, n_allocated = 0, j; sd_bus *bus = userdata; @@ -148,15 +236,25 @@ static int list_machines(int argc, char *argv[], void *userdata) { r = sd_bus_message_enter_container(reply, 'a', "(ssso)"); if (r < 0) return bus_log_parse_error(r); - while ((r = sd_bus_message_read(reply, "(ssso)", &name, &class, &service, &object)) > 0) { size_t l; if (name[0] == '.' && !arg_all) continue; - if (!GREEDY_REALLOC(machines, n_allocated, n_machines + 1)) - return log_oom(); + if (!GREEDY_REALLOC(machines, n_allocated, n_machines + 1)) { + r = log_oom(); + goto out; + } + + machines[n_machines].os = NULL; + machines[n_machines].version_id = NULL; + r = get_os_release_property(bus, name, + "ID\0" "VERSION_ID\0", + &machines[n_machines].os, + &machines[n_machines].version_id); + if (r < 0) + goto out; machines[n_machines].name = name; machines[n_machines].class = class; @@ -174,33 +272,72 @@ static int list_machines(int argc, char *argv[], void *userdata) { if (l > max_service) max_service = l; + l = machines[n_machines].os ? strlen(machines[n_machines].os) : 1; + if (l > max_os) + max_os = l; + + l = machines[n_machines].version_id ? strlen(machines[n_machines].version_id) : 1; + if (l > max_version_id) + max_version_id = l; + n_machines++; } - if (r < 0) - return bus_log_parse_error(r); + if (r < 0) { + r = bus_log_parse_error(r); + goto out; + } r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); + if (r < 0) { + r = bus_log_parse_error(r); + goto out; + } qsort_safe(machines, n_machines, sizeof(MachineInfo), compare_machine_info); - if (arg_legend) - printf("%-*s %-*s %-*s\n", + /* Allocate for prefix max characters for all fields + spaces between them + strlen(",\n") */ + r = asprintf(&prefix, "%-*s", + (int) (max_name + + max_class + + max_service + + max_os + + max_version_id + 5 + strlen(",\n")), + ",\n"); + if (r < 0) { + r = log_oom(); + goto out; + } + + if (arg_legend && n_machines > 0) + printf("%-*s %-*s %-*s %-*s %-*s %s\n", (int) max_name, "MACHINE", (int) max_class, "CLASS", - (int) max_service, "SERVICE"); + (int) max_service, "SERVICE", + (int) max_os, "OS", + (int) max_version_id, "VERSION", + "ADDRESSES"); - for (j = 0; j < n_machines; j++) - printf("%-*s %-*s %-*s\n", + for (j = 0; j < n_machines; j++) { + printf("%-*s %-*s %-*s %-*s %-*s ", (int) max_name, machines[j].name, (int) max_class, machines[j].class, - (int) max_service, machines[j].service); + (int) max_service, strdash_if_empty(machines[j].service), + (int) max_os, strdash_if_empty(machines[j].os), + (int) max_version_id, strdash_if_empty(machines[j].version_id)); - if (arg_legend) + r = print_addresses(bus, machines[j].name, 0, "", prefix, arg_addrs); + if (r == -ENOSYS) + printf("-\n"); + } + + if (arg_legend && n_machines > 0) printf("\n%zu machines listed.\n", n_machines); + else + printf("No machines.\n"); - return 0; +out: + clean_machine_info(machines, n_machines); + return r; } typedef struct ImageInfo { @@ -305,7 +442,7 @@ static int list_images(int argc, char *argv[], void *userdata) { qsort_safe(images, n_images, sizeof(ImageInfo), compare_image_info); - if (arg_legend) + if (arg_legend && n_images > 0) printf("%-*s %-*s %-3s %-*s %-*s %-*s\n", (int) max_name, "NAME", (int) max_type, "TYPE", @@ -326,8 +463,10 @@ static int list_images(int argc, char *argv[], void *userdata) { (int) max_mtime, strna(format_timestamp(mtime_buf, sizeof(mtime_buf), images[j].mtime))); } - if (arg_legend) + if (arg_legend && n_images > 0) printf("\n%zu images listed.\n", n_images); + else + printf("No images.\n"); return 0; } @@ -390,8 +529,10 @@ static int show_unit_cgroup(sd_bus *bus, const char *unit, pid_t leader) { return 0; } -static int print_addresses(sd_bus *bus, const char *name, int ifi, const char *prefix, const char *prefix2) { +static int print_addresses(sd_bus *bus, const char *name, int ifi, const char *prefix, const char *prefix2, int n_addr) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *addresses = NULL; + bool truncate = false; int r; assert(bus); @@ -410,6 +551,11 @@ static int print_addresses(sd_bus *bus, const char *name, int ifi, const char *p if (r < 0) return r; + addresses = strdup(prefix); + if (!addresses) + return log_oom(); + prefix = ""; + r = sd_bus_message_enter_container(reply, 'a', "(iay)"); if (r < 0) return bus_log_parse_error(r); @@ -418,7 +564,7 @@ static int print_addresses(sd_bus *bus, const char *name, int ifi, const char *p int family; const void *a; size_t sz; - char buffer[MAX(INET6_ADDRSTRLEN, INET_ADDRSTRLEN)]; + char buf_ifi[DECIMAL_STR_MAX(int) + 2], buffer[MAX(INET6_ADDRSTRLEN, INET_ADDRSTRLEN)]; r = sd_bus_message_read(reply, "i", &family); if (r < 0) @@ -428,11 +574,16 @@ static int print_addresses(sd_bus *bus, const char *name, int ifi, const char *p if (r < 0) return bus_log_parse_error(r); - fputs(prefix, stdout); - fputs(inet_ntop(family, a, buffer, sizeof(buffer)), stdout); - if (family == AF_INET6 && ifi > 0) - printf("%%%i", ifi); - fputc('\n', stdout); + if (n_addr != 0) { + if (family == AF_INET6 && ifi > 0) + xsprintf(buf_ifi, "%%%i", ifi); + else + strcpy(buf_ifi, ""); + + if(!strextend(&addresses, prefix, inet_ntop(family, a, buffer, sizeof(buffer)), buf_ifi, NULL)) + return log_oom(); + } else + truncate = true; r = sd_bus_message_exit_container(reply); if (r < 0) @@ -440,6 +591,9 @@ static int print_addresses(sd_bus *bus, const char *name, int ifi, const char *p if (prefix != prefix2) prefix = prefix2; + + if (n_addr > 0) + n_addr -= 1; } if (r < 0) return bus_log_parse_error(r); @@ -448,45 +602,22 @@ static int print_addresses(sd_bus *bus, const char *name, int ifi, const char *p if (r < 0) return bus_log_parse_error(r); + fprintf(stdout, "%s%s\n", addresses, truncate ? "..." : ""); return 0; } static int print_os_release(sd_bus *bus, const char *name, const char *prefix) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - const char *k, *v, *pretty = NULL; + _cleanup_free_ char *pretty = NULL; int r; assert(bus); assert(name); assert(prefix); - r = sd_bus_call_method(bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "GetMachineOSRelease", - NULL, - &reply, - "s", name); + r = get_os_release_property(bus, name, "PRETTY_NAME\0", &pretty, NULL); if (r < 0) return r; - r = sd_bus_message_enter_container(reply, 'a', "{ss}"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(reply, "{ss}", &k, &v)) > 0) { - if (streq(k, "PRETTY_NAME")) - pretty = v; - - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - if (pretty) printf("%s%s\n", prefix, pretty); @@ -591,7 +722,8 @@ static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) { print_addresses(bus, i->name, ifi, "\t Address: ", - "\t "); + "\n\t ", + ALL_IP_ADDRESSES); print_os_release(bus, i->name, "\t OS: "); @@ -1194,10 +1326,12 @@ static int process_forward(sd_event *event, PTYForward **forward, int master, PT assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGWINCH, SIGTERM, SIGINT, -1) >= 0); - if (streq(name, ".host")) - log_info("Connected to the local host. Press ^] three times within 1s to exit session."); - else - log_info("Connected to machine %s. Press ^] three times within 1s to exit session.", name); + if (!arg_quiet) { + if (streq(name, ".host")) + log_info("Connected to the local host. Press ^] three times within 1s to exit session."); + else + log_info("Connected to machine %s. Press ^] three times within 1s to exit session.", name); + } sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); @@ -1221,17 +1355,54 @@ static int process_forward(sd_event *event, PTYForward **forward, int master, PT if (last_char != '\n') fputc('\n', stdout); - if (machine_died) - log_info("Machine %s terminated.", name); - else if (streq(name, ".host")) - log_info("Connection to the local host terminated."); - else - log_info("Connection to machine %s terminated.", name); + if (!arg_quiet) { + if (machine_died) + log_info("Machine %s terminated.", name); + else if (streq(name, ".host")) + log_info("Connection to the local host terminated."); + else + log_info("Connection to machine %s terminated.", name); + } sd_event_get_exit_code(event, &ret); return ret; } +static int parse_machine_uid(const char *spec, const char **machine, char **uid) { + /* + * Whatever is specified in the spec takes priority over global arguments. + */ + char *_uid = NULL; + const char *_machine = NULL; + + if (spec) { + const char *at; + + at = strchr(spec, '@'); + if (at) { + if (at == spec) + /* Do the same as ssh and refuse "@host". */ + return -EINVAL; + + _machine = at + 1; + _uid = strndup(spec, at - spec); + if (!_uid) + return -ENOMEM; + } else + _machine = spec; + }; + + if (arg_uid && !_uid) { + _uid = strdup(arg_uid); + if (!_uid) + return -ENOMEM; + } + + *uid = _uid; + *machine = isempty(_machine) ? ".host" : _machine; + return 0; +} + static int login_machine(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -1307,7 +1478,8 @@ static int shell_machine(int argc, char *argv[], void *userdata) { _cleanup_(sd_event_unrefp) sd_event *event = NULL; int master = -1, r; sd_bus *bus = userdata; - const char *pty, *match, *machine, *path, *uid = NULL; + const char *pty, *match, *machine, *path; + _cleanup_free_ char *uid = NULL; assert(bus); @@ -1338,22 +1510,9 @@ static int shell_machine(int argc, char *argv[], void *userdata) { if (r < 0) return log_error_errno(r, "Failed to attach bus to event loop: %m"); - machine = argc < 2 || isempty(argv[1]) ? NULL : argv[1]; - - if (arg_uid) - uid = arg_uid; - else if (machine) { - const char *at; - - at = strchr(machine, '@'); - if (at) { - uid = strndupa(machine, at - machine); - machine = at + 1; - } - } - - if (isempty(machine)) - machine = ".host"; + r = parse_machine_uid(argc >= 2 ? argv[1] : NULL, &machine, &uid); + if (r < 0) + return log_error_errno(r, "Failed to parse machine specification: %m"); match = strjoina("type='signal'," "sender='org.freedesktop.machine1'," @@ -2314,7 +2473,7 @@ static int list_transfers(int argc, char *argv[], void *userdata) { qsort_safe(transfers, n_transfers, sizeof(TransferInfo), compare_transfer_info); - if (arg_legend) + if (arg_legend && n_transfers > 0) printf("%-*s %-*s %-*s %-*s %-*s\n", (int) MAX(2U, DECIMAL_STR_WIDTH(max_id)), "ID", (int) 7, "PERCENT", @@ -2330,8 +2489,10 @@ static int list_transfers(int argc, char *argv[], void *userdata) { (int) max_local, transfers[j].local, (int) max_remote, transfers[j].remote); - if (arg_legend) + if (arg_legend && n_transfers > 0) printf("\n%zu transfers listed.\n", n_transfers); + else + printf("No transfers.\n"); return 0; } @@ -2468,6 +2629,7 @@ static int clean_images(int argc, char *argv[], void *userdata) { } static int help(int argc, char *argv[], void *userdata) { + pager_open(arg_no_pager, false); printf("%s [OPTIONS...] {COMMAND} ...\n\n" "Send control commands to or query the virtual machine and container\n" @@ -2491,6 +2653,7 @@ static int help(int argc, char *argv[], void *userdata) { " --read-only Create read-only bind mount\n" " --mkdir Create directory before bind mounting, if missing\n" " -n --lines=INTEGER Number of journal entries to show\n" + " --max-addresses=INTEGER Number of internet addresses to show at most\n" " -o --output=STRING Change journal output mode (short,\n" " short-monotonic, verbose, export, json,\n" " json-pretty, json-sse, cat)\n" @@ -2555,6 +2718,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_FORCE, ARG_FORMAT, ARG_UID, + ARG_NUMBER_IPS, }; static const struct option options[] = { @@ -2581,6 +2745,7 @@ static int parse_argv(int argc, char *argv[]) { { "format", required_argument, NULL, ARG_FORMAT }, { "uid", required_argument, NULL, ARG_UID }, { "setenv", required_argument, NULL, 'E' }, + { "max-addresses", required_argument, NULL, ARG_NUMBER_IPS }, {} }; @@ -2771,6 +2936,18 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); break; + case ARG_NUMBER_IPS: + if (streq(optarg, "all")) + arg_addrs = ALL_IP_ADDRESSES; + else if (safe_atoi(optarg, &arg_addrs) < 0) { + log_error("Invalid number of IPs"); + return -EINVAL; + } else if (arg_addrs < 0) { + log_error("Number of IPs cannot be negative"); + return -EINVAL; + } + break; + case '?': return -EINVAL; diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index 1923e8b971..e40f40a263 100644 --- a/src/machine/machined-dbus.c +++ b/src/machine/machined-dbus.c @@ -444,7 +444,9 @@ static int method_register_machine_internal(sd_bus_message *message, bool read_n r = cg_pid_get_unit(m->leader, &m->unit); if (r < 0) { - r = sd_bus_error_set_errnof(error, r, "Failed to determine unit of process "PID_FMT" : %s", m->leader, strerror(-r)); + r = sd_bus_error_set_errnof(error, r, + "Failed to determine unit of process "PID_FMT" : %m", + m->leader); goto fail; } @@ -954,7 +956,7 @@ static int method_clean_pool(sd_bus_message *message, void *userdata, sd_bus_err /* Create a temporary file we can dump information about deleted images into. We use a temporary file for this * instead of a pipe or so, since this might grow quit large in theory and we don't want to process this * continuously */ - result_fd = open_tmpfile_unlinkable("/tmp/", O_RDWR|O_CLOEXEC); + result_fd = open_tmpfile_unlinkable(NULL, O_RDWR|O_CLOEXEC); if (result_fd < 0) return -errno; diff --git a/src/machine/operation.c b/src/machine/operation.c index 2bf93cb493..c966d0d21c 100644 --- a/src/machine/operation.c +++ b/src/machine/operation.c @@ -147,6 +147,5 @@ Operation *operation_free(Operation *o) { if (o->machine) LIST_REMOVE(operations_by_machine, o->machine->operations, o); - free(o); - return NULL; + return mfree(o); } diff --git a/src/modules-load/modules-load.c b/src/modules-load/modules-load.c index f75015d8c3..0901fea8dc 100644 --- a/src/modules-load/modules-load.c +++ b/src/modules-load/modules-load.c @@ -59,10 +59,10 @@ static int add_modules(const char *p) { return 0; } -static int parse_proc_cmdline_item(const char *key, const char *value) { +static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { int r; - if (STR_IN_SET(key, "modules-load", "rd.modules-load") && value) { + if (streq(key, "modules-load") && value) { r = add_modules(value); if (r < 0) return r; @@ -226,7 +226,7 @@ int main(int argc, char *argv[]) { umask(0022); - r = parse_proc_cmdline(parse_proc_cmdline_item); + r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, true); if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); diff --git a/src/mount/Makefile b/src/mount/Makefile new file mode 120000 index 0000000000..d0b0e8e008 --- /dev/null +++ b/src/mount/Makefile @@ -0,0 +1 @@ +../Makefile
\ No newline at end of file diff --git a/src/mount/mount-tool.c b/src/mount/mount-tool.c new file mode 100644 index 0000000000..80bba086e4 --- /dev/null +++ b/src/mount/mount-tool.c @@ -0,0 +1,1114 @@ +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <getopt.h> + +#include "libudev.h" +#include "sd-bus.h" + +#include "bus-error.h" +#include "bus-unit-util.h" +#include "bus-util.h" +#include "escape.h" +#include "fstab-util.h" +#include "pager.h" +#include "parse-util.h" +#include "path-util.h" +#include "spawn-polkit-agent.h" +#include "strv.h" +#include "udev-util.h" +#include "unit-name.h" +#include "terminal-util.h" + +enum { + ACTION_DEFAULT, + ACTION_MOUNT, + ACTION_AUTOMOUNT, + ACTION_LIST, +} arg_action = ACTION_DEFAULT; + +static bool arg_no_block = false; +static bool arg_no_pager = false; +static bool arg_ask_password = true; +static bool arg_quiet = false; +static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; +static bool arg_user = false; +static const char *arg_host = NULL; +static bool arg_discover = false; +static char *arg_mount_what = NULL; +static char *arg_mount_where = NULL; +static char *arg_mount_type = NULL; +static char *arg_mount_options = NULL; +static char *arg_description = NULL; +static char **arg_property = NULL; +static usec_t arg_timeout_idle = USEC_INFINITY; +static bool arg_timeout_idle_set = false; +static char **arg_automount_property = NULL; +static int arg_bind_device = -1; +static bool arg_fsck = true; + +static void polkit_agent_open_if_enabled(void) { + + /* Open the polkit agent as a child process if necessary */ + if (!arg_ask_password) + return; + + if (arg_transport != BUS_TRANSPORT_LOCAL) + return; + + polkit_agent_open(); +} + +static void help(void) { + printf("%s [OPTIONS...] WHAT [WHERE]\n\n" + "Establish a mount or auto-mount point transiently.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-block Do not wait until operation finished\n" + " --no-pager Do not pipe output into a pager\n" + " --no-ask-password Do not prompt for password\n" + " -q --quiet Suppress information messages during runtime\n" + " --user Run as user unit\n" + " -H --host=[USER@]HOST Operate on remote host\n" + " -M --machine=CONTAINER Operate on local container\n" + " --discover Discover mount device metadata\n" + " -t --type=TYPE File system type\n" + " -o --options=OPTIONS Mount options\n" + " --fsck=no Don't run file system check before mount\n" + " --description=TEXT Description for unit\n" + " -p --property=NAME=VALUE Set mount unit property\n" + " -A --automount=BOOL Create an auto-mount point\n" + " --timeout-idle-sec=SEC Specify automount idle timeout\n" + " --automount-property=NAME=VALUE\n" + " Set automount unit property\n" + " --bind-device Bind automount unit to device\n" + " --list List mountable block devices\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_NO_BLOCK, + ARG_NO_PAGER, + ARG_NO_ASK_PASSWORD, + ARG_USER, + ARG_SYSTEM, + ARG_DISCOVER, + ARG_MOUNT_TYPE, + ARG_MOUNT_OPTIONS, + ARG_FSCK, + ARG_DESCRIPTION, + ARG_TIMEOUT_IDLE, + ARG_AUTOMOUNT, + ARG_AUTOMOUNT_PROPERTY, + ARG_BIND_DEVICE, + ARG_LIST, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "no-block", no_argument, NULL, ARG_NO_BLOCK }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, + { "quiet", no_argument, NULL, 'q' }, + { "user", no_argument, NULL, ARG_USER }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "host", required_argument, NULL, 'H' }, + { "machine", required_argument, NULL, 'M' }, + { "discover", no_argument, NULL, ARG_DISCOVER }, + { "type", required_argument, NULL, 't' }, + { "options", required_argument, NULL, 'o' }, + { "description", required_argument, NULL, ARG_DESCRIPTION }, + { "property", required_argument, NULL, 'p' }, + { "automount", required_argument, NULL, ARG_AUTOMOUNT }, + { "timeout-idle-sec", required_argument, NULL, ARG_TIMEOUT_IDLE }, + { "automount-property", required_argument, NULL, ARG_AUTOMOUNT_PROPERTY }, + { "bind-device", no_argument, NULL, ARG_BIND_DEVICE }, + { "list", no_argument, NULL, ARG_LIST }, + {}, + }; + + int r, c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hqH:M:t:o:p:A", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_NO_BLOCK: + arg_no_block = true; + break; + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case ARG_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + case 'q': + arg_quiet = true; + break; + + case ARG_USER: + arg_user = true; + break; + + case ARG_SYSTEM: + arg_user = false; + break; + + case 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + arg_transport = BUS_TRANSPORT_MACHINE; + arg_host = optarg; + break; + + case ARG_DISCOVER: + arg_discover = true; + break; + + case 't': + if (free_and_strdup(&arg_mount_type, optarg) < 0) + return log_oom(); + break; + + case 'o': + if (free_and_strdup(&arg_mount_options, optarg) < 0) + return log_oom(); + break; + + case ARG_FSCK: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --fsck= argument: %s", optarg); + + arg_fsck = r; + break; + + case ARG_DESCRIPTION: + if (free_and_strdup(&arg_description, optarg) < 0) + return log_oom(); + break; + + case 'p': + if (strv_extend(&arg_property, optarg) < 0) + return log_oom(); + + break; + + case 'A': + arg_action = ACTION_AUTOMOUNT; + break; + + case ARG_AUTOMOUNT: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "--automount= expects a valid boolean parameter: %s", optarg); + + arg_action = r ? ACTION_AUTOMOUNT : ACTION_MOUNT; + break; + + case ARG_TIMEOUT_IDLE: + r = parse_sec(optarg, &arg_timeout_idle); + if (r < 0) + return log_error_errno(r, "Failed to parse timeout: %s", optarg); + + break; + + case ARG_AUTOMOUNT_PROPERTY: + if (strv_extend(&arg_automount_property, optarg) < 0) + return log_oom(); + + break; + + case ARG_BIND_DEVICE: + arg_bind_device = true; + break; + + case ARG_LIST: + arg_action = ACTION_LIST; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (arg_user && arg_transport != BUS_TRANSPORT_LOCAL) { + log_error("Execution in user context is not supported on non-local systems."); + return -EINVAL; + } + + if (arg_action == ACTION_LIST) { + if (optind < argc) { + log_error("Too many arguments."); + return -EINVAL; + } + + if (arg_transport != BUS_TRANSPORT_LOCAL) { + log_error("Listing devices only supported locally."); + return -EOPNOTSUPP; + } + } else { + if (optind >= argc) { + log_error("At least one argument required."); + return -EINVAL; + } + + if (argc > optind+2) { + log_error("At most two arguments required."); + return -EINVAL; + } + + arg_mount_what = fstab_node_to_udev_node(argv[optind]); + if (!arg_mount_what) + return log_oom(); + + if (argc > optind+1) { + r = path_make_absolute_cwd(argv[optind+1], &arg_mount_where); + if (r < 0) + return log_error_errno(r, "Failed to make path absolute: %m"); + } else + arg_discover = true; + + if (arg_discover && arg_transport != BUS_TRANSPORT_LOCAL) { + log_error("Automatic mount location discovery is only supported locally."); + return -EOPNOTSUPP; + } + } + + return 1; +} + +static int transient_unit_set_properties(sd_bus_message *m, char **properties) { + int r; + + if (!isempty(arg_description)) { + r = sd_bus_message_append(m, "(sv)", "Description", "s", arg_description); + if (r < 0) + return r; + } + + if (arg_bind_device && is_device_path(arg_mount_what)) { + _cleanup_free_ char *device_unit = NULL; + + r = unit_name_from_path(arg_mount_what, ".device", &device_unit); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "(sv)(sv)", + "After", "as", 1, device_unit, + "BindsTo", "as", 1, device_unit); + if (r < 0) + return r; + } + + r = bus_append_unit_property_assignment_many(m, properties); + if (r < 0) + return r; + + return 0; +} + +static int transient_mount_set_properties(sd_bus_message *m) { + int r; + + assert(m); + + r = transient_unit_set_properties(m, arg_property); + if (r < 0) + return r; + + if (arg_mount_what) { + r = sd_bus_message_append(m, "(sv)", "What", "s", arg_mount_what); + if (r < 0) + return r; + } + + if (arg_mount_type) { + r = sd_bus_message_append(m, "(sv)", "Type", "s", arg_mount_type); + if (r < 0) + return r; + } + + if (arg_mount_options) { + r = sd_bus_message_append(m, "(sv)", "Options", "s", arg_mount_options); + if (r < 0) + return r; + } + + if (arg_fsck) { + _cleanup_free_ char *fsck = NULL; + + r = unit_name_from_path_instance("systemd-fsck", arg_mount_what, ".service", &fsck); + if (r < 0) + return r; + + r = sd_bus_message_append(m, + "(sv)(sv)", + "Requires", "as", 1, fsck, + "After", "as", 1, fsck); + if (r < 0) + return r; + } + + return 0; +} + +static int transient_automount_set_properties(sd_bus_message *m) { + int r; + + assert(m); + + r = transient_unit_set_properties(m, arg_automount_property); + if (r < 0) + return r; + + if (arg_timeout_idle != USEC_INFINITY) { + r = sd_bus_message_append(m, "(sv)", "TimeoutIdleUSec", "t", arg_timeout_idle); + if (r < 0) + return r; + } + + return 0; +} + +static int start_transient_mount( + sd_bus *bus, + char **argv) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + _cleanup_free_ char *mount_unit = NULL; + int r; + + if (!arg_no_block) { + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_error_errno(r, "Could not watch jobs: %m"); + } + + r = unit_name_from_path(arg_mount_where, ".mount", &mount_unit); + if (r < 0) + return log_error_errno(r, "Failed to make mount unit name: %m"); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartTransientUnit"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); + if (r < 0) + return bus_log_create_error(r); + + /* Name and mode */ + r = sd_bus_message_append(m, "ss", mount_unit, "fail"); + if (r < 0) + return bus_log_create_error(r); + + /* Properties */ + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = transient_mount_set_properties(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + /* Auxiliary units */ + r = sd_bus_message_append(m, "a(sa(sv))", 0); + if (r < 0) + return bus_log_create_error(r); + + polkit_agent_open_if_enabled(); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "Failed to start transient mount unit: %s", bus_error_message(&error, r)); + + if (w) { + const char *object; + + r = sd_bus_message_read(reply, "o", &object); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_wait_for_jobs_one(w, object, arg_quiet); + if (r < 0) + return r; + } + + if (!arg_quiet) + log_info("Started unit %s%s%s for mount point: %s%s%s", + ansi_highlight(), mount_unit, ansi_normal(), + ansi_highlight(), arg_mount_where, ansi_normal()); + + return 0; +} + +static int start_transient_automount( + sd_bus *bus, + char **argv) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + _cleanup_free_ char *automount_unit = NULL, *mount_unit = NULL; + int r; + + if (!arg_no_block) { + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_error_errno(r, "Could not watch jobs: %m"); + } + + r = unit_name_from_path(arg_mount_where, ".automount", &automount_unit); + if (r < 0) + return log_error_errno(r, "Failed to make automount unit name: %m"); + + r = unit_name_from_path(arg_mount_where, ".mount", &mount_unit); + if (r < 0) + return log_error_errno(r, "Failed to make mount unit name: %m"); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartTransientUnit"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); + if (r < 0) + return bus_log_create_error(r); + + /* Name and mode */ + r = sd_bus_message_append(m, "ss", automount_unit, "fail"); + if (r < 0) + return bus_log_create_error(r); + + /* Properties */ + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = transient_automount_set_properties(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + /* Auxiliary units */ + r = sd_bus_message_open_container(m, 'a', "(sa(sv))"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'r', "sa(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "s", mount_unit); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = transient_mount_set_properties(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + polkit_agent_open_if_enabled(); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "Failed to start transient automount unit: %s", bus_error_message(&error, r)); + + if (w) { + const char *object; + + r = sd_bus_message_read(reply, "o", &object); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_wait_for_jobs_one(w, object, arg_quiet); + if (r < 0) + return r; + } + + if (!arg_quiet) + log_info("Started unit %s%s%s for mount point: %s%s%s", + ansi_highlight(), automount_unit, ansi_normal(), + ansi_highlight(), arg_mount_where, ansi_normal()); + + return 0; +} + +static int acquire_mount_type(struct udev_device *d) { + const char *v; + + assert(d); + + if (arg_mount_type) + return 0; + + v = udev_device_get_property_value(d, "ID_FS_TYPE"); + if (isempty(v)) + return 0; + + arg_mount_type = strdup(v); + if (!arg_mount_type) + return log_oom(); + + log_debug("Discovered type=%s", arg_mount_type); + return 1; +} + +static int acquire_mount_options(struct udev_device *d) { + const char *v; + + if (arg_mount_options) + return 0; + + v = udev_device_get_property_value(d, "SYSTEMD_MOUNT_OPTIONS"); + if (isempty(v)) + return 0; + + arg_mount_options = strdup(v); + if (!arg_mount_options) + return log_oom(); + + log_debug("Discovered options=%s", arg_mount_options); + return 1; +} + +static const char *get_model(struct udev_device *d) { + const char *model; + + assert(d); + + model = udev_device_get_property_value(d, "ID_MODEL_FROM_DATABASE"); + if (model) + return model; + + return udev_device_get_property_value(d, "ID_MODEL"); +} + +static const char* get_label(struct udev_device *d) { + const char *label; + + assert(d); + + label = udev_device_get_property_value(d, "ID_FS_LABEL"); + if (label) + return label; + + return udev_device_get_property_value(d, "ID_PART_ENTRY_NAME"); +} + +static int acquire_mount_where(struct udev_device *d) { + const char *v; + + if (arg_mount_where) + return 0; + + v = udev_device_get_property_value(d, "SYSTEMD_MOUNT_WHERE"); + if (isempty(v)) { + _cleanup_free_ char *escaped = NULL; + const char *name; + + name = get_label(d); + if (!name) + name = get_model(d); + if (!name) { + const char *dn; + + dn = udev_device_get_devnode(d); + if (!dn) + return 0; + + name = basename(dn); + } + + escaped = xescape(name, "\\"); + if (!filename_is_valid(escaped)) + return 0; + + arg_mount_where = strjoin("/run/media/system/", escaped, NULL); + } else + arg_mount_where = strdup(v); + + if (!arg_mount_where) + return log_oom(); + + log_debug("Discovered where=%s", arg_mount_where); + return 1; +} + +static int acquire_description(struct udev_device *d) { + const char *model, *label; + + if (arg_description) + return 0; + + model = get_model(d); + + label = get_label(d); + if (!label) + label = udev_device_get_property_value(d, "ID_PART_ENTRY_NUMBER"); + + if (model && label) + arg_description = strjoin(model, " ", label, NULL); + else if (label) + arg_description = strdup(label); + else if (model) + arg_description = strdup(model); + else + return 0; + + if (!arg_description) + return log_oom(); + + log_debug("Discovered description=%s", arg_description); + return 1; +} + +static int acquire_removable(struct udev_device *d) { + const char *v; + + /* Shortcut this if there's no reason to check it */ + if (arg_action != ACTION_DEFAULT && arg_timeout_idle_set && arg_bind_device >= 0) + return 0; + + for (;;) { + v = udev_device_get_sysattr_value(d, "removable"); + if (v) + break; + + d = udev_device_get_parent(d); + if (!d) + return 0; + + if (!streq_ptr(udev_device_get_subsystem(d), "block")) + return 0; + } + + if (parse_boolean(v) <= 0) + return 0; + + log_debug("Discovered removable device."); + + if (arg_action == ACTION_DEFAULT) { + log_debug("Automatically turning on automount."); + arg_action = ACTION_AUTOMOUNT; + } + + if (!arg_timeout_idle_set) { + log_debug("Setting idle timeout to 1s."); + arg_timeout_idle = USEC_PER_SEC; + } + + if (arg_bind_device < 0) { + log_debug("Binding automount unit to device."); + arg_bind_device = true; + } + + return 1; +} + +static int discover_device(void) { + _cleanup_udev_device_unref_ struct udev_device *d = NULL; + _cleanup_udev_unref_ struct udev *udev = NULL; + struct stat st; + const char *v; + int r; + + if (!arg_discover) + return 0; + + if (!is_device_path(arg_mount_what)) { + log_error("Discovery only supported for block devices, don't know what to do."); + return -EINVAL; + } + + if (stat(arg_mount_what, &st) < 0) + return log_error_errno(errno, "Can't stat %s: %m", arg_mount_what); + + if (!S_ISBLK(st.st_mode)) { + log_error("Path %s is not a block device, don't know what to do.", arg_mount_what); + return -ENOTBLK; + } + + udev = udev_new(); + if (!udev) + return log_oom(); + + d = udev_device_new_from_devnum(udev, 'b', st.st_rdev); + if (!d) + return log_oom(); + + v = udev_device_get_property_value(d, "ID_FS_USAGE"); + if (!streq_ptr(v, "filesystem")) { + log_error("%s does not contain a file system.", arg_mount_what); + return -EINVAL; + } + + r = acquire_mount_type(d); + if (r < 0) + return r; + + r = acquire_mount_options(d); + if (r < 0) + return r; + + r = acquire_mount_where(d); + if (r < 0) + return r; + + r = acquire_description(d); + if (r < 0) + return r; + + r = acquire_removable(d); + if (r < 0) + return r; + + return 0; +} + +enum { + COLUMN_NODE, + COLUMN_PATH, + COLUMN_MODEL, + COLUMN_WWN, + COLUMN_FSTYPE, + COLUMN_LABEL, + COLUMN_UUID, + _COLUMN_MAX, +}; + +struct item { + char* columns[_COLUMN_MAX]; +}; + +static int compare_item(const void *a, const void *b) { + const struct item *x = a, *y = b; + + if (x->columns[COLUMN_NODE] == y->columns[COLUMN_NODE]) + return 0; + if (!x->columns[COLUMN_NODE]) + return 1; + if (!y->columns[COLUMN_NODE]) + return -1; + + return path_compare(x->columns[COLUMN_NODE], y->columns[COLUMN_NODE]); +} + +static int list_devices(void) { + + static const char * const titles[_COLUMN_MAX] = { + [COLUMN_NODE] = "NODE", + [COLUMN_PATH] = "PATH", + [COLUMN_MODEL] = "MODEL", + [COLUMN_WWN] = "WWN", + [COLUMN_FSTYPE] = "TYPE", + [COLUMN_LABEL] = "LABEL", + [COLUMN_UUID] = "UUID" + }; + + _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL; + _cleanup_udev_unref_ struct udev *udev = NULL; + struct udev_list_entry *item = NULL, *first = NULL; + size_t n_allocated = 0, n = 0, i; + size_t column_width[_COLUMN_MAX]; + struct item *items = NULL; + unsigned c; + int r; + + for (c = 0; c < _COLUMN_MAX; c++) + column_width[c] = strlen(titles[c]); + + udev = udev_new(); + if (!udev) + return log_oom(); + + e = udev_enumerate_new(udev); + if (!e) + return log_oom(); + + r = udev_enumerate_add_match_subsystem(e, "block"); + if (r < 0) + return log_error_errno(r, "Failed to add block match: %m"); + + r = udev_enumerate_add_match_property(e, "ID_FS_USAGE", "filesystem"); + if (r < 0) + return log_error_errno(r, "Failed to add property match: %m"); + + r = udev_enumerate_scan_devices(e); + if (r < 0) + return log_error_errno(r, "Failed to scan devices: %m"); + + first = udev_enumerate_get_list_entry(e); + udev_list_entry_foreach(item, first) { + _cleanup_udev_device_unref_ struct udev_device *d; + struct item *j; + + d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); + if (!d) { + r = log_oom(); + goto finish; + } + + if (!GREEDY_REALLOC0(items, n_allocated, n+1)) { + r = log_oom(); + goto finish; + } + + j = items + n++; + + for (c = 0; c < _COLUMN_MAX; c++) { + const char *x; + size_t k; + + switch (c) { + + case COLUMN_NODE: + x = udev_device_get_devnode(d); + break; + + case COLUMN_PATH: + x = udev_device_get_property_value(d, "ID_PATH"); + break; + + case COLUMN_MODEL: + x = get_model(d); + break; + + case COLUMN_WWN: + x = udev_device_get_property_value(d, "ID_WWN"); + break; + + case COLUMN_FSTYPE: + x = udev_device_get_property_value(d, "ID_FS_TYPE"); + break; + + case COLUMN_LABEL: + x = get_label(d); + break; + + case COLUMN_UUID: + x = udev_device_get_property_value(d, "ID_FS_UUID"); + break; + } + + if (isempty(x)) + continue; + + j->columns[c] = strdup(x); + if (!j->columns[c]) { + r = log_oom(); + goto finish; + } + + k = strlen(x); + if (k > column_width[c]) + column_width[c] = k; + } + } + + if (n == 0) { + log_info("No devices found."); + goto finish; + } + + qsort_safe(items, n, sizeof(struct item), compare_item); + + pager_open(arg_no_pager, false); + + fputs(ansi_underline(), stdout); + for (c = 0; c < _COLUMN_MAX; c++) { + if (c > 0) + fputc(' ', stdout); + + printf("%-*s", (int) column_width[c], titles[c]); + } + fputs(ansi_normal(), stdout); + fputc('\n', stdout); + + for (i = 0; i < n; i++) { + for (c = 0; c < _COLUMN_MAX; c++) { + if (c > 0) + fputc(' ', stdout); + + printf("%-*s", (int) column_width[c], strna(items[i].columns[c])); + } + fputc('\n', stdout); + } + + r = 0; + +finish: + for (i = 0; i < n; i++) + for (c = 0; c < _COLUMN_MAX; c++) + free(items[i].columns[c]); + + free(items); + return r; +} + +int main(int argc, char* argv[]) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (arg_action == ACTION_LIST) { + r = list_devices(); + goto finish; + } + + r = discover_device(); + if (r < 0) + goto finish; + if (!arg_mount_where) { + log_error("Can't figure out where to mount %s.", arg_mount_what); + r = -EINVAL; + goto finish; + } + + path_kill_slashes(arg_mount_where); + + if (path_equal(arg_mount_where, "/")) { + log_error("Refusing to operate on root directory."); + r = -EINVAL; + goto finish; + } + + if (!path_is_safe(arg_mount_where)) { + log_error("Path is contains unsafe components."); + r = -EINVAL; + goto finish; + } + + if (streq_ptr(arg_mount_type, "auto")) + arg_mount_type = mfree(arg_mount_type); + if (streq_ptr(arg_mount_options, "defaults")) + arg_mount_options = mfree(arg_mount_options); + + if (!is_device_path(arg_mount_what)) + arg_fsck = false; + + if (arg_fsck && arg_mount_type && arg_transport == BUS_TRANSPORT_LOCAL) { + r = fsck_exists(arg_mount_type); + if (r < 0) + log_warning_errno(r, "Couldn't determine whether fsck for %s exists, proceeding anyway.", arg_mount_type); + else if (r == 0) { + log_debug("Disabling file system check as fsck for %s doesn't exist.", arg_mount_type); + arg_fsck = false; /* fsck doesn't exist, let's not attempt it */ + } + } + + r = bus_connect_transport_systemd(arg_transport, arg_host, arg_user, &bus); + if (r < 0) { + log_error_errno(r, "Failed to create bus connection: %m"); + goto finish; + } + + switch (arg_action) { + + case ACTION_MOUNT: + case ACTION_DEFAULT: + r = start_transient_mount(bus, argv + optind); + break; + + case ACTION_AUTOMOUNT: + r = start_transient_automount(bus, argv + optind); + break; + + default: + assert_not_reached("Unexpected action."); + } + +finish: + bus = sd_bus_flush_close_unref(bus); + + pager_close(); + + free(arg_mount_what); + free(arg_mount_where); + free(arg_mount_type); + free(arg_mount_options); + free(arg_description); + strv_free(arg_property); + strv_free(arg_automount_property); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/network/networkctl.c b/src/network/networkctl.c index d2df9b7560..6f7f41bf7d 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -122,7 +122,7 @@ static void setup_state_to_color(const char *state, const char **on, const char } else if (streq_ptr(state, "configuring")) { *on = ansi_highlight_yellow(); *off = ansi_normal(); - } else if (streq_ptr(state, "failed") || streq_ptr(state, "linger")) { + } else if (STRPTR_IN_SET(state, "failed", "linger")) { *on = ansi_highlight_red(); *off = ansi_normal(); } else diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 5498e352d8..ed52d5e42d 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -571,6 +571,21 @@ int address_configure( address->flags |= IFA_F_PERMANENT; + if (address->home_address) + address->flags |= IFA_F_HOMEADDRESS; + + if (address->duplicate_address_detection) + address->flags |= IFA_F_NODAD; + + if (address->manage_temporary_address) + address->flags |= IFA_F_MANAGETEMPADDR; + + if (address->prefix_route) + address->flags |= IFA_F_NOPREFIXROUTE; + + if (address->autojoin) + address->flags |= IFA_F_MCAUTOJOIN; + r = sd_rtnl_message_addr_set_flags(req, (address->flags & 0xff)); if (r < 0) return log_error_errno(r, "Could not set flags: %m"); @@ -856,6 +871,50 @@ int config_parse_lifetime(const char *unit, return 0; } +int config_parse_address_flags(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) { + Network *network = userdata; + _cleanup_address_free_ Address *n = NULL; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = address_new_static(network, section_line, &n); + if (r < 0) + return r; + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse address flag, ignoring: %s", rvalue); + return 0; + } + + if (streq(lvalue, "HomeAddress")) + n->home_address = r; + else if (streq(lvalue, "DuplicateAddressDetection")) + n->duplicate_address_detection = r; + else if (streq(lvalue, "ManageTemporaryAddress")) + n->manage_temporary_address = r; + else if (streq(lvalue, "PrefixRoute")) + n->prefix_route = r; + else if (streq(lvalue, "AutoJoin")) + n->autojoin = r; + + return 0; +} + bool address_is_ready(const Address *a) { assert(a); diff --git a/src/network/networkd-address.h b/src/network/networkd-address.h index 03c4bea7c6..bc3b4fc7f3 100644 --- a/src/network/networkd-address.h +++ b/src/network/networkd-address.h @@ -53,6 +53,11 @@ struct Address { union in_addr_union in_addr_peer; bool ip_masquerade_done:1; + bool duplicate_address_detection; + bool manage_temporary_address; + bool home_address; + bool prefix_route; + bool autojoin; LIST_FIELDS(Address, addresses); }; @@ -77,3 +82,4 @@ int config_parse_address(const char *unit, const char *filename, unsigned line, int config_parse_broadcast(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); int config_parse_label(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); int config_parse_lifetime(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); +int config_parse_address_flags(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); diff --git a/src/network/networkd-brvlan.c b/src/network/networkd-brvlan.c index 8bc330ebae..18ecd86858 100644 --- a/src/network/networkd-brvlan.c +++ b/src/network/networkd-brvlan.c @@ -257,6 +257,24 @@ static int parse_vid_range(const char *rvalue, uint16_t *vid, uint16_t *vid_end) return r; } +int config_parse_brvlan_pvid(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) { + Network *network = userdata; + int r; + uint16_t pvid; + r = parse_vlanid(rvalue, &pvid); + if (r < 0) + return r; + + network->pvid = pvid; + network->use_br_vlan = true; + + return 0; +} + int config_parse_brvlan_vlan(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, @@ -288,6 +306,7 @@ int config_parse_brvlan_vlan(const char *unit, const char *filename, for (; vid <= vid_end; vid++) set_bit(vid, network->br_vid_bitmap); } + network->use_br_vlan = true; return 0; } @@ -325,5 +344,6 @@ int config_parse_brvlan_untagged(const char *unit, const char *filename, set_bit(vid, network->br_untagged_bitmap); } } + network->use_br_vlan = true; return 0; } diff --git a/src/network/networkd-brvlan.h b/src/network/networkd-brvlan.h index 6aa6883bfc..b37633f94f 100644 --- a/src/network/networkd-brvlan.h +++ b/src/network/networkd-brvlan.h @@ -25,5 +25,6 @@ typedef struct Link Link; int br_vlan_configure(Link *link, uint16_t pvid, uint32_t *br_vid_bitmap, uint32_t *br_untagged_bitmap); +int config_parse_brvlan_pvid(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); int config_parse_brvlan_vlan(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); int config_parse_brvlan_untagged(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); diff --git a/src/network/networkd-conf.c b/src/network/networkd-conf.c index c03e2b2ebf..49bb8c18f6 100644 --- a/src/network/networkd-conf.c +++ b/src/network/networkd-conf.c @@ -29,7 +29,7 @@ int manager_parse_config_file(Manager *m) { assert(m); - return config_parse_many(PKGSYSCONFDIR "/networkd.conf", + return config_parse_many_nulstr(PKGSYSCONFDIR "/networkd.conf", CONF_PATHS_NULSTR("systemd/networkd.conf.d"), "DHCP\0", config_item_perf_lookup, networkd_gperf_lookup, diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 12fb8e3fce..76d3d132ea 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -95,6 +95,7 @@ static int link_set_dhcp_routes(Link *link) { route_gw->scope = RT_SCOPE_LINK; route_gw->protocol = RTPROT_DHCP; route_gw->priority = link->network->dhcp_route_metric; + route_gw->table = link->network->dhcp_route_table; r = route_configure(route_gw, link, dhcp4_route_handler); if (r < 0) @@ -106,6 +107,7 @@ static int link_set_dhcp_routes(Link *link) { route->gw.in = gateway; route->prefsrc.in = address; route->priority = link->network->dhcp_route_metric; + route->table = link->network->dhcp_route_table; r = route_configure(route, link, dhcp4_route_handler); if (r < 0) { @@ -136,6 +138,7 @@ static int link_set_dhcp_routes(Link *link) { assert_se(sd_dhcp_route_get_destination(static_routes[i], &route->dst.in) >= 0); assert_se(sd_dhcp_route_get_destination_prefix_length(static_routes[i], &route->dst_prefixlen) >= 0); route->priority = link->network->dhcp_route_metric; + route->table = link->network->dhcp_route_table; r = route_configure(route, link, dhcp4_route_handler); if (r < 0) diff --git a/src/network/networkd-fdb.c b/src/network/networkd-fdb.c index be8aebee2d..ed5a47589e 100644 --- a/src/network/networkd-fdb.c +++ b/src/network/networkd-fdb.c @@ -107,20 +107,28 @@ int fdb_entry_configure(Link *link, FdbEntry *fdb_entry) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; sd_netlink *rtnl; int r; + uint8_t flags; + Bridge *bridge; assert(link); + assert(link->network); assert(link->manager); assert(fdb_entry); rtnl = link->manager->rtnl; + bridge = BRIDGE(link->network->bridge); /* create new RTM message */ r = sd_rtnl_message_new_neigh(rtnl, &req, RTM_NEWNEIGH, link->ifindex, PF_BRIDGE); if (r < 0) return rtnl_log_create_error(r); - /* only NTF_SELF flag supported. */ - r = sd_rtnl_message_neigh_set_flags(req, NTF_SELF); + if (bridge) + flags = NTF_MASTER; + else + flags = NTF_SELF; + + r = sd_rtnl_message_neigh_set_flags(req, flags); if (r < 0) return rtnl_log_create_error(r); diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 82f56158be..aefe7335b9 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -256,12 +256,8 @@ static int link_enable_ipv6(Link *link) { r = write_string_file(p, one_zero(disabled), WRITE_STRING_FILE_VERIFY_ON_FAILURE); if (r < 0) log_link_warning_errno(link, r, "Cannot %s IPv6 for interface %s: %m", disabled ? "disable" : "enable", link->ifname); - else { - if (disabled) - log_link_info(link, "IPv6 disabled for interface: %m"); - else - log_link_info(link, "IPv6 enabled for interface: %m"); - } + else + log_link_info(link, "IPv6 %sd for interface: %m", enable_disable(!disabled)); return 0; } @@ -518,13 +514,12 @@ static void link_free(Link *link) { sd_lldp_unref(link->lldp); free(link->lldp_file); + ndisc_flush(link); + sd_ipv4ll_unref(link->ipv4ll); sd_dhcp6_client_unref(link->dhcp6_client); sd_ndisc_unref(link->ndisc); - set_free_free(link->ndisc_rdnss); - set_free_free(link->ndisc_dnssl); - if (link->manager) hashmap_remove(link->manager->links, INT_TO_PTR(link->ifindex)); @@ -946,6 +941,19 @@ static int link_push_ntp_to_dhcp_server(Link *link, sd_dhcp_server *s) { return sd_dhcp_server_set_ntp(s, addresses, n_addresses); } +static int link_set_bridge_fdb(Link *link) { + FdbEntry *fdb_entry; + int r; + + LIST_FOREACH(static_fdb_entries, fdb_entry, link->network->static_fdb_entries) { + r = fdb_entry_configure(link, fdb_entry); + if (r < 0) + return log_link_error_errno(link, r, "Failed to add MAC entry to static MAC table: %m"); + } + + return 0; +} + static int link_enter_set_addresses(Link *link) { Address *ad; int r; @@ -954,6 +962,10 @@ static int link_enter_set_addresses(Link *link) { assert(link->network); assert(link->state != _LINK_STATE_INVALID); + r = link_set_bridge_fdb(link); + if (r < 0) + return r; + link_set_state(link, LINK_STATE_SETTING_ADDRESSES); LIST_FOREACH(addresses, ad, link->network->static_addresses) { @@ -1123,21 +1135,6 @@ static int link_set_bridge_vlan(Link *link) { return r; } -static int link_set_bridge_fdb(Link *link) { - FdbEntry *fdb_entry; - int r = 0; - - LIST_FOREACH(static_fdb_entries, fdb_entry, link->network->static_fdb_entries) { - r = fdb_entry_configure(link, fdb_entry); - if (r < 0) { - log_link_error_errno(link, r, "Failed to add MAC entry to static MAC table: %m"); - break; - } - } - - return r; -} - static int link_set_proxy_arp(Link *link) { const char *p = NULL; int r; @@ -1318,6 +1315,65 @@ int link_set_mtu(Link *link, uint32_t mtu) { return 0; } +static int set_flags_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { + _cleanup_link_unref_ Link *link = userdata; + int r; + + assert(m); + assert(link); + assert(link->ifname); + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0) + log_link_warning_errno(link, r, "Could not set link flags: %m"); + + return 1; +} + +static int link_set_flags(Link *link) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + unsigned ifi_change = 0; + unsigned ifi_flags = 0; + int r; + + assert(link); + assert(link->manager); + assert(link->manager->rtnl); + + if (link->flags & IFF_LOOPBACK) + return 0; + + if (!link->network) + return 0; + + if (link->network->arp < 0) + return 0; + + r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m"); + + if (link->network->arp >= 0) { + ifi_change |= IFF_NOARP; + ifi_flags |= link->network->arp ? 0 : IFF_NOARP; + } + + r = sd_rtnl_message_link_set_flags(req, ifi_flags, ifi_change); + if (r < 0) + return log_link_error_errno(link, r, "Could not set link flags: %m"); + + r = sd_netlink_call_async(link->manager->rtnl, req, set_flags_handler, link, 0, NULL); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + return 0; +} + static int link_set_bridge(Link *link) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; int r; @@ -1732,6 +1788,31 @@ static int link_down(Link *link) { return 0; } +static int link_up_can(Link *link) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(link); + + log_link_debug(link, "Bringing CAN link up"); + + r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m"); + + r = sd_rtnl_message_link_set_flags(req, IFF_UP, IFF_UP); + if (r < 0) + return log_link_error_errno(link, r, "Could not set link flags: %m"); + + r = sd_netlink_call_async(link->manager->rtnl, req, link_up_handler, link, 0, NULL); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + return 0; +} + static int link_handle_bound_to_list(Link *link) { Link *l; Iterator i; @@ -2005,7 +2086,8 @@ static int link_joined(Link *link) { log_link_error_errno(link, r, "Could not set bridge message: %m"); } - if (link->network->bridge || streq_ptr("bridge", link->kind)) { + if (link->network->use_br_vlan && + (link->network->bridge || streq_ptr("bridge", link->kind))) { r = link_set_bridge_vlan(link); if (r < 0) log_link_error_errno(link, r, "Could not set bridge vlan: %m"); @@ -2318,6 +2400,37 @@ static int link_drop_foreign_config(Link *link) { return 0; } +static int link_drop_config(Link *link) { + Address *address; + Route *route; + Iterator i; + int r; + + SET_FOREACH(address, link->addresses, i) { + /* we consider IPv6LL addresses to be managed by the kernel */ + if (address->family == AF_INET6 && in_addr_is_link_local(AF_INET6, &address->in_addr) == 1) + continue; + + r = address_remove(address, link, link_address_remove_handler); + if (r < 0) + return r; + } + + SET_FOREACH(route, link->routes, i) { + /* do not touch routes managed by the kernel */ + if (route->protocol == RTPROT_KERNEL) + continue; + + r = route_remove(route, link, link_route_remove_handler); + if (r < 0) + return r; + } + + ndisc_flush(link); + + return 0; +} + static int link_update_lldp(Link *link) { int r; @@ -2346,6 +2459,19 @@ static int link_configure(Link *link) { assert(link->network); assert(link->state == LINK_STATE_PENDING); + if (streq_ptr(link->kind, "vcan")) { + + if (!(link->flags & IFF_UP)) { + r = link_up_can(link); + if (r < 0) { + link_enter_failed(link); + return r; + } + } + + return 0; + } + /* Drop foreign config, but ignore loopback or critical devices. * We do not want to remove loopback address or addresses used for root NFS. */ if (!(link->flags & IFF_LOOPBACK) && !(link->network->dhcp_critical)) { @@ -2354,10 +2480,6 @@ static int link_configure(Link *link) { return r; } - r = link_set_bridge_fdb(link); - if (r < 0) - return r; - r = link_set_proxy_arp(link); if (r < 0) return r; @@ -2386,6 +2508,10 @@ static int link_configure(Link *link) { if (r < 0) return r; + r = link_set_flags(link); + if (r < 0) + return r; + if (link_ipv4ll_enabled(link)) { r = ipv4ll_configure(link); if (r < 0) @@ -2716,17 +2842,17 @@ network_file_fail: if (dhcp4_address) { r = in_addr_from_string(AF_INET, dhcp4_address, &address); if (r < 0) { - log_link_debug_errno(link, r, "Falied to parse DHCPv4 address %s: %m", dhcp4_address); + log_link_debug_errno(link, r, "Failed to parse DHCPv4 address %s: %m", dhcp4_address); goto dhcp4_address_fail; } r = sd_dhcp_client_new(&link->dhcp_client); if (r < 0) - return log_link_error_errno(link, r, "Falied to create DHCPv4 client: %m"); + return log_link_error_errno(link, r, "Failed to create DHCPv4 client: %m"); r = sd_dhcp_client_set_request_address(link->dhcp_client, &address.in); if (r < 0) - return log_link_error_errno(link, r, "Falied to set initial DHCPv4 address %s: %m", dhcp4_address); + return log_link_error_errno(link, r, "Failed to set initial DHCPv4 address %s: %m", dhcp4_address); } dhcp4_address_fail: @@ -2734,17 +2860,17 @@ dhcp4_address_fail: if (ipv4ll_address) { r = in_addr_from_string(AF_INET, ipv4ll_address, &address); if (r < 0) { - log_link_debug_errno(link, r, "Falied to parse IPv4LL address %s: %m", ipv4ll_address); + log_link_debug_errno(link, r, "Failed to parse IPv4LL address %s: %m", ipv4ll_address); goto ipv4ll_address_fail; } r = sd_ipv4ll_new(&link->ipv4ll); if (r < 0) - return log_link_error_errno(link, r, "Falied to create IPv4LL client: %m"); + return log_link_error_errno(link, r, "Failed to create IPv4LL client: %m"); r = sd_ipv4ll_set_address(link->ipv4ll, &address.in); if (r < 0) - return log_link_error_errno(link, r, "Falied to set initial IPv4LL address %s: %m", ipv4ll_address); + return log_link_error_errno(link, r, "Failed to set initial IPv4LL address %s: %m", ipv4ll_address); } ipv4ll_address_fail: @@ -2864,6 +2990,17 @@ static int link_carrier_lost(Link *link) { return r; } + r = link_drop_config(link); + if (r < 0) + return r; + + if (!IN_SET(link->state, LINK_STATE_UNMANAGED, LINK_STATE_PENDING)) { + log_link_debug(link, "State is %s, dropping config", link_state_to_string(link->state)); + r = link_drop_foreign_config(link); + if (r < 0) + return r; + } + r = link_handle_bound_by_list(link); if (r < 0) return r; diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 2809b1fe0b..77f72d070e 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -186,8 +186,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_unref); #define log_link_full(link, level, error, ...) \ ({ \ - Link *_l = (link); \ - _l ? log_object_internal(level, error, __FILE__, __LINE__, __func__, "INTERFACE=", _l->ifname, ##__VA_ARGS__) : \ + const Link *_l = (link); \ + _l ? log_object_internal(level, error, __FILE__, __LINE__, __func__, "INTERFACE=", _l->ifname, NULL, NULL, ##__VA_ARGS__) : \ log_internal(level, error, __FILE__, __LINE__, __func__, ##__VA_ARGS__); \ }) \ diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index d9c18b32a5..4853791aa5 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -57,6 +57,8 @@ static void ndisc_router_process_default(Link *link, sd_ndisc_router *rt) { unsigned preference; usec_t time_now; int r; + Address *address; + Iterator i; assert(link); assert(rt); @@ -75,6 +77,32 @@ static void ndisc_router_process_default(Link *link, sd_ndisc_router *rt) { return; } + SET_FOREACH(address, link->addresses, i) { + if (!memcmp(&gateway, &address->in_addr.in6, + sizeof(address->in_addr.in6))) { + char buffer[INET6_ADDRSTRLEN]; + + log_link_debug(link, "No NDisc route added, gateway %s matches local address", + inet_ntop(AF_INET6, + &address->in_addr.in6, + buffer, sizeof(buffer))); + return; + } + } + + SET_FOREACH(address, link->addresses_foreign, i) { + if (!memcmp(&gateway, &address->in_addr.in6, + sizeof(address->in_addr.in6))) { + char buffer[INET6_ADDRSTRLEN]; + + log_link_debug(link, "No NDisc route added, gateway %s matches local address", + inet_ntop(AF_INET6, + &address->in_addr.in6, + buffer, sizeof(buffer))); + return; + } + } + r = sd_ndisc_router_get_preference(rt, &preference); if (r < 0) { log_link_warning_errno(link, r, "Failed to get default router preference from RA: %m"); @@ -94,7 +122,7 @@ static void ndisc_router_process_default(Link *link, sd_ndisc_router *rt) { } route->family = AF_INET6; - route->table = RT_TABLE_MAIN; + route->table = link->network->ipv6_accept_ra_route_table; route->protocol = RTPROT_RA; route->pref = preference; route->gw.in6 = gateway; @@ -214,7 +242,7 @@ static void ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) } route->family = AF_INET6; - route->table = RT_TABLE_MAIN; + route->table = link->network->ipv6_accept_ra_route_table; route->protocol = RTPROT_RA; route->flags = RTM_F_PREFIX; route->dst_prefixlen = prefixlen; @@ -285,7 +313,7 @@ static void ndisc_router_process_route(Link *link, sd_ndisc_router *rt) { } route->family = AF_INET6; - route->table = RT_TABLE_MAIN; + route->table = link->network->ipv6_accept_ra_route_table; route->protocol = RTPROT_RA; route->pref = preference; route->gw.in6 = gateway; @@ -652,13 +680,22 @@ void ndisc_vacuum(Link *link) { SET_FOREACH(r, link->ndisc_rdnss, i) if (r->valid_until < time_now) { - (void) set_remove(link->ndisc_rdnss, r); + free(set_remove(link->ndisc_rdnss, r)); link_dirty(link); } SET_FOREACH(d, link->ndisc_dnssl, i) if (d->valid_until < time_now) { - (void) set_remove(link->ndisc_dnssl, d); + free(set_remove(link->ndisc_dnssl, d)); link_dirty(link); } } + +void ndisc_flush(Link *link) { + assert(link); + + /* Removes all RDNSS and DNSSL entries, without exception */ + + link->ndisc_rdnss = set_free_free(link->ndisc_rdnss); + link->ndisc_dnssl = set_free_free(link->ndisc_dnssl); +} diff --git a/src/network/networkd-ndisc.h b/src/network/networkd-ndisc.h index 2002f55107..127126190e 100644 --- a/src/network/networkd-ndisc.h +++ b/src/network/networkd-ndisc.h @@ -37,3 +37,4 @@ static inline char* NDISC_DNSSL_DOMAIN(const NDiscDNSSL *n) { int ndisc_configure(Link *link); void ndisc_vacuum(Link *link); +void ndisc_flush(Link *link); diff --git a/src/network/networkd-netdev-bond.c b/src/network/networkd-netdev-bond.c index 7913b0088e..46d1669337 100644 --- a/src/network/networkd-netdev-bond.c +++ b/src/network/networkd-netdev-bond.c @@ -268,13 +268,13 @@ static int netdev_bond_fill_message_create(NetDev *netdev, Link *link, sd_netlin if (b->arp_all_targets != _NETDEV_BOND_ARP_ALL_TARGETS_INVALID) { r = sd_netlink_message_append_u32(m, IFLA_BOND_ARP_ALL_TARGETS, b->arp_all_targets); if (r < 0) - return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_ARP_VALIDATE attribute: %m"); + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_ARP_ALL_TARGETS attribute: %m"); } if (b->primary_reselect != _NETDEV_BOND_PRIMARY_RESELECT_INVALID) { - r = sd_netlink_message_append_u32(m, IFLA_BOND_ARP_ALL_TARGETS, b->primary_reselect); + r = sd_netlink_message_append_u8(m, IFLA_BOND_PRIMARY_RESELECT, b->primary_reselect); if (r < 0) - return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_ARP_ALL_TARGETS attribute: %m"); + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_PRIMARY_RESELECT attribute: %m"); } if (b->resend_igmp <= RESEND_IGMP_MAX) { diff --git a/src/network/networkd-netdev-bridge.c b/src/network/networkd-netdev-bridge.c index a5085d2b19..002ad94210 100644 --- a/src/network/networkd-netdev-bridge.c +++ b/src/network/networkd-netdev-bridge.c @@ -39,7 +39,7 @@ static int netdev_bridge_set_handler(sd_netlink *rtnl, sd_netlink_message *m, vo return 1; } - log_netdev_debug(netdev, "Bridge parametres set success"); + log_netdev_debug(netdev, "Bridge parameters set success"); return 1; } @@ -90,6 +90,24 @@ static int netdev_bridge_post_create(NetDev *netdev, Link *link, sd_netlink_mess return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_MAX_AGE attribute: %m"); } + if (b->ageing_time > 0) { + r = sd_netlink_message_append_u32(req, IFLA_BR_AGEING_TIME, usec_to_jiffies(b->ageing_time)); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_AGEING_TIME attribute: %m"); + } + + if (b->priority > 0) { + r = sd_netlink_message_append_u16(req, IFLA_BR_PRIORITY, b->priority); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_PRIORITY attribute: %m"); + } + + if (b->default_pvid > 0) { + r = sd_netlink_message_append_u16(req, IFLA_BR_VLAN_DEFAULT_PVID, b->default_pvid); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_VLAN_DEFAULT_PVID attribute: %m"); + } + if (b->mcast_querier >= 0) { r = sd_netlink_message_append_u8(req, IFLA_BR_MCAST_QUERIER, b->mcast_querier); if (r < 0) @@ -108,6 +126,12 @@ static int netdev_bridge_post_create(NetDev *netdev, Link *link, sd_netlink_mess return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_VLAN_FILTERING attribute: %m"); } + if (b->stp >= 0) { + r = sd_netlink_message_append_u32(req, IFLA_BR_STP_STATE, b->stp); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_STP_STATE attribute: %m"); + } + r = sd_netlink_message_close_container(req); if (r < 0) return log_netdev_error_errno(netdev, r, "Could not append IFLA_LINKINFO attribute: %m"); @@ -135,6 +159,7 @@ static void bridge_init(NetDev *n) { b->mcast_querier = -1; b->mcast_snooping = -1; b->vlan_filtering = -1; + b->stp = -1; } const NetDevVTable bridge_vtable = { diff --git a/src/network/networkd-netdev-bridge.h b/src/network/networkd-netdev-bridge.h index a637aea0a3..53f72f1ea5 100644 --- a/src/network/networkd-netdev-bridge.h +++ b/src/network/networkd-netdev-bridge.h @@ -27,10 +27,14 @@ typedef struct Bridge { int mcast_querier; int mcast_snooping; int vlan_filtering; + int stp; + uint16_t priority; + uint16_t default_pvid; usec_t forward_delay; usec_t hello_time; usec_t max_age; + usec_t ageing_time; } Bridge; DEFINE_NETDEV_CAST(BRIDGE, Bridge); diff --git a/src/network/networkd-netdev-gperf.gperf b/src/network/networkd-netdev-gperf.gperf index 9d69f61376..323eaa8032 100644 --- a/src/network/networkd-netdev-gperf.gperf +++ b/src/network/networkd-netdev-gperf.gperf @@ -63,8 +63,13 @@ VXLAN.L2MissNotification, config_parse_bool, 0, VXLAN.L3MissNotification, config_parse_bool, 0, offsetof(VxLan, l3miss) VXLAN.RouteShortCircuit, config_parse_bool, 0, offsetof(VxLan, route_short_circuit) VXLAN.UDPCheckSum, config_parse_bool, 0, offsetof(VxLan, udpcsum) +VXLAN.UDPChecksum, config_parse_bool, 0, offsetof(VxLan, udpcsum) VXLAN.UDP6ZeroCheckSumRx, config_parse_bool, 0, offsetof(VxLan, udp6zerocsumrx) +VXLAN.UDP6ZeroChecksumRx, config_parse_bool, 0, offsetof(VxLan, udp6zerocsumrx) VXLAN.UDP6ZeroCheckSumTx, config_parse_bool, 0, offsetof(VxLan, udp6zerocsumtx) +VXLAN.UDP6ZeroChecksumTx, config_parse_bool, 0, offsetof(VxLan, udp6zerocsumtx) +VXLAN.RemoteChecksumTx, config_parse_bool, 0, offsetof(VxLan, remote_csum_tx) +VXLAN.RemoteChecksumRx, config_parse_bool, 0, offsetof(VxLan, remote_csum_rx) VXLAN.FDBAgeingSec, config_parse_sec, 0, offsetof(VxLan, fdb_ageing) VXLAN.GroupPolicyExtension, config_parse_bool, 0, offsetof(VxLan, group_policy) VXLAN.MaximumFDBEntries, config_parse_unsigned, 0, offsetof(VxLan, max_fdb) @@ -102,8 +107,12 @@ Bond.ARPIntervalSec, config_parse_sec, 0, Bond.LearnPacketIntervalSec, config_parse_sec, 0, offsetof(Bond, lp_interval) Bridge.HelloTimeSec, config_parse_sec, 0, offsetof(Bridge, hello_time) Bridge.MaxAgeSec, config_parse_sec, 0, offsetof(Bridge, max_age) +Bridge.AgeingTimeSec, config_parse_sec, 0, offsetof(Bridge, ageing_time) Bridge.ForwardDelaySec, config_parse_sec, 0, offsetof(Bridge, forward_delay) +Bridge.Priority, config_parse_uint16, 0, offsetof(Bridge, priority) +Bridge.DefaultPVID, config_parse_vlanid, 0, offsetof(Bridge, default_pvid) Bridge.MulticastQuerier, config_parse_tristate, 0, offsetof(Bridge, mcast_querier) Bridge.MulticastSnooping, config_parse_tristate, 0, offsetof(Bridge, mcast_snooping) Bridge.VLANFiltering, config_parse_tristate, 0, offsetof(Bridge, vlan_filtering) +Bridge.STP, config_parse_tristate, 0, offsetof(Bridge, stp) VRF.TableId, config_parse_uint32, 0, offsetof(Vrf, table_id) diff --git a/src/network/networkd-netdev-tunnel.c b/src/network/networkd-netdev-tunnel.c index 77a4734df8..9138ee4511 100644 --- a/src/network/networkd-netdev-tunnel.c +++ b/src/network/networkd-netdev-tunnel.c @@ -201,12 +201,18 @@ static int netdev_ip6gre_fill_message_create(NetDev *netdev, Link *link, sd_netl } static int netdev_vti_fill_message_key(NetDev *netdev, Link *link, sd_netlink_message *m) { - Tunnel *t = VTI(netdev); uint32_t ikey, okey; + Tunnel *t; int r; assert(link); assert(m); + + if (netdev->kind == NETDEV_KIND_VTI) + t = VTI(netdev); + else + t = VTI6(netdev); + assert(t); if (t->key != 0) diff --git a/src/network/networkd-netdev-vcan.c b/src/network/networkd-netdev-vcan.c new file mode 100644 index 0000000000..bfce6e1962 --- /dev/null +++ b/src/network/networkd-netdev-vcan.c @@ -0,0 +1,25 @@ +/*** + This file is part of systemd. + + Copyright 2016 Susant Sahani + + 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 "networkd-netdev-vcan.h" + +const NetDevVTable vcan_vtable = { + .object_size = sizeof(VCan), + .create_type = NETDEV_CREATE_INDEPENDENT, +}; diff --git a/src/network/networkd-netdev-vcan.h b/src/network/networkd-netdev-vcan.h new file mode 100644 index 0000000000..6ba47fd70e --- /dev/null +++ b/src/network/networkd-netdev-vcan.h @@ -0,0 +1,34 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2016 Susant Sahani + + 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/>. +***/ + +typedef struct VCan VCan; + +#include <linux/can/netlink.h> + +#include "networkd-netdev.h" + +struct VCan { + NetDev meta; +}; + +DEFINE_NETDEV_CAST(VCAN, VCan); + +extern const NetDevVTable vcan_vtable; diff --git a/src/network/networkd-netdev-vxlan.c b/src/network/networkd-netdev-vxlan.c index 724f9861be..706e52b698 100644 --- a/src/network/networkd-netdev-vxlan.c +++ b/src/network/networkd-netdev-vxlan.c @@ -112,6 +112,14 @@ static int netdev_vxlan_fill_message_create(NetDev *netdev, Link *link, sd_netli if (r < 0) return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_UDP_ZERO_CSUM6_RX attribute: %m"); + r = sd_netlink_message_append_u8(m, IFLA_VXLAN_REMCSUM_TX, v->remote_csum_tx); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_REMCSUM_TX attribute: %m"); + + r = sd_netlink_message_append_u8(m, IFLA_VXLAN_REMCSUM_RX, v->remote_csum_rx); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_REMCSUM_RX attribute: %m"); + r = sd_netlink_message_append_u16(m, IFLA_VXLAN_PORT, htobe16(v->dest_port)); if (r < 0) return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_PORT attribute: %m"); diff --git a/src/network/networkd-netdev-vxlan.h b/src/network/networkd-netdev-vxlan.h index 4614c66fd1..3906820afb 100644 --- a/src/network/networkd-netdev-vxlan.h +++ b/src/network/networkd-netdev-vxlan.h @@ -50,6 +50,8 @@ struct VxLan { bool udpcsum; bool udp6zerocsumtx; bool udp6zerocsumrx; + bool remote_csum_tx; + bool remote_csum_rx; bool group_policy; struct ifla_vxlan_port_range port_range; diff --git a/src/network/networkd-netdev.c b/src/network/networkd-netdev.c index e7edc366af..a210ba1242 100644 --- a/src/network/networkd-netdev.c +++ b/src/network/networkd-netdev.c @@ -34,7 +34,6 @@ #include "string-util.h" const NetDevVTable * const netdev_vtable[_NETDEV_KIND_MAX] = { - [NETDEV_KIND_BRIDGE] = &bridge_vtable, [NETDEV_KIND_BOND] = &bond_vtable, [NETDEV_KIND_VLAN] = &vlan_vtable, @@ -56,7 +55,7 @@ const NetDevVTable * const netdev_vtable[_NETDEV_KIND_MAX] = { [NETDEV_KIND_TAP] = &tap_vtable, [NETDEV_KIND_IP6TNL] = &ip6tnl_vtable, [NETDEV_KIND_VRF] = &vrf_vtable, - + [NETDEV_KIND_VCAN] = &vcan_vtable, }; static const char* const netdev_kind_table[_NETDEV_KIND_MAX] = { @@ -81,7 +80,7 @@ static const char* const netdev_kind_table[_NETDEV_KIND_MAX] = { [NETDEV_KIND_TAP] = "tap", [NETDEV_KIND_IP6TNL] = "ip6tnl", [NETDEV_KIND_VRF] = "vrf", - + [NETDEV_KIND_VCAN] = "vcan", }; DEFINE_STRING_TABLE_LOOKUP(netdev_kind, NetDevKind); @@ -516,7 +515,7 @@ static int netdev_create(NetDev *netdev, Link *link, r = sd_netlink_message_close_container(m); if (r < 0) - return log_netdev_error_errno(netdev, r, "Could not append IFLA_LINKINFO attribute: %m"); + return log_netdev_error_errno(netdev, r, "Could not append IFLA_INFO_DATA attribute: %m"); r = sd_netlink_message_close_container(m); if (r < 0) @@ -577,6 +576,7 @@ static int netdev_load_one(Manager *manager, const char *filename) { _cleanup_netdev_unref_ NetDev *netdev = NULL; _cleanup_free_ NetDev *netdev_raw = NULL; _cleanup_fclose_ FILE *file = NULL; + const char *dropin_dirname; int r; assert(manager); @@ -600,11 +600,12 @@ static int netdev_load_one(Manager *manager, const char *filename) { return log_oom(); netdev_raw->kind = _NETDEV_KIND_INVALID; + dropin_dirname = strjoina(basename(filename), ".d"); - r = config_parse(NULL, filename, file, - "Match\0NetDev\0", - config_item_perf_lookup, network_netdev_gperf_lookup, - true, false, true, netdev_raw); + r = config_parse_many(filename, network_dirs, dropin_dirname, + "Match\0NetDev\0", + config_item_perf_lookup, network_netdev_gperf_lookup, + true, netdev_raw); if (r < 0) return r; @@ -620,7 +621,7 @@ static int netdev_load_one(Manager *manager, const char *filename) { return 0; if (netdev_raw->kind == _NETDEV_KIND_INVALID) { - log_warning("NetDev with invalid Kind configured in %s. Ignoring", filename); + log_warning("NetDev has no Kind configured in %s. Ignoring", filename); return 0; } diff --git a/src/network/networkd-netdev.h b/src/network/networkd-netdev.h index b92a973b85..70ff947b99 100644 --- a/src/network/networkd-netdev.h +++ b/src/network/networkd-netdev.h @@ -56,6 +56,7 @@ typedef enum NetDevKind { NETDEV_KIND_TUN, NETDEV_KIND_TAP, NETDEV_KIND_VRF, + NETDEV_KIND_VCAN, _NETDEV_KIND_MAX, _NETDEV_KIND_INVALID = -1 } NetDevKind; @@ -180,8 +181,8 @@ const struct ConfigPerfItem* network_netdev_gperf_lookup(const char *key, unsign #define log_netdev_full(netdev, level, error, ...) \ ({ \ - NetDev *_n = (netdev); \ - _n ? log_object_internal(level, error, __FILE__, __LINE__, __func__, "INTERFACE=", _n->ifname, ##__VA_ARGS__) : \ + const NetDev *_n = (netdev); \ + _n ? log_object_internal(level, error, __FILE__, __LINE__, __func__, "INTERFACE=", _n->ifname, NULL, NULL, ##__VA_ARGS__) : \ log_internal(level, error, __FILE__, __LINE__, __func__, ##__VA_ARGS__); \ }) diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 5172a7b5e9..bcf8186c33 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -28,6 +28,7 @@ Match.KernelCommandLine, config_parse_net_condition, Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(Network, match_arch) Link.MACAddress, config_parse_hwaddr, 0, offsetof(Network, mac) Link.MTUBytes, config_parse_iec_size, 0, offsetof(Network, mtu) +Link.ARP, config_parse_tristate, 0, offsetof(Network, arp) Network.Description, config_parse_string, 0, offsetof(Network, description) Network.Bridge, config_parse_netdev, 0, offsetof(Network, bridge) Network.Bond, config_parse_netdev, 0, offsetof(Network, bond) @@ -48,7 +49,7 @@ Network.EmitLLDP, config_parse_lldp_emit, Network.Address, config_parse_address, 0, 0 Network.Gateway, config_parse_gateway, 0, 0 Network.Domains, config_parse_domains, 0, 0 -Network.DNS, config_parse_strv, 0, offsetof(Network, dns) +Network.DNS, config_parse_dns, 0, 0 Network.LLMNR, config_parse_resolve_support, 0, offsetof(Network, llmnr) Network.MulticastDNS, config_parse_resolve_support, 0, offsetof(Network, mdns) Network.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Network, dnssec_mode) @@ -69,6 +70,11 @@ Address.Peer, config_parse_address, Address.Broadcast, config_parse_broadcast, 0, 0 Address.Label, config_parse_label, 0, 0 Address.PreferredLifetime, config_parse_lifetime, 0, 0 +Address.HomeAddress, config_parse_address_flags, 0, 0 +Address.DuplicateAddressDetection, config_parse_address_flags, 0, 0 +Address.ManageTemporaryAddress, config_parse_address_flags, 0, 0 +Address.PrefixRoute, config_parse_address_flags, 0, 0 +Address.AutoJoin, config_parse_address_flags, 0, 0 Route.Gateway, config_parse_gateway, 0, 0 Route.Destination, config_parse_destination, 0, 0 Route.Source, config_parse_destination, 0, 0 @@ -91,10 +97,12 @@ DHCP.VendorClassIdentifier, config_parse_string, DHCP.DUIDType, config_parse_duid_type, 0, offsetof(Network, duid.type) DHCP.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Network, duid) DHCP.RouteMetric, config_parse_unsigned, 0, offsetof(Network, dhcp_route_metric) +DHCP.RouteTable, config_parse_dhcp_route_table, 0, offsetof(Network, dhcp_route_table) DHCP.UseTimezone, config_parse_bool, 0, offsetof(Network, dhcp_use_timezone) DHCP.IAID, config_parse_iaid, 0, offsetof(Network, iaid) IPv6AcceptRA.UseDNS, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_dns) IPv6AcceptRA.UseDomains, config_parse_dhcp_use_domains, 0, offsetof(Network, ipv6_accept_ra_use_domains) +IPv6AcceptRA.RouteTable, config_parse_dhcp_route_table, 0, offsetof(Network, ipv6_accept_ra_route_table) DHCPServer.MaxLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_max_lease_time_usec) DHCPServer.DefaultLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_default_lease_time_usec) DHCPServer.EmitDNS, config_parse_bool, 0, offsetof(Network, dhcp_server_emit_dns) @@ -114,7 +122,7 @@ Bridge.AllowPortToBeRoot, config_parse_bool, Bridge.UnicastFlood, config_parse_bool, 0, offsetof(Network, unicast_flood) BridgeFDB.MACAddress, config_parse_fdb_hwaddr, 0, 0 BridgeFDB.VLANId, config_parse_fdb_vlan_id, 0, 0 -BridgeVLAN.PVID, config_parse_vlanid, 0, offsetof(Network, pvid) +BridgeVLAN.PVID, config_parse_brvlan_pvid, 0, 0 BridgeVLAN.VLAN, config_parse_brvlan_vlan, 0, 0 BridgeVLAN.EgressUntagged, config_parse_brvlan_untagged, 0, 0 /* backwards compatibility: do not add new entries to this section */ diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 2b764d4f24..042232fcac 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -40,6 +40,7 @@ static int network_load_one(Manager *manager, const char *filename) { _cleanup_network_free_ Network *network = NULL; _cleanup_fclose_ FILE *file = NULL; char *d; + const char *dropin_dirname; Route *route; Address *address; int r; @@ -110,6 +111,7 @@ static int network_load_one(Manager *manager, const char *filename) { network->dhcp_send_hostname = true; network->dhcp_route_metric = DHCP_ROUTE_METRIC; network->dhcp_client_identifier = DHCP_CLIENT_ID_DUID; + network->dhcp_route_table = RT_TABLE_MAIN; network->dhcp_server_emit_dns = true; network->dhcp_server_emit_ntp = true; @@ -134,23 +136,27 @@ static int network_load_one(Manager *manager, const char *filename) { network->ipv6_hop_limit = -1; network->duid.type = _DUID_TYPE_INVALID; network->proxy_arp = -1; + network->arp = -1; network->ipv6_accept_ra_use_dns = true; - - r = config_parse(NULL, filename, file, - "Match\0" - "Link\0" - "Network\0" - "Address\0" - "Route\0" - "DHCP\0" - "DHCPv4\0" /* compat */ - "DHCPServer\0" - "IPv6AcceptRA\0" - "Bridge\0" - "BridgeFDB\0" - "BridgeVLAN\0", - config_item_perf_lookup, network_network_gperf_lookup, - false, false, true, network); + network->ipv6_accept_ra_route_table = RT_TABLE_MAIN; + + dropin_dirname = strjoina(network->name, ".network.d"); + + r = config_parse_many(filename, network_dirs, dropin_dirname, + "Match\0" + "Link\0" + "Network\0" + "Address\0" + "Route\0" + "DHCP\0" + "DHCPv4\0" /* compat */ + "DHCPServer\0" + "IPv6AcceptRA\0" + "Bridge\0" + "BridgeFDB\0" + "BridgeVLAN\0", + config_item_perf_lookup, network_network_gperf_lookup, + false, network); if (r < 0) return r; @@ -394,10 +400,8 @@ int network_apply(Manager *manager, Network *network, Link *link) { if (!strv_isempty(network->dns) || !strv_isempty(network->ntp) || !strv_isempty(network->search_domains) || - !strv_isempty(network->route_domains)) { - manager_dirty(manager); + !strv_isempty(network->route_domains)) link_dirty(link); - } return 0; } @@ -480,9 +484,10 @@ int config_parse_netdev(const char *unit, case NETDEV_KIND_MACVTAP: case NETDEV_KIND_IPVLAN: case NETDEV_KIND_VXLAN: + case NETDEV_KIND_VCAN: r = hashmap_put(network->stacked_netdevs, netdev->ifname, netdev); if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Can not add VLAN '%s' to network: %m", rvalue); + log_syntax(unit, LOG_ERR, filename, line, r, "Can not add NetDev '%s' to network: %m", rvalue); return 0; } @@ -974,6 +979,56 @@ int config_parse_dhcp_server_ntp( } } +int config_parse_dns( + 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) { + + Network *n = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + for (;;) { + _cleanup_free_ char *w = NULL; + union in_addr_union a; + int family; + + r = extract_first_word(&rvalue, &w, WHITESPACE, EXTRACT_QUOTES|EXTRACT_RETAIN_ESCAPE); + if (r == 0) + break; + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Invalid syntax, ignoring: %s", rvalue); + break; + } + + r = in_addr_from_string_auto(w, &family, &a); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse dns server address, ignoring: %s", w); + continue; + } + + r = strv_consume(&n->dns, w); + if (r < 0) + return log_oom(); + + w = NULL; + } + + return 0; +} + int config_parse_dnssec_negative_trust_anchors( const char *unit, const char *filename, @@ -1030,6 +1085,36 @@ int config_parse_dnssec_negative_trust_anchors( return 0; } +int config_parse_dhcp_route_table(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) { + uint32_t rt; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = safe_atou32(rvalue, &rt); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Unable to read RouteTable, ignoring assignment: %s", rvalue); + return 0; + } + + *((uint32_t *)data) = rt; + + return 0; +} + DEFINE_CONFIG_PARSE_ENUM(config_parse_dhcp_use_domains, dhcp_use_domains, DHCPUseDomains, "Failed to parse DHCP use domains setting"); static const char* const dhcp_use_domains_table[_DHCP_USE_DOMAINS_MAX] = { diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 08ee939faa..42fc82d392 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -123,6 +123,7 @@ struct Network { bool dhcp_use_routes; bool dhcp_use_timezone; unsigned dhcp_route_metric; + uint32_t dhcp_route_table; /* DHCP Server Support */ bool dhcp_server; @@ -151,6 +152,7 @@ struct Network { bool unicast_flood; unsigned cost; + bool use_br_vlan; uint16_t pvid; uint32_t br_vid_bitmap[BRIDGE_VLAN_BITMAP_LEN]; uint32_t br_untagged_bitmap[BRIDGE_VLAN_BITMAP_LEN]; @@ -165,12 +167,14 @@ struct Network { bool ipv6_accept_ra_use_dns; DHCPUseDomains ipv6_accept_ra_use_domains; + uint32_t ipv6_accept_ra_route_table; union in_addr_union ipv6_token; IPv6PrivacyExtensions ipv6_privacy_extensions; struct ether_addr *mac; unsigned mtu; + int arp; uint32_t iaid; DUID duid; @@ -216,6 +220,7 @@ int config_parse_netdev(const char *unit, const char *filename, unsigned line, c int config_parse_domains(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); int config_parse_tunnel(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); int config_parse_dhcp(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); +int config_parse_dns(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); int config_parse_dhcp_client_identifier(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); int config_parse_ipv6token(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); int config_parse_ipv6_privacy_extensions(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); @@ -226,6 +231,7 @@ int config_parse_dhcp_server_ntp(const char *unit, const char *filename, unsigne int config_parse_dnssec_negative_trust_anchors(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); int config_parse_dhcp_use_domains(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); int config_parse_lldp_mode(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); +int config_parse_dhcp_route_table(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); /* Legacy IPv4LL support */ int config_parse_ipv4ll(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); diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index cedaf47cf8..6f60ee5e31 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -26,10 +26,37 @@ #include "parse-util.h" #include "set.h" #include "string-util.h" +#include "sysctl-util.h" #include "util.h" -#define ROUTES_PER_LINK_MAX 2048U -#define STATIC_ROUTES_PER_NETWORK_MAX 1024U +#define ROUTES_DEFAULT_MAX_PER_FAMILY 4096U + +static unsigned routes_max(void) { + static thread_local unsigned cached = 0; + + _cleanup_free_ char *s4 = NULL, *s6 = NULL; + unsigned val4 = ROUTES_DEFAULT_MAX_PER_FAMILY, val6 = ROUTES_DEFAULT_MAX_PER_FAMILY; + + if (cached > 0) + return cached; + + if (sysctl_read("net/ipv4/route/max_size", &s4) >= 0) { + truncate_nl(s4); + if (safe_atou(s4, &val4) >= 0 && + val4 == 2147483647U) + /* This is the default "no limit" value in the kernel */ + val4 = ROUTES_DEFAULT_MAX_PER_FAMILY; + } + + if (sysctl_read("net/ipv6/route/max_size", &s6) >= 0) { + truncate_nl(s6); + (void) safe_atou(s6, &val6); + } + + cached = MAX(ROUTES_DEFAULT_MAX_PER_FAMILY, val4) + + MAX(ROUTES_DEFAULT_MAX_PER_FAMILY, val6); + return cached; +} int route_new(Route **ret) { _cleanup_route_free_ Route *route = NULL; @@ -41,7 +68,7 @@ int route_new(Route **ret) { route->family = AF_UNSPEC; route->scope = RT_SCOPE_UNIVERSE; route->protocol = RTPROT_UNSPEC; - route->table = RT_TABLE_DEFAULT; + route->table = RT_TABLE_MAIN; route->lifetime = USEC_INFINITY; *ret = route; @@ -67,7 +94,7 @@ int route_new_static(Network *network, unsigned section, Route **ret) { } } - if (network->n_static_routes >= STATIC_ROUTES_PER_NETWORK_MAX) + if (network->n_static_routes >= routes_max()) return -E2BIG; r = route_new(&route); @@ -322,7 +349,8 @@ int route_add( } else return r; - *ret = route; + if (ret) + *ret = route; return 0; } @@ -440,20 +468,14 @@ static int route_expire_callback(sd_netlink *rtnl, sd_netlink_message *m, void * assert(m); assert(link); assert(link->ifname); - assert(link->link_messages > 0); if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) return 1; - link->link_messages--; - r = sd_netlink_message_get_errno(m); if (r < 0 && r != -EEXIST) log_link_warning_errno(link, r, "could not remove route: %m"); - if (link->link_messages == 0) - log_link_debug(link, "route removed"); - return 1; } @@ -466,11 +488,8 @@ int route_expire_handler(sd_event_source *s, uint64_t usec, void *userdata) { r = route_remove(route, route->link, route_expire_callback); if (r < 0) log_warning_errno(r, "Could not remove route: %m"); - else { - /* route may not be exist in kernel. If we fail still remove it */ - route->link->link_messages++; + else route_free(route); - } return 1; } @@ -492,7 +511,7 @@ int route_configure( assert(route->family == AF_INET || route->family == AF_INET6); if (route_get(link, route->family, &route->dst, route->dst_prefixlen, route->tos, route->priority, route->table, NULL) <= 0 && - set_size(link->routes) >= ROUTES_PER_LINK_MAX) + set_size(link->routes) >= routes_max()) return -E2BIG; r = sd_rtnl_message_new_route(link->manager->rtnl, &req, @@ -557,14 +576,12 @@ int route_configure( if (r < 0) return log_error_errno(r, "Could not set flags: %m"); - if (route->table != RT_TABLE_DEFAULT) { - + if (route->table != RT_TABLE_MAIN) { if (route->table < 256) { r = sd_rtnl_message_route_set_table(req, route->table); if (r < 0) return log_error_errno(r, "Could not set route table: %m"); } else { - r = sd_rtnl_message_route_set_table(req, RT_TABLE_UNSPEC); if (r < 0) return log_error_errno(r, "Could not set route table: %m"); diff --git a/src/network/networkd-wait-online-link.c b/src/network/networkd-wait-online-link.c index 5727422e3d..e63ba07e90 100644 --- a/src/network/networkd-wait-online-link.c +++ b/src/network/networkd-wait-online-link.c @@ -77,8 +77,7 @@ Link *link_free(Link *l) { } free(l->ifname); - free(l); - return NULL; + return mfree(l); } int link_update_rtnl(Link *l, sd_netlink_message *m) { diff --git a/src/network/networkd.h b/src/network/networkd.h index c4bd712147..cb1b73145e 100644 --- a/src/network/networkd.h +++ b/src/network/networkd.h @@ -43,6 +43,7 @@ #include "networkd-netdev-vlan.h" #include "networkd-netdev-vrf.h" #include "networkd-netdev-vxlan.h" +#include "networkd-netdev-vcan.h" #include "networkd-network.h" #include "networkd-util.h" diff --git a/src/nspawn/nspawn-cgroup.c b/src/nspawn/nspawn-cgroup.c index b1580236c9..5274767b96 100644 --- a/src/nspawn/nspawn-cgroup.c +++ b/src/nspawn/nspawn-cgroup.c @@ -20,32 +20,23 @@ #include <sys/mount.h> #include "alloc-util.h" -#include "cgroup-util.h" #include "fd-util.h" #include "fileio.h" #include "mkdir.h" +#include "mount-util.h" #include "nspawn-cgroup.h" +#include "rm-rf.h" #include "string-util.h" #include "strv.h" #include "util.h" -int chown_cgroup(pid_t pid, uid_t uid_shift) { - _cleanup_free_ char *path = NULL, *fs = NULL; +static int chown_cgroup_path(const char *path, uid_t uid_shift) { _cleanup_close_ int fd = -1; const char *fn; - int r; - - r = cg_pid_get_path(NULL, pid, &path); - if (r < 0) - return log_error_errno(r, "Failed to get container cgroup path: %m"); - r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs); - if (r < 0) - return log_error_errno(r, "Failed to get file system path for container cgroup: %m"); - - fd = open(fs, O_RDONLY|O_CLOEXEC|O_DIRECTORY); + fd = open(path, O_RDONLY|O_CLOEXEC|O_DIRECTORY); if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", fs); + return -errno; FOREACH_STRING(fn, ".", @@ -63,18 +54,37 @@ int chown_cgroup(pid_t pid, uid_t uid_shift) { return 0; } -int sync_cgroup(pid_t pid, bool unified_requested) { +int chown_cgroup(pid_t pid, uid_t uid_shift) { + _cleanup_free_ char *path = NULL, *fs = NULL; + int r; + + r = cg_pid_get_path(NULL, pid, &path); + if (r < 0) + return log_error_errno(r, "Failed to get container cgroup path: %m"); + + r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs); + if (r < 0) + return log_error_errno(r, "Failed to get file system path for container cgroup: %m"); + + r = chown_cgroup_path(fs, uid_shift); + if (r < 0) + return log_error_errno(r, "Failed to chown() cgroup %s: %m", fs); + + return 0; +} + +int sync_cgroup(pid_t pid, CGroupUnified unified_requested, uid_t arg_uid_shift) { _cleanup_free_ char *cgroup = NULL; char tree[] = "/tmp/unifiedXXXXXX", pid_string[DECIMAL_STR_MAX(pid) + 1]; bool undo_mount = false; const char *fn; int unified, r; - unified = cg_unified(); + unified = cg_unified(SYSTEMD_CGROUP_CONTROLLER); if (unified < 0) return log_error_errno(unified, "Failed to determine whether the unified hierarchy is used: %m"); - if ((unified > 0) == unified_requested) + if ((unified > 0) == (unified_requested >= CGROUP_UNIFIED_SYSTEMD)) return 0; /* When the host uses the legacy cgroup setup, but the @@ -91,33 +101,45 @@ int sync_cgroup(pid_t pid, bool unified_requested) { return log_error_errno(errno, "Failed to generate temporary mount point for unified hierarchy: %m"); if (unified) - r = mount("cgroup", tree, "cgroup", MS_NOSUID|MS_NOEXEC|MS_NODEV, "none,name=systemd,xattr"); + r = mount_verbose(LOG_ERR, "cgroup", tree, "cgroup", + MS_NOSUID|MS_NOEXEC|MS_NODEV, "none,name=systemd,xattr"); else - r = mount("cgroup", tree, "cgroup2", MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL); - if (r < 0) { - r = log_error_errno(errno, "Failed to mount unified hierarchy: %m"); + r = mount_verbose(LOG_ERR, "cgroup", tree, "cgroup2", + MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL); + if (r < 0) goto finish; - } undo_mount = true; + /* If nspawn dies abruptly the cgroup hierarchy created below + * its unit isn't cleaned up. So, let's remove it + * https://github.com/systemd/systemd/pull/4223#issuecomment-252519810 */ + fn = strjoina(tree, cgroup); + (void) rm_rf(fn, REMOVE_ROOT|REMOVE_ONLY_DIRECTORIES); + fn = strjoina(tree, cgroup, "/cgroup.procs"); (void) mkdir_parents(fn, 0755); sprintf(pid_string, PID_FMT, pid); r = write_string_file(fn, pid_string, 0); - if (r < 0) + if (r < 0) { log_error_errno(r, "Failed to move process: %m"); + goto finish; + } + fn = strjoina(tree, cgroup); + r = chown_cgroup_path(fn, arg_uid_shift); + if (r < 0) + log_error_errno(r, "Failed to chown() cgroup %s: %m", fn); finish: if (undo_mount) - (void) umount(tree); + (void) umount_verbose(tree); (void) rmdir(tree); return r; } -int create_subcgroup(pid_t pid, bool unified_requested) { +int create_subcgroup(pid_t pid, CGroupUnified unified_requested) { _cleanup_free_ char *cgroup = NULL; const char *child; int unified, r; @@ -129,10 +151,10 @@ int create_subcgroup(pid_t pid, bool unified_requested) { * did not create a scope unit for the container move us and * the container into two separate subcgroups. */ - if (!unified_requested) + if (unified_requested == CGROUP_UNIFIED_NONE) return 0; - unified = cg_unified(); + unified = cg_unified(SYSTEMD_CGROUP_CONTROLLER); if (unified < 0) return log_error_errno(unified, "Failed to determine whether the unified hierarchy is used: %m"); if (unified == 0) diff --git a/src/nspawn/nspawn-cgroup.h b/src/nspawn/nspawn-cgroup.h index 1ff35a299a..fa4321ab43 100644 --- a/src/nspawn/nspawn-cgroup.h +++ b/src/nspawn/nspawn-cgroup.h @@ -22,6 +22,8 @@ #include <stdbool.h> #include <sys/types.h> +#include "cgroup-util.h" + int chown_cgroup(pid_t pid, uid_t uid_shift); -int sync_cgroup(pid_t pid, bool unified_requested); -int create_subcgroup(pid_t pid, bool unified_requested); +int sync_cgroup(pid_t pid, CGroupUnified unified_requested, uid_t uid_shift); +int create_subcgroup(pid_t pid, CGroupUnified unified_requested); diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c index 85e2c943e3..115de64cf9 100644 --- a/src/nspawn/nspawn-mount.c +++ b/src/nspawn/nspawn-mount.c @@ -21,8 +21,9 @@ #include <linux/magic.h> #include "alloc-util.h" -#include "cgroup-util.h" #include "escape.h" +#include "fd-util.h" +#include "fileio.h" #include "fs-util.h" #include "label.h" #include "mkdir.h" @@ -181,13 +182,15 @@ int tmpfs_mount_parse(CustomMount **l, unsigned *n, const char *s) { static int tmpfs_patch_options( const char *options, - bool userns, uid_t uid_shift, uid_t uid_range, + bool userns, + uid_t uid_shift, uid_t uid_range, + bool patch_ids, const char *selinux_apifs_context, char **ret) { char *buf = NULL; - if (userns && uid_shift != 0) { + if ((userns && uid_shift != 0) || patch_ids) { assert(uid_shift != UID_INVALID); if (options) @@ -218,7 +221,13 @@ static int tmpfs_patch_options( } #endif + if (!buf && options) { + buf = strdup(options); + if (!buf) + return -ENOMEM; + } *ret = buf; + return !!buf; } @@ -241,8 +250,10 @@ int mount_sysfs(const char *dest) { (void) mkdir(full, 0755); - if (mount("sysfs", full, "sysfs", MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) < 0) - return log_error_errno(errno, "Failed to mount sysfs to %s: %m", full); + r = mount_verbose(LOG_ERR, "sysfs", full, "sysfs", + MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL); + if (r < 0) + return r; FOREACH_STRING(x, "block", "bus", "class", "dev", "devices", "kernel") { _cleanup_free_ char *from = NULL, *to = NULL; @@ -257,28 +268,91 @@ int mount_sysfs(const char *dest) { (void) mkdir(to, 0755); - if (mount(from, to, NULL, MS_BIND, NULL) < 0) - return log_error_errno(errno, "Failed to mount /sys/%s into place: %m", x); + r = mount_verbose(LOG_ERR, from, to, NULL, MS_BIND, NULL); + if (r < 0) + return r; - if (mount(NULL, to, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, NULL) < 0) - return log_error_errno(errno, "Failed to mount /sys/%s read-only: %m", x); + r = mount_verbose(LOG_ERR, NULL, to, NULL, + MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, NULL); + if (r < 0) + return r; } - if (umount(full) < 0) - return log_error_errno(errno, "Failed to unmount %s: %m", full); + r = umount_verbose(full); + if (r < 0) + return r; if (rmdir(full) < 0) return log_error_errno(errno, "Failed to remove %s: %m", full); x = prefix_roota(top, "/fs/kdbus"); - (void) mkdir(x, 0755); + (void) mkdir_p(x, 0755); + + /* Create mountpoint for cgroups. Otherwise we are not allowed since we + * remount /sys read-only. + */ + if (cg_ns_supported()) { + x = prefix_roota(top, "/fs/cgroup"); + (void) mkdir_p(x, 0755); + } + + return mount_verbose(LOG_ERR, NULL, top, NULL, + MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, NULL); +} + +static int mkdir_userns(const char *path, mode_t mode, bool in_userns, uid_t uid_shift) { + int r; + + assert(path); - if (mount(NULL, top, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, NULL) < 0) - return log_error_errno(errno, "Failed to make %s read-only: %m", top); + r = mkdir(path, mode); + if (r < 0 && errno != EEXIST) + return -errno; + + if (!in_userns) { + r = lchown(path, uid_shift, uid_shift); + if (r < 0) + return -errno; + } return 0; } +static int mkdir_userns_p(const char *prefix, const char *path, mode_t mode, bool in_userns, uid_t uid_shift) { + const char *p, *e; + int r; + + assert(path); + + if (prefix && !path_startswith(path, prefix)) + return -ENOTDIR; + + /* create every parent directory in the path, except the last component */ + p = path + strspn(path, "/"); + for (;;) { + char t[strlen(path) + 1]; + + e = p + strcspn(p, "/"); + p = e + strspn(e, "/"); + + /* Is this the last component? If so, then we're done */ + if (*p == 0) + break; + + memcpy(t, path, e - path); + t[e-path] = 0; + + if (prefix && path_startswith(prefix, t)) + continue; + + r = mkdir_userns(t, mode, in_userns, uid_shift); + if (r < 0) + return r; + } + + return mkdir_userns(path, mode, in_userns, uid_shift); +} + int mount_all(const char *dest, bool use_userns, bool in_userns, bool use_netns, @@ -297,19 +371,21 @@ int mount_all(const char *dest, } MountPoint; static const MountPoint mount_table[] = { - { "proc", "/proc", "proc", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, true, true, false }, - { "/proc/sys", "/proc/sys", NULL, NULL, MS_BIND, true, true, false }, /* Bind mount first ...*/ - { "/proc/sys/net", "/proc/sys/net", NULL, NULL, MS_BIND, true, true, true }, /* (except for this) */ - { NULL, "/proc/sys", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, true, true, false }, /* ... then, make it r/o */ - { "tmpfs", "/sys", "tmpfs", "mode=755", MS_NOSUID|MS_NOEXEC|MS_NODEV, true, false, true }, - { "sysfs", "/sys", "sysfs", NULL, MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, true, false, false }, - { "tmpfs", "/dev", "tmpfs", "mode=755", MS_NOSUID|MS_STRICTATIME, true, false, false }, - { "tmpfs", "/dev/shm", "tmpfs", "mode=1777", MS_NOSUID|MS_NODEV|MS_STRICTATIME, true, false, false }, - { "tmpfs", "/run", "tmpfs", "mode=755", MS_NOSUID|MS_NODEV|MS_STRICTATIME, true, false, false }, - { "tmpfs", "/tmp", "tmpfs", "mode=1777", MS_STRICTATIME, true, false, false }, + { "proc", "/proc", "proc", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, true, true, false }, + { "/proc/sys", "/proc/sys", NULL, NULL, MS_BIND, true, true, false }, /* Bind mount first ...*/ + { "/proc/sys/net", "/proc/sys/net", NULL, NULL, MS_BIND, true, true, true }, /* (except for this) */ + { NULL, "/proc/sys", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, true, true, false }, /* ... then, make it r/o */ + { "/proc/sysrq-trigger", "/proc/sysrq-trigger", NULL, NULL, MS_BIND, false, true, false }, /* Bind mount first ...*/ + { NULL, "/proc/sysrq-trigger", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, false, true, false }, /* ... then, make it r/o */ + { "tmpfs", "/sys", "tmpfs", "mode=755", MS_NOSUID|MS_NOEXEC|MS_NODEV, true, false, true }, + { "sysfs", "/sys", "sysfs", NULL, MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, true, false, false }, + { "tmpfs", "/dev", "tmpfs", "mode=755", MS_NOSUID|MS_STRICTATIME, true, false, false }, + { "tmpfs", "/dev/shm", "tmpfs", "mode=1777", MS_NOSUID|MS_NODEV|MS_STRICTATIME, true, false, false }, + { "tmpfs", "/run", "tmpfs", "mode=755", MS_NOSUID|MS_NODEV|MS_STRICTATIME, true, false, false }, + { "tmpfs", "/tmp", "tmpfs", "mode=1777", MS_STRICTATIME, true, true, false }, #ifdef HAVE_SELINUX - { "/sys/fs/selinux", "/sys/fs/selinux", NULL, NULL, MS_BIND, false, false, false }, /* Bind mount first */ - { NULL, "/sys/fs/selinux", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, false, false, false }, /* Then, make it r/o */ + { "/sys/fs/selinux", "/sys/fs/selinux", NULL, NULL, MS_BIND, false, false, false }, /* Bind mount first */ + { NULL, "/sys/fs/selinux", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, false, false, false }, /* Then, make it r/o */ #endif }; @@ -338,8 +414,8 @@ int mount_all(const char *dest, if (mount_table[k].what && r > 0) continue; - r = mkdir_p(where, 0755); - if (r < 0) { + r = mkdir_userns_p(dest, where, 0755, in_userns, uid_shift); + if (r < 0 && r != -EEXIST) { if (mount_table[k].fatal) return log_error_errno(r, "Failed to create directory %s: %m", where); @@ -349,24 +425,24 @@ int mount_all(const char *dest, o = mount_table[k].options; if (streq_ptr(mount_table[k].type, "tmpfs")) { - r = tmpfs_patch_options(o, use_userns, uid_shift, uid_range, selinux_apifs_context, &options); + if (in_userns) + r = tmpfs_patch_options(o, use_userns, 0, uid_range, true, selinux_apifs_context, &options); + else + r = tmpfs_patch_options(o, use_userns, uid_shift, uid_range, false, selinux_apifs_context, &options); if (r < 0) return log_oom(); if (r > 0) o = options; } - if (mount(mount_table[k].what, - where, - mount_table[k].type, - mount_table[k].flags, - o) < 0) { - - if (mount_table[k].fatal) - return log_error_errno(errno, "mount(%s) failed: %m", where); - - log_warning_errno(errno, "mount(%s) failed, ignoring: %m", where); - } + r = mount_verbose(mount_table[k].fatal ? LOG_ERR : LOG_WARNING, + mount_table[k].what, + where, + mount_table[k].type, + mount_table[k].flags, + o); + if (r < 0 && mount_table[k].fatal) + return r; } return 0; @@ -451,15 +527,15 @@ static int mount_bind(const char *dest, CustomMount *m) { if (r < 0) return log_error_errno(r, "Failed to create mount point %s: %m", where); - } else { + } else return log_error_errno(errno, "Failed to stat %s: %m", where); - } - if (mount(m->source, where, NULL, mount_flags, mount_opts) < 0) - return log_error_errno(errno, "mount(%s) failed: %m", where); + r = mount_verbose(LOG_ERR, m->source, where, NULL, mount_flags, mount_opts); + if (r < 0) + return r; if (m->read_only) { - r = bind_remount_recursive(where, true); + r = bind_remount_recursive(where, true, NULL); if (r < 0) return log_error_errno(r, "Read-only bind mount failed: %m"); } @@ -486,15 +562,12 @@ static int mount_tmpfs( if (r < 0 && r != -EEXIST) return log_error_errno(r, "Creating mount point for tmpfs %s failed: %m", where); - r = tmpfs_patch_options(m->options, userns, uid_shift, uid_range, selinux_apifs_context, &buf); + r = tmpfs_patch_options(m->options, userns, uid_shift, uid_range, false, selinux_apifs_context, &buf); if (r < 0) return log_oom(); options = r > 0 ? buf : m->options; - if (mount("tmpfs", where, "tmpfs", MS_NODEV|MS_STRICTATIME, options) < 0) - return log_error_errno(errno, "tmpfs mount to %s failed: %m", where); - - return 0; + return mount_verbose(LOG_ERR, "tmpfs", where, "tmpfs", MS_NODEV|MS_STRICTATIME, options); } static char *joined_and_escaped_lower_dirs(char * const *lower) { @@ -556,10 +629,7 @@ static int mount_overlay(const char *dest, CustomMount *m) { options = strjoina("lowerdir=", lower, ",upperdir=", escaped_source, ",workdir=", escaped_work_dir); } - if (mount("overlay", where, "overlay", m->read_only ? MS_RDONLY : 0, options) < 0) - return log_error_errno(errno, "overlay mount to %s failed: %m", where); - - return 0; + return mount_verbose(LOG_ERR, "overlay", where, "overlay", m->read_only ? MS_RDONLY : 0, options); } int mount_custom( @@ -601,8 +671,52 @@ int mount_custom( return 0; } -static int mount_legacy_cgroup_hierarchy(const char *dest, const char *controller, const char *hierarchy, bool read_only) { - char *to; +/* Retrieve existing subsystems. This function is called in a new cgroup + * namespace. + */ +static int get_controllers(Set *subsystems) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + + assert(subsystems); + + f = fopen("/proc/self/cgroup", "re"); + if (!f) + return errno == ENOENT ? -ESRCH : -errno; + + FOREACH_LINE(line, f, return -errno) { + int r; + char *e, *l, *p; + + l = strchr(line, ':'); + if (!l) + continue; + + l++; + e = strchr(l, ':'); + if (!e) + continue; + + *e = 0; + + if (STR_IN_SET(l, "", "name=systemd")) + continue; + + p = strdup(l); + if (!p) + return -ENOMEM; + + r = set_consume(subsystems, p); + if (r < 0) + return r; + } + + return 0; +} + +static int mount_legacy_cgroup_hierarchy(const char *dest, const char *controller, const char *hierarchy, + CGroupUnified unified_requested, bool read_only) { + const char *to, *fstype, *opts; int r; to = strjoina(strempty(dest), "/sys/fs/cgroup/", hierarchy); @@ -617,23 +731,136 @@ static int mount_legacy_cgroup_hierarchy(const char *dest, const char *controlle /* The superblock mount options of the mount point need to be * identical to the hosts', and hence writable... */ - if (mount("cgroup", to, "cgroup", MS_NOSUID|MS_NOEXEC|MS_NODEV, controller) < 0) - return log_error_errno(errno, "Failed to mount to %s: %m", to); + if (streq(controller, SYSTEMD_CGROUP_CONTROLLER)) { + if (unified_requested >= CGROUP_UNIFIED_SYSTEMD) { + fstype = "cgroup2"; + opts = NULL; + } else { + fstype = "cgroup"; + opts = "none,name=systemd,xattr"; + } + } else { + fstype = "cgroup"; + opts = controller; + } - /* ... hence let's only make the bind mount read-only, not the - * superblock. */ + r = mount_verbose(LOG_ERR, "cgroup", to, fstype, MS_NOSUID|MS_NOEXEC|MS_NODEV, opts); + if (r < 0) + return r; + + /* ... hence let's only make the bind mount read-only, not the superblock. */ if (read_only) { - if (mount(NULL, to, NULL, MS_BIND|MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_RDONLY, NULL) < 0) - return log_error_errno(errno, "Failed to remount %s read-only: %m", to); + r = mount_verbose(LOG_ERR, NULL, to, NULL, + MS_BIND|MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_RDONLY, NULL); + if (r < 0) + return r; } + return 1; } -static int mount_legacy_cgroups( +/* Mount a legacy cgroup hierarchy when cgroup namespaces are supported. */ +static int mount_legacy_cgns_supported( + CGroupUnified unified_requested, bool userns, uid_t uid_shift, + uid_t uid_range, const char *selinux_apifs_context) { + _cleanup_set_free_free_ Set *controllers = NULL; + const char *cgroup_root = "/sys/fs/cgroup", *c; + int r; + + (void) mkdir_p(cgroup_root, 0755); + + /* Mount a tmpfs to /sys/fs/cgroup if it's not mounted there yet. */ + r = path_is_mount_point(cgroup_root, AT_SYMLINK_FOLLOW); + if (r < 0) + return log_error_errno(r, "Failed to determine if /sys/fs/cgroup is already mounted: %m"); + if (r == 0) { + _cleanup_free_ char *options = NULL; + + /* When cgroup namespaces are enabled and user namespaces are + * used then the mount of the cgroupfs is done *inside* the new + * user namespace. We're root in the new user namespace and the + * kernel will happily translate our uid/gid to the correct + * uid/gid as seen from e.g. /proc/1/mountinfo. So we simply + * pass uid 0 and not uid_shift to tmpfs_patch_options(). + */ + r = tmpfs_patch_options("mode=755", userns, 0, uid_range, true, selinux_apifs_context, &options); + if (r < 0) + return log_oom(); + + r = mount_verbose(LOG_ERR, "tmpfs", cgroup_root, "tmpfs", + MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME, options); + if (r < 0) + return r; + } + + if (cg_all_unified() > 0) + goto skip_controllers; + + controllers = set_new(&string_hash_ops); + if (!controllers) + return log_oom(); + + r = get_controllers(controllers); + if (r < 0) + return log_error_errno(r, "Failed to determine cgroup controllers: %m"); + + for (;;) { + _cleanup_free_ const char *controller = NULL; + + controller = set_steal_first(controllers); + if (!controller) + break; + + r = mount_legacy_cgroup_hierarchy("", controller, controller, unified_requested, !userns); + if (r < 0) + return r; + + /* When multiple hierarchies are co-mounted, make their + * constituting individual hierarchies a symlink to the + * co-mount. + */ + c = controller; + for (;;) { + _cleanup_free_ char *target = NULL, *tok = NULL; + + r = extract_first_word(&c, &tok, ",", 0); + if (r < 0) + return log_error_errno(r, "Failed to extract co-mounted cgroup controller: %m"); + if (r == 0) + break; + + target = prefix_root("/sys/fs/cgroup", tok); + if (!target) + return log_oom(); + + if (streq(controller, tok)) + break; + + r = symlink_idempotent(controller, target); + if (r == -EINVAL) + return log_error_errno(r, "Invalid existing symlink for combined hierarchy: %m"); + if (r < 0) + return log_error_errno(r, "Failed to create symlink for combined hierarchy: %m"); + } + } + +skip_controllers: + r = mount_legacy_cgroup_hierarchy("", SYSTEMD_CGROUP_CONTROLLER, "systemd", unified_requested, false); + if (r < 0) + return r; + + if (!userns) + return mount_verbose(LOG_ERR, NULL, cgroup_root, NULL, + MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME|MS_RDONLY, "mode=755"); + + return 0; +} + +/* Mount legacy cgroup hierarchy when cgroup namespaces are unsupported. */ +static int mount_legacy_cgns_unsupported( const char *dest, - bool userns, uid_t uid_shift, uid_t uid_range, + CGroupUnified unified_requested, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context) { - _cleanup_set_free_free_ Set *controllers = NULL; const char *cgroup_root; int r; @@ -649,15 +876,17 @@ static int mount_legacy_cgroups( if (r == 0) { _cleanup_free_ char *options = NULL; - r = tmpfs_patch_options("mode=755", userns, uid_shift, uid_range, selinux_apifs_context, &options); + r = tmpfs_patch_options("mode=755", userns, uid_shift, uid_range, false, selinux_apifs_context, &options); if (r < 0) return log_oom(); - if (mount("tmpfs", cgroup_root, "tmpfs", MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME, options) < 0) - return log_error_errno(errno, "Failed to mount /sys/fs/cgroup: %m"); + r = mount_verbose(LOG_ERR, "tmpfs", cgroup_root, "tmpfs", + MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME, options); + if (r < 0) + return r; } - if (cg_unified() > 0) + if (cg_all_unified() > 0) goto skip_controllers; controllers = set_new(&string_hash_ops); @@ -683,7 +912,7 @@ static int mount_legacy_cgroups( if (r == -EINVAL) { /* Not a symbolic link, but directly a single cgroup hierarchy */ - r = mount_legacy_cgroup_hierarchy(dest, controller, controller, true); + r = mount_legacy_cgroup_hierarchy(dest, controller, controller, unified_requested, true); if (r < 0) return r; @@ -703,29 +932,25 @@ static int mount_legacy_cgroups( continue; } - r = mount_legacy_cgroup_hierarchy(dest, combined, combined, true); + r = mount_legacy_cgroup_hierarchy(dest, combined, combined, unified_requested, true); if (r < 0) return r; r = symlink_idempotent(combined, target); - if (r == -EINVAL) { - log_error("Invalid existing symlink for combined hierarchy"); - return r; - } + if (r == -EINVAL) + return log_error_errno(r, "Invalid existing symlink for combined hierarchy: %m"); if (r < 0) return log_error_errno(r, "Failed to create symlink for combined hierarchy: %m"); } } skip_controllers: - r = mount_legacy_cgroup_hierarchy(dest, "none,name=systemd,xattr", "systemd", false); + r = mount_legacy_cgroup_hierarchy(dest, SYSTEMD_CGROUP_CONTROLLER, "systemd", unified_requested, false); if (r < 0) return r; - if (mount(NULL, cgroup_root, NULL, MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME|MS_RDONLY, "mode=755") < 0) - return log_error_errno(errno, "Failed to remount %s read-only: %m", cgroup_root); - - return 0; + return mount_verbose(LOG_ERR, NULL, cgroup_root, NULL, + MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME|MS_RDONLY, "mode=755"); } static int mount_unified_cgroups(const char *dest) { @@ -752,27 +977,27 @@ static int mount_unified_cgroups(const char *dest) { return -EINVAL; } - if (mount("cgroup", p, "cgroup2", MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) < 0) - return log_error_errno(errno, "Failed to mount unified cgroup hierarchy to %s: %m", p); - - return 0; + return mount_verbose(LOG_ERR, "cgroup", p, "cgroup2", MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL); } int mount_cgroups( const char *dest, - bool unified_requested, + CGroupUnified unified_requested, bool userns, uid_t uid_shift, uid_t uid_range, - const char *selinux_apifs_context) { + const char *selinux_apifs_context, + bool use_cgns) { - if (unified_requested) + if (unified_requested >= CGROUP_UNIFIED_ALL) return mount_unified_cgroups(dest); - else - return mount_legacy_cgroups(dest, userns, uid_shift, uid_range, selinux_apifs_context); + else if (use_cgns) + return mount_legacy_cgns_supported(unified_requested, userns, uid_shift, uid_range, selinux_apifs_context); + + return mount_legacy_cgns_unsupported(dest, unified_requested, userns, uid_shift, uid_range, selinux_apifs_context); } int mount_systemd_cgroup_writable( const char *dest, - bool unified_requested) { + CGroupUnified unified_requested) { _cleanup_free_ char *own_cgroup_path = NULL; const char *systemd_root, *systemd_own; @@ -788,7 +1013,7 @@ int mount_systemd_cgroup_writable( if (path_equal(own_cgroup_path, "/")) return 0; - if (unified_requested) { + if (unified_requested >= CGROUP_UNIFIED_ALL) { systemd_own = strjoina(dest, "/sys/fs/cgroup", own_cgroup_path); systemd_root = prefix_roota(dest, "/sys/fs/cgroup"); } else { @@ -797,14 +1022,13 @@ int mount_systemd_cgroup_writable( } /* Make our own cgroup a (writable) bind mount */ - if (mount(systemd_own, systemd_own, NULL, MS_BIND, NULL) < 0) - return log_error_errno(errno, "Failed to turn %s into a bind mount: %m", own_cgroup_path); + r = mount_verbose(LOG_ERR, systemd_own, systemd_own, NULL, MS_BIND, NULL); + if (r < 0) + return r; /* And then remount the systemd cgroup root read-only */ - if (mount(NULL, systemd_root, NULL, MS_BIND|MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_RDONLY, NULL) < 0) - return log_error_errno(errno, "Failed to mount cgroup root read-only: %m"); - - return 0; + return mount_verbose(LOG_ERR, NULL, systemd_root, NULL, + MS_BIND|MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_RDONLY, NULL); } int setup_volatile_state( @@ -825,7 +1049,7 @@ int setup_volatile_state( /* --volatile=state means we simply overmount /var with a tmpfs, and the rest read-only. */ - r = bind_remount_recursive(directory, true); + r = bind_remount_recursive(directory, true, NULL); if (r < 0) return log_error_errno(r, "Failed to remount %s read-only: %m", directory); @@ -835,16 +1059,13 @@ int setup_volatile_state( return log_error_errno(errno, "Failed to create %s: %m", directory); options = "mode=755"; - r = tmpfs_patch_options(options, userns, uid_shift, uid_range, selinux_apifs_context, &buf); + r = tmpfs_patch_options(options, userns, uid_shift, uid_range, false, selinux_apifs_context, &buf); if (r < 0) return log_oom(); if (r > 0) options = buf; - if (mount("tmpfs", p, "tmpfs", MS_STRICTATIME, options) < 0) - return log_error_errno(errno, "Failed to mount tmpfs to /var: %m"); - - return 0; + return mount_verbose(LOG_ERR, "tmpfs", p, "tmpfs", MS_STRICTATIME, options); } int setup_volatile( @@ -871,16 +1092,15 @@ int setup_volatile( return log_error_errno(errno, "Failed to create temporary directory: %m"); options = "mode=755"; - r = tmpfs_patch_options(options, userns, uid_shift, uid_range, selinux_apifs_context, &buf); + r = tmpfs_patch_options(options, userns, uid_shift, uid_range, false, selinux_apifs_context, &buf); if (r < 0) return log_oom(); if (r > 0) options = buf; - if (mount("tmpfs", template, "tmpfs", MS_STRICTATIME, options) < 0) { - r = log_error_errno(errno, "Failed to mount tmpfs for root directory: %m"); + r = mount_verbose(LOG_ERR, "tmpfs", template, "tmpfs", MS_STRICTATIME, options); + if (r < 0) goto fail; - } tmpfs_mounted = true; @@ -893,23 +1113,21 @@ int setup_volatile( goto fail; } - if (mount(f, t, NULL, MS_BIND|MS_REC, NULL) < 0) { - r = log_error_errno(errno, "Failed to create /usr bind mount: %m"); + r = mount_verbose(LOG_ERR, f, t, NULL, MS_BIND|MS_REC, NULL); + if (r < 0) goto fail; - } bind_mounted = true; - r = bind_remount_recursive(t, true); + r = bind_remount_recursive(t, true, NULL); if (r < 0) { log_error_errno(r, "Failed to remount %s read-only: %m", t); goto fail; } - if (mount(template, directory, NULL, MS_MOVE, NULL) < 0) { - r = log_error_errno(errno, "Failed to move root mount: %m"); + r = mount_verbose(LOG_ERR, template, directory, NULL, MS_MOVE, NULL); + if (r < 0) goto fail; - } (void) rmdir(template); @@ -917,10 +1135,10 @@ int setup_volatile( fail: if (bind_mounted) - (void) umount(t); + (void) umount_verbose(t); if (tmpfs_mounted) - (void) umount(template); + (void) umount_verbose(template); (void) rmdir(template); return r; } diff --git a/src/nspawn/nspawn-mount.h b/src/nspawn/nspawn-mount.h index 0daf145412..7307a838a5 100644 --- a/src/nspawn/nspawn-mount.h +++ b/src/nspawn/nspawn-mount.h @@ -21,6 +21,8 @@ #include <stdbool.h> +#include "cgroup-util.h" + typedef enum VolatileMode { VOLATILE_NO, VOLATILE_YES, @@ -58,8 +60,8 @@ int custom_mount_compare(const void *a, const void *b); int mount_all(const char *dest, bool use_userns, bool in_userns, bool use_netns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); int mount_sysfs(const char *dest); -int mount_cgroups(const char *dest, bool unified_requested, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); -int mount_systemd_cgroup_writable(const char *dest, bool unified_requested); +int mount_cgroups(const char *dest, CGroupUnified unified_requested, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context, bool use_cgns); +int mount_systemd_cgroup_writable(const char *dest, CGroupUnified unified_requested); int mount_custom(const char *dest, CustomMount *mounts, unsigned n, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); diff --git a/src/nspawn/nspawn-register.c b/src/nspawn/nspawn-register.c index e5b76a0c5d..06c56d9ec8 100644 --- a/src/nspawn/nspawn-register.c +++ b/src/nspawn/nspawn-register.c @@ -68,7 +68,6 @@ int register_machine( local_ifindex > 0 ? 1 : 0, local_ifindex); } else { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - char **i; unsigned j; r = sd_bus_message_new_method_call( @@ -157,11 +156,9 @@ int register_machine( return bus_log_create_error(r); } - STRV_FOREACH(i, properties) { - r = bus_append_unit_property_assignment(m, *i); - if (r < 0) - return r; - } + r = bus_append_unit_property_assignment_many(m, properties); + if (r < 0) + return r; r = sd_bus_message_close_container(m); if (r < 0) diff --git a/src/nspawn/nspawn-seccomp.c b/src/nspawn/nspawn-seccomp.c index 3ab7160ebe..03a397d30c 100644 --- a/src/nspawn/nspawn-seccomp.c +++ b/src/nspawn/nspawn-seccomp.c @@ -130,16 +130,15 @@ int setup_seccomp(uint64_t cap_list_retain) { scmp_filter_ctx seccomp; int r; - seccomp = seccomp_init(SCMP_ACT_ALLOW); - if (!seccomp) - return log_oom(); - - r = seccomp_add_secondary_archs(seccomp); - if (r < 0) { - log_error_errno(r, "Failed to add secondary archs to seccomp filter: %m"); - goto finish; + if (!is_seccomp_available()) { + log_debug("SECCOMP features not detected in the kernel, disabling SECCOMP audit filter"); + return 0; } + r = seccomp_init_conservative(&seccomp, SCMP_ACT_ALLOW); + if (r < 0) + return log_error_errno(r, "Failed to allocate seccomp object: %m"); + r = seccomp_add_default_syscall_filter(seccomp, cap_list_retain); if (r < 0) goto finish; @@ -166,18 +165,7 @@ int setup_seccomp(uint64_t cap_list_retain) { goto finish; } - r = seccomp_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0); - if (r < 0) { - log_error_errno(r, "Failed to unset NO_NEW_PRIVS: %m"); - goto finish; - } - r = seccomp_load(seccomp); - if (r == -EINVAL) { - log_debug_errno(r, "Kernel is probably not configured with CONFIG_SECCOMP. Disabling seccomp audit filter: %m"); - r = 0; - goto finish; - } if (r < 0) { log_error_errno(r, "Failed to install seccomp audit filter: %m"); goto finish; diff --git a/src/nspawn/nspawn-settings.c b/src/nspawn/nspawn-settings.c index 5f1522cfb6..09c8f070ba 100644 --- a/src/nspawn/nspawn-settings.c +++ b/src/nspawn/nspawn-settings.c @@ -101,9 +101,7 @@ Settings* settings_free(Settings *s) { expose_port_free_all(s->expose_ports); custom_mount_free_all(s->custom_mounts, s->n_custom_mounts); - free(s); - - return NULL; + return mfree(s); } bool settings_private_network(Settings *s) { diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index b1c012a9e4..dea54c70b4 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -169,7 +169,6 @@ static CustomMount *arg_custom_mounts = NULL; static unsigned arg_n_custom_mounts = 0; static char **arg_setenv = NULL; static bool arg_quiet = false; -static bool arg_share_system = false; static bool arg_register = true; static bool arg_keep_unit = false; static char **arg_network_interfaces = NULL; @@ -188,12 +187,14 @@ static UserNamespaceMode arg_userns_mode = USER_NAMESPACE_NO; static uid_t arg_uid_shift = UID_INVALID, arg_uid_range = 0x10000U; static bool arg_userns_chown = false; static int arg_kill_signal = 0; -static bool arg_unified_cgroup_hierarchy = false; +static CGroupUnified arg_unified_cgroup_hierarchy = CGROUP_UNIFIED_UNKNOWN; static SettingsMask arg_settings_mask = 0; static int arg_settings_trusted = -1; static char **arg_parameters = NULL; static const char *arg_container_service_name = "systemd-nspawn"; static bool arg_notify_ready = false; +static bool arg_use_cgns = true; +static unsigned long arg_clone_ns_flags = CLONE_NEWIPC|CLONE_NEWPID|CLONE_NEWUTS; static void help(void) { printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n" @@ -215,10 +216,10 @@ static void help(void) { " --uuid=UUID Set a specific machine UUID for the container\n" " -S --slice=SLICE Place the container in the specified slice\n" " --property=NAME=VALUE Set scope unit property\n" - " -U --private-users=pick Run within user namespace, pick UID/GID range automatically\n" + " -U --private-users=pick Run within user namespace, autoselect UID/GID range\n" " --private-users[=UIDBASE[:NUIDS]]\n" - " Run within user namespace, user configured UID/GID range\n" - " --private-user-chown Adjust OS tree file ownership for private UID/GID range\n" + " Similar, but with user configured UID/GID range\n" + " --private-users-chown Adjust OS tree ownership to private UID/GID range\n" " --private-network Disable network in container\n" " --network-interface=INTERFACE\n" " Assign an existing network interface to the\n" @@ -235,11 +236,10 @@ static void help(void) { " Add an additional virtual Ethernet link between\n" " host and container\n" " --network-bridge=INTERFACE\n" - " Add a virtual Ethernet connection between host\n" - " and container and add it to an existing bridge on\n" - " the host\n" - " --network-zone=NAME Add a virtual Ethernet connection to the container,\n" - " and add it to an automatically managed bridge interface\n" + " Add a virtual Ethernet connection to the container\n" + " and attach it to an existing bridge on the host\n" + " --network-zone=NAME Similar, but attach the new interface to an\n" + " an automatically managed bridge interface\n" " -p --port=[PROTOCOL:]HOSTPORT[:CONTAINERPORT]\n" " Expose a container IP port on the host\n" " -Z --selinux-context=SECLABEL\n" @@ -268,14 +268,12 @@ static void help(void) { " --overlay-ro=PATH[:PATH...]:PATH\n" " Similar, but creates a read-only overlay mount\n" " -E --setenv=NAME=VALUE Pass an environment variable to PID 1\n" - " --share-system Share system namespaces with host\n" " --register=BOOLEAN Register container as machine\n" " --keep-unit Do not register a scope for the machine, reuse\n" " the service unit nspawn is running in\n" " --volatile[=MODE] Run the system in volatile mode\n" " --settings=BOOLEAN Load additional settings from .nspawn file\n" - " --notify-ready=BOOLEAN Receive notifications from the container's init process,\n" - " accepted values: yes and no\n" + " --notify-ready=BOOLEAN Receive notifications from the child init process\n" , program_invocation_short_name); } @@ -318,9 +316,9 @@ static int custom_mounts_prepare(void) { return 0; } -static int detect_unified_cgroup_hierarchy(void) { +static int detect_unified_cgroup_hierarchy(const char *directory) { const char *e; - int r; + int r, all_unified, systemd_unified; /* Allow the user to control whether the unified hierarchy is used */ e = getenv("UNIFIED_CGROUP_HIERARCHY"); @@ -328,20 +326,58 @@ static int detect_unified_cgroup_hierarchy(void) { r = parse_boolean(e); if (r < 0) return log_error_errno(r, "Failed to parse $UNIFIED_CGROUP_HIERARCHY."); + if (r > 0) + arg_unified_cgroup_hierarchy = CGROUP_UNIFIED_ALL; + else + arg_unified_cgroup_hierarchy = CGROUP_UNIFIED_NONE; - arg_unified_cgroup_hierarchy = r; return 0; } + all_unified = cg_all_unified(); + systemd_unified = cg_unified(SYSTEMD_CGROUP_CONTROLLER); + + if (all_unified < 0 || systemd_unified < 0) + return log_error_errno(all_unified < 0 ? all_unified : systemd_unified, + "Failed to determine whether the unified cgroups hierarchy is used: %m"); + /* Otherwise inherit the default from the host system */ - r = cg_unified(); - if (r < 0) - return log_error_errno(r, "Failed to determine whether the unified cgroups hierarchy is used: %m"); + if (all_unified > 0) { + /* Unified cgroup hierarchy support was added in 230. Unfortunately the detection + * routine only detects 231, so we'll have a false negative here for 230. */ + r = systemd_installation_has_version(directory, 230); + if (r < 0) + return log_error_errno(r, "Failed to determine systemd version in container: %m"); + if (r > 0) + arg_unified_cgroup_hierarchy = CGROUP_UNIFIED_ALL; + else + arg_unified_cgroup_hierarchy = CGROUP_UNIFIED_NONE; + } else if (systemd_unified > 0) { + /* Mixed cgroup hierarchy support was added in 232 */ + r = systemd_installation_has_version(directory, 232); + if (r < 0) + return log_error_errno(r, "Failed to determine systemd version in container: %m"); + if (r > 0) + arg_unified_cgroup_hierarchy = CGROUP_UNIFIED_SYSTEMD; + else + arg_unified_cgroup_hierarchy = CGROUP_UNIFIED_NONE; + } else + arg_unified_cgroup_hierarchy = CGROUP_UNIFIED_NONE; - arg_unified_cgroup_hierarchy = r; return 0; } +static void parse_share_ns_env(const char *name, unsigned long ns_flag) { + int r; + + r = getenv_bool(name); + if (r == -ENXIO) + return; + if (r < 0) + log_warning_errno(r, "Failed to parse %s from environment, defaulting to false.", name); + arg_clone_ns_flags = (arg_clone_ns_flags & ~ns_flag) | (r > 0 ? 0 : ns_flag); +} + static int parse_argv(int argc, char *argv[]) { enum { @@ -379,52 +415,52 @@ static int parse_argv(int argc, char *argv[]) { }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "directory", required_argument, NULL, 'D' }, - { "template", required_argument, NULL, ARG_TEMPLATE }, - { "ephemeral", no_argument, NULL, 'x' }, - { "user", required_argument, NULL, 'u' }, - { "private-network", no_argument, NULL, ARG_PRIVATE_NETWORK }, - { "as-pid2", no_argument, NULL, 'a' }, - { "boot", no_argument, NULL, 'b' }, - { "uuid", required_argument, NULL, ARG_UUID }, - { "read-only", no_argument, NULL, ARG_READ_ONLY }, - { "capability", required_argument, NULL, ARG_CAPABILITY }, - { "drop-capability", required_argument, NULL, ARG_DROP_CAPABILITY }, - { "link-journal", required_argument, NULL, ARG_LINK_JOURNAL }, - { "bind", required_argument, NULL, ARG_BIND }, - { "bind-ro", required_argument, NULL, ARG_BIND_RO }, - { "tmpfs", required_argument, NULL, ARG_TMPFS }, - { "overlay", required_argument, NULL, ARG_OVERLAY }, - { "overlay-ro", required_argument, NULL, ARG_OVERLAY_RO }, - { "machine", required_argument, NULL, 'M' }, - { "slice", required_argument, NULL, 'S' }, - { "setenv", required_argument, NULL, 'E' }, - { "selinux-context", required_argument, NULL, 'Z' }, - { "selinux-apifs-context", required_argument, NULL, 'L' }, - { "quiet", no_argument, NULL, 'q' }, - { "share-system", no_argument, NULL, ARG_SHARE_SYSTEM }, - { "register", required_argument, NULL, ARG_REGISTER }, - { "keep-unit", no_argument, NULL, ARG_KEEP_UNIT }, - { "network-interface", required_argument, NULL, ARG_NETWORK_INTERFACE }, - { "network-macvlan", required_argument, NULL, ARG_NETWORK_MACVLAN }, - { "network-ipvlan", required_argument, NULL, ARG_NETWORK_IPVLAN }, - { "network-veth", no_argument, NULL, 'n' }, - { "network-veth-extra", required_argument, NULL, ARG_NETWORK_VETH_EXTRA}, - { "network-bridge", required_argument, NULL, ARG_NETWORK_BRIDGE }, - { "network-zone", required_argument, NULL, ARG_NETWORK_ZONE }, - { "personality", required_argument, NULL, ARG_PERSONALITY }, - { "image", required_argument, NULL, 'i' }, - { "volatile", optional_argument, NULL, ARG_VOLATILE }, - { "port", required_argument, NULL, 'p' }, - { "property", required_argument, NULL, ARG_PROPERTY }, - { "private-users", optional_argument, NULL, ARG_PRIVATE_USERS }, - { "private-users-chown", optional_argument, NULL, ARG_PRIVATE_USERS_CHOWN}, - { "kill-signal", required_argument, NULL, ARG_KILL_SIGNAL }, - { "settings", required_argument, NULL, ARG_SETTINGS }, - { "chdir", required_argument, NULL, ARG_CHDIR }, - { "notify-ready", required_argument, NULL, ARG_NOTIFY_READY }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "directory", required_argument, NULL, 'D' }, + { "template", required_argument, NULL, ARG_TEMPLATE }, + { "ephemeral", no_argument, NULL, 'x' }, + { "user", required_argument, NULL, 'u' }, + { "private-network", no_argument, NULL, ARG_PRIVATE_NETWORK }, + { "as-pid2", no_argument, NULL, 'a' }, + { "boot", no_argument, NULL, 'b' }, + { "uuid", required_argument, NULL, ARG_UUID }, + { "read-only", no_argument, NULL, ARG_READ_ONLY }, + { "capability", required_argument, NULL, ARG_CAPABILITY }, + { "drop-capability", required_argument, NULL, ARG_DROP_CAPABILITY }, + { "link-journal", required_argument, NULL, ARG_LINK_JOURNAL }, + { "bind", required_argument, NULL, ARG_BIND }, + { "bind-ro", required_argument, NULL, ARG_BIND_RO }, + { "tmpfs", required_argument, NULL, ARG_TMPFS }, + { "overlay", required_argument, NULL, ARG_OVERLAY }, + { "overlay-ro", required_argument, NULL, ARG_OVERLAY_RO }, + { "machine", required_argument, NULL, 'M' }, + { "slice", required_argument, NULL, 'S' }, + { "setenv", required_argument, NULL, 'E' }, + { "selinux-context", required_argument, NULL, 'Z' }, + { "selinux-apifs-context", required_argument, NULL, 'L' }, + { "quiet", no_argument, NULL, 'q' }, + { "share-system", no_argument, NULL, ARG_SHARE_SYSTEM }, /* not documented */ + { "register", required_argument, NULL, ARG_REGISTER }, + { "keep-unit", no_argument, NULL, ARG_KEEP_UNIT }, + { "network-interface", required_argument, NULL, ARG_NETWORK_INTERFACE }, + { "network-macvlan", required_argument, NULL, ARG_NETWORK_MACVLAN }, + { "network-ipvlan", required_argument, NULL, ARG_NETWORK_IPVLAN }, + { "network-veth", no_argument, NULL, 'n' }, + { "network-veth-extra", required_argument, NULL, ARG_NETWORK_VETH_EXTRA }, + { "network-bridge", required_argument, NULL, ARG_NETWORK_BRIDGE }, + { "network-zone", required_argument, NULL, ARG_NETWORK_ZONE }, + { "personality", required_argument, NULL, ARG_PERSONALITY }, + { "image", required_argument, NULL, 'i' }, + { "volatile", optional_argument, NULL, ARG_VOLATILE }, + { "port", required_argument, NULL, 'p' }, + { "property", required_argument, NULL, ARG_PROPERTY }, + { "private-users", optional_argument, NULL, ARG_PRIVATE_USERS }, + { "private-users-chown", optional_argument, NULL, ARG_PRIVATE_USERS_CHOWN }, + { "kill-signal", required_argument, NULL, ARG_KILL_SIGNAL }, + { "settings", required_argument, NULL, ARG_SETTINGS }, + { "chdir", required_argument, NULL, ARG_CHDIR }, + { "notify-ready", required_argument, NULL, ARG_NOTIFY_READY }, {} }; @@ -813,7 +849,9 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_SHARE_SYSTEM: - arg_share_system = true; + /* We don't officially support this anymore, except for compat reasons. People should use the + * $SYSTEMD_NSPAWN_SHARE_* environment variables instead. */ + arg_clone_ns_flags = 0; break; case ARG_REGISTER: @@ -875,15 +913,21 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_PRIVATE_USERS: + case ARG_PRIVATE_USERS: { + int boolean = -1; - r = optarg ? parse_boolean(optarg) : 1; - if (r == 0) { + if (!optarg) + boolean = true; + else if (!in_charset(optarg, DIGITS)) + /* do *not* parse numbers as booleans */ + boolean = parse_boolean(optarg); + + if (boolean == false) { /* no: User namespacing off */ arg_userns_mode = USER_NAMESPACE_NO; arg_uid_shift = UID_INVALID; arg_uid_range = UINT32_C(0x10000); - } else if (r > 0) { + } else if (boolean == true) { /* yes: User namespacing on, UID range is read from root dir */ arg_userns_mode = USER_NAMESPACE_FIXED; arg_uid_shift = UID_INVALID; @@ -907,23 +951,27 @@ static int parse_argv(int argc, char *argv[]) { shift = buffer; range++; - if (safe_atou32(range, &arg_uid_range) < 0 || arg_uid_range <= 0) { - log_error("Failed to parse UID range: %s", range); - return -EINVAL; - } + r = safe_atou32(range, &arg_uid_range); + if (r < 0) + return log_error_errno(r, "Failed to parse UID range \"%s\": %m", range); } else shift = optarg; - if (parse_uid(shift, &arg_uid_shift) < 0) { - log_error("Failed to parse UID: %s", optarg); - return -EINVAL; - } + r = parse_uid(shift, &arg_uid_shift); + if (r < 0) + return log_error_errno(r, "Failed to parse UID \"%s\": %m", optarg); arg_userns_mode = USER_NAMESPACE_FIXED; } + if (arg_uid_range <= 0) { + log_error("UID range cannot be 0."); + return -EINVAL; + } + arg_settings_mask |= SETTING_USERNS; break; + } case 'U': if (userns_supported()) { @@ -1017,17 +1065,23 @@ static int parse_argv(int argc, char *argv[]) { assert_not_reached("Unhandled option"); } - if (arg_share_system) + parse_share_ns_env("SYSTEMD_NSPAWN_SHARE_NS_IPC", CLONE_NEWIPC); + parse_share_ns_env("SYSTEMD_NSPAWN_SHARE_NS_PID", CLONE_NEWPID); + parse_share_ns_env("SYSTEMD_NSPAWN_SHARE_NS_UTS", CLONE_NEWUTS); + parse_share_ns_env("SYSTEMD_NSPAWN_SHARE_SYSTEM", CLONE_NEWIPC|CLONE_NEWPID|CLONE_NEWUTS); + + if (!(arg_clone_ns_flags & CLONE_NEWPID) || + !(arg_clone_ns_flags & CLONE_NEWUTS)) { arg_register = false; + if (arg_start_mode != START_PID1) { + log_error("--boot cannot be used without namespacing."); + return -EINVAL; + } + } if (arg_userns_mode == USER_NAMESPACE_PICK) arg_userns_chown = true; - if (arg_start_mode != START_PID1 && arg_share_system) { - log_error("--boot and --share-system may not be combined."); - return -EINVAL; - } - if (arg_keep_unit && cg_pid_get_owner_uid(0, NULL) >= 0) { log_error("--keep-unit may not be used when invoked from a user session."); return -EINVAL; @@ -1096,14 +1150,16 @@ static int parse_argv(int argc, char *argv[]) { arg_caps_retain = (arg_caps_retain | plus | (arg_private_network ? 1ULL << CAP_NET_ADMIN : 0)) & ~minus; - r = detect_unified_cgroup_hierarchy(); - if (r < 0) - return r; - e = getenv("SYSTEMD_NSPAWN_CONTAINER_SERVICE"); if (e) arg_container_service_name = e; + r = getenv_bool("SYSTEMD_NSPAWN_USE_CGNS"); + if (r < 0) + arg_use_cgns = cg_ns_supported(); + else + arg_use_cgns = r; + return 1; } @@ -1185,7 +1241,13 @@ static int setup_timezone(const char *dest) { /* Fix the timezone, if possible */ r = readlink_malloc("/etc/localtime", &p); if (r < 0) { - log_warning("/etc/localtime is not a symlink, not updating container timezone."); + log_warning("host's /etc/localtime is not a symlink, not updating container timezone."); + /* to handle warning, delete /etc/localtime and replace it + * with a symbolic link to a time zone data file. + * + * Example: + * ln -s /usr/share/zoneinfo/UTC /etc/localtime + */ return 0; } @@ -1274,9 +1336,6 @@ static int setup_boot_id(const char *dest) { const char *from, *to; int r; - if (arg_share_system) - return 0; - /* Generate a new randomized boot ID, so that each boot-up of * the container gets a new one */ @@ -1291,10 +1350,10 @@ static int setup_boot_id(const char *dest) { if (r < 0) return log_error_errno(r, "Failed to write boot id: %m"); - if (mount(from, to, NULL, MS_BIND, NULL) < 0) - r = log_error_errno(errno, "Failed to bind mount boot id: %m"); - else if (mount(NULL, to, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NOSUID|MS_NODEV, NULL) < 0) - log_warning_errno(errno, "Failed to make boot id read-only, ignoring: %m"); + r = mount_verbose(LOG_ERR, from, to, NULL, MS_BIND, NULL); + if (r >= 0) + r = mount_verbose(LOG_ERR, NULL, to, NULL, + MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NOSUID|MS_NODEV, NULL); (void) unlink(from); return r; @@ -1342,6 +1401,12 @@ static int copy_devnodes(const char *dest) { } else { if (mknod(to, st.st_mode, st.st_rdev) < 0) { + /* + * This is some sort of protection too against + * recursive userns chown on shared /dev/ + */ + if (errno == EEXIST) + log_notice("%s/dev/ should be an empty directory", dest); if (errno != EPERM) return log_error_errno(errno, "mknod(%s) failed: %m", to); @@ -1350,8 +1415,9 @@ static int copy_devnodes(const char *dest) { r = touch(to); if (r < 0) return log_error_errno(r, "touch (%s) failed: %m", to); - if (mount(from, to, NULL, MS_BIND, NULL) < 0) - return log_error_errno(errno, "Both mknod and bind mount (%s) failed: %m", to); + r = mount_verbose(LOG_DEBUG, from, to, NULL, MS_BIND, NULL); + if (r < 0) + return log_error_errno(r, "Both mknod and bind mount (%s) failed: %m", to); } r = userns_lchown(to, 0, 0); @@ -1387,8 +1453,9 @@ static int setup_pts(const char *dest) { p = prefix_roota(dest, "/dev/pts"); if (mkdir(p, 0755) < 0) return log_error_errno(errno, "Failed to create /dev/pts: %m"); - if (mount("devpts", p, "devpts", MS_NOSUID|MS_NOEXEC, options) < 0) - return log_error_errno(errno, "Failed to mount /dev/pts: %m"); + r = mount_verbose(LOG_ERR, "devpts", p, "devpts", MS_NOSUID|MS_NOEXEC, options); + if (r < 0) + return r; r = userns_lchown(p, 0, 0); if (r < 0) return log_error_errno(r, "Failed to chown /dev/pts: %m"); @@ -1433,10 +1500,7 @@ static int setup_dev_console(const char *dest, const char *console) { if (r < 0) return log_error_errno(r, "touch() for /dev/console failed: %m"); - if (mount(console, to, NULL, MS_BIND, NULL) < 0) - return log_error_errno(errno, "Bind mount for /dev/console failed: %m"); - - return 0; + return mount_verbose(LOG_ERR, console, to, NULL, MS_BIND, NULL); } static int setup_kmsg(const char *dest, int kmsg_socket) { @@ -1460,8 +1524,9 @@ static int setup_kmsg(const char *dest, int kmsg_socket) { if (mkfifo(from, 0600) < 0) return log_error_errno(errno, "mkfifo() for /run/kmsg failed: %m"); - if (mount(from, to, NULL, MS_BIND, NULL) < 0) - return log_error_errno(errno, "Bind mount for /proc/kmsg failed: %m"); + r = mount_verbose(LOG_ERR, from, to, NULL, MS_BIND, NULL); + if (r < 0) + return r; fd = open(from, O_RDWR|O_NDELAY|O_CLOEXEC); if (fd < 0) @@ -1494,7 +1559,7 @@ static int on_address_change(sd_netlink *rtnl, sd_netlink_message *m, void *user static int setup_hostname(void) { - if (arg_share_system) + if ((arg_clone_ns_flags & CLONE_NEWUTS) == 0) return 0; if (sethostname_idempotent(arg_machine) < 0) @@ -1631,7 +1696,8 @@ static int setup_journal(const char *directory) { if (r < 0) return log_error_errno(r, "Failed to create %s: %m", q); - if (mount(p, q, NULL, MS_BIND, NULL) < 0) + r = mount_verbose(LOG_DEBUG, p, q, NULL, MS_BIND, NULL); + if (r < 0) return log_error_errno(errno, "Failed to bind mount journal from host into guest: %m"); return 0; @@ -1645,7 +1711,7 @@ static int reset_audit_loginuid(void) { _cleanup_free_ char *p = NULL; int r; - if (arg_share_system) + if ((arg_clone_ns_flags & CLONE_NEWPID) == 0) return 0; r = read_one_line_file("/proc/self/loginuid", &p); @@ -1696,13 +1762,17 @@ static int setup_propagate(const char *root) { return log_error_errno(r, "Failed to create /run/systemd/nspawn/incoming: %m"); q = prefix_roota(root, "/run/systemd/nspawn/incoming"); - if (mount(p, q, NULL, MS_BIND, NULL) < 0) - return log_error_errno(errno, "Failed to install propagation bind mount."); + r = mount_verbose(LOG_ERR, p, q, NULL, MS_BIND, NULL); + if (r < 0) + return r; - if (mount(NULL, q, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY, NULL) < 0) - return log_error_errno(errno, "Failed to make propagation mount read-only"); + r = mount_verbose(LOG_ERR, NULL, q, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY, NULL); + if (r < 0) + return r; - return 0; + /* machined will MS_MOVE into that directory, and that's only + * supported for non-shared mounts. */ + return mount_verbose(LOG_ERR, NULL, q, NULL, MS_SLAVE, NULL); } static int setup_image(char **device_path, int *loop_nr) { @@ -1794,17 +1864,18 @@ static int dissect_image( char **root_device, bool *root_device_rw, char **home_device, bool *home_device_rw, char **srv_device, bool *srv_device_rw, + char **esp_device, bool *secondary) { #ifdef HAVE_BLKID - int home_nr = -1, srv_nr = -1; + int home_nr = -1, srv_nr = -1, esp_nr = -1; #ifdef GPT_ROOT_NATIVE int root_nr = -1; #endif #ifdef GPT_ROOT_SECONDARY int secondary_root_nr = -1; #endif - _cleanup_free_ char *home = NULL, *root = NULL, *secondary_root = NULL, *srv = NULL, *generic = NULL; + _cleanup_free_ char *home = NULL, *root = NULL, *secondary_root = NULL, *srv = NULL, *esp = NULL, *generic = NULL; _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL; _cleanup_udev_device_unref_ struct udev_device *d = NULL; _cleanup_blkid_free_probe_ blkid_probe b = NULL; @@ -1822,6 +1893,7 @@ static int dissect_image( assert(root_device); assert(home_device); assert(srv_device); + assert(esp_device); assert(secondary); assert(arg_image); @@ -2035,6 +2107,16 @@ static int dissect_image( r = free_and_strdup(&srv, node); if (r < 0) return log_oom(); + } else if (sd_id128_equal(type_id, GPT_ESP)) { + + if (esp && nr >= esp_nr) + continue; + + esp_nr = nr; + + r = free_and_strdup(&esp, node); + if (r < 0) + return log_oom(); } #ifdef GPT_ROOT_NATIVE else if (sd_id128_equal(type_id, GPT_ROOT_NATIVE)) { @@ -2152,6 +2234,11 @@ static int dissect_image( *srv_device_rw = srv_rw; } + if (esp) { + *esp_device = esp; + esp = NULL; + } + return 0; #else log_error("--image= is not supported, compiled without blkid support."); @@ -2162,7 +2249,7 @@ static int dissect_image( static int mount_device(const char *what, const char *where, const char *directory, bool rw) { #ifdef HAVE_BLKID _cleanup_blkid_free_probe_ blkid_probe b = NULL; - const char *fstype, *p; + const char *fstype, *p, *options; int r; assert(what); @@ -2211,10 +2298,17 @@ static int mount_device(const char *what, const char *where, const char *directo return -EOPNOTSUPP; } - if (mount(what, p, fstype, MS_NODEV|(rw ? 0 : MS_RDONLY), NULL) < 0) - return log_error_errno(errno, "Failed to mount %s: %m", what); + /* If this is a loopback device then let's mount the image with discard, so that the underlying file remains + * sparse when possible. */ + if (STR_IN_SET(fstype, "btrfs", "ext4", "vfat", "xfs")) { + const char *l; - return 0; + l = path_startswith(what, "/dev"); + if (l && startswith(l, "loop")) + options = "discard"; + } + + return mount_verbose(LOG_ERR, what, p, fstype, MS_NODEV|(rw ? 0 : MS_RDONLY), options); #else log_error("--image= is not supported, compiled without blkid support."); return -EOPNOTSUPP; @@ -2284,7 +2378,8 @@ static int mount_devices( const char *where, const char *root_device, bool root_device_rw, const char *home_device, bool home_device_rw, - const char *srv_device, bool srv_device_rw) { + const char *srv_device, bool srv_device_rw, + const char *esp_device) { int r; assert(where); @@ -2307,6 +2402,27 @@ static int mount_devices( return log_error_errno(r, "Failed to mount server data directory: %m"); } + if (esp_device) { + const char *mp, *x; + + /* Mount the ESP to /efi if it exists and is empty. If it doesn't exist, use /boot instead. */ + + mp = "/efi"; + x = strjoina(arg_directory, mp); + r = dir_is_empty(x); + if (r == -ENOENT) { + mp = "/boot"; + x = strjoina(arg_directory, mp); + r = dir_is_empty(x); + } + + if (r > 0) { + r = mount_device(esp_device, arg_directory, mp, true); + if (r < 0) + return log_error_errno(r, "Failed to mount ESP: %m"); + } + } + return 0; } @@ -2567,6 +2683,10 @@ static int inner_child( } } + r = reset_uid_gid(); + if (r < 0) + return log_error_errno(r, "Couldn't become new root: %m"); + r = mount_all(NULL, arg_userns_mode != USER_NAMESPACE_NO, true, @@ -2589,13 +2709,25 @@ static int inner_child( return -ESRCH; } - r = mount_systemd_cgroup_writable("", arg_unified_cgroup_hierarchy); - if (r < 0) - return r; - - r = reset_uid_gid(); - if (r < 0) - return log_error_errno(r, "Couldn't become new root: %m"); + if (arg_use_cgns && cg_ns_supported()) { + r = unshare(CLONE_NEWCGROUP); + if (r < 0) + return log_error_errno(errno, "Failed to unshare cgroup namespace"); + r = mount_cgroups( + "", + arg_unified_cgroup_hierarchy, + arg_userns_mode != USER_NAMESPACE_NO, + arg_uid_shift, + arg_uid_range, + arg_selinux_apifs_context, + true); + if (r < 0) + return r; + } else { + r = mount_systemd_cgroup_writable("", arg_unified_cgroup_hierarchy); + if (r < 0) + return r; + } r = setup_boot_id(NULL); if (r < 0) @@ -2780,6 +2912,7 @@ static int outer_child( const char *root_device, bool root_device_rw, const char *home_device, bool home_device_rw, const char *srv_device, bool srv_device_rw, + const char *esp_device, bool interactive, bool secondary, int pid_socket, @@ -2835,13 +2968,15 @@ static int outer_child( /* Mark everything as slave, so that we still * receive mounts from the real root, but don't * propagate mounts to the real root. */ - if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) - return log_error_errno(errno, "MS_SLAVE|MS_REC failed: %m"); + r = mount_verbose(LOG_ERR, NULL, "/", NULL, MS_SLAVE|MS_REC, NULL); + if (r < 0) + return r; r = mount_devices(directory, root_device, root_device_rw, home_device, home_device_rw, - srv_device, srv_device_rw); + srv_device, srv_device_rw, + esp_device); if (r < 0) return r; @@ -2849,6 +2984,10 @@ static int outer_child( if (r < 0) return r; + r = detect_unified_cgroup_hierarchy(directory); + if (r < 0) + return r; + if (arg_userns_mode != USER_NAMESPACE_NO) { /* Let the parent know which UID shift we read from the image */ l = send(uid_shift_socket, &arg_uid_shift, sizeof(arg_uid_shift), MSG_NOSIGNAL); @@ -2877,8 +3016,19 @@ static int outer_child( } /* Turn directory into bind mount */ - if (mount(directory, directory, NULL, MS_BIND|MS_REC, NULL) < 0) - return log_error_errno(errno, "Failed to make bind mount: %m"); + r = mount_verbose(LOG_ERR, directory, directory, NULL, MS_BIND|MS_REC, NULL); + if (r < 0) + return r; + + /* Mark everything as shared so our mounts get propagated down. This is + * required to make new bind mounts available in systemd services + * inside the containter that create a new mount namespace. + * See https://github.com/systemd/systemd/issues/3860 + * Further submounts (such as /dev) done after this will inherit the + * shared propagation mode.*/ + r = mount_verbose(LOG_ERR, NULL, directory, NULL, MS_SHARED|MS_REC, NULL); + if (r < 0) + return r; r = recursive_chown(directory, arg_uid_shift, arg_uid_range); if (r < 0) @@ -2909,7 +3059,7 @@ static int outer_child( return r; if (arg_read_only) { - r = bind_remount_recursive(directory, true); + r = bind_remount_recursive(directory, true, NULL); if (r < 0) return log_error_errno(r, "Failed to make tree read-only: %m"); } @@ -2973,15 +3123,18 @@ static int outer_child( if (r < 0) return r; - r = mount_cgroups( - directory, - arg_unified_cgroup_hierarchy, - arg_userns_mode != USER_NAMESPACE_NO, - arg_uid_shift, - arg_uid_range, - arg_selinux_apifs_context); - if (r < 0) - return r; + if (!arg_use_cgns || !cg_ns_supported()) { + r = mount_cgroups( + directory, + arg_unified_cgroup_hierarchy, + arg_userns_mode != USER_NAMESPACE_NO, + arg_uid_shift, + arg_uid_range, + arg_selinux_apifs_context, + false); + if (r < 0) + return r; + } r = mount_move_root(directory); if (r < 0) @@ -2992,7 +3145,7 @@ static int outer_child( return fd; pid = raw_clone(SIGCHLD|CLONE_NEWNS| - (arg_share_system ? 0 : CLONE_NEWIPC|CLONE_NEWPID|CLONE_NEWUTS) | + arg_clone_ns_flags | (arg_private_network ? CLONE_NEWNET : 0) | (arg_userns_mode != USER_NAMESPACE_NO ? CLONE_NEWUSER : 0)); if (pid < 0) @@ -3442,18 +3595,437 @@ static int load_settings(void) { return 0; } +static int run(int master, + const char* console, + const char *root_device, bool root_device_rw, + const char *home_device, bool home_device_rw, + const char *srv_device, bool srv_device_rw, + const char *esp_device, + bool interactive, + bool secondary, + FDSet *fds, + char veth_name[IFNAMSIZ], bool *veth_created, + union in_addr_union *exposed, + pid_t *pid, int *ret) { + + static const struct sigaction sa = { + .sa_handler = nop_signal_handler, + .sa_flags = SA_NOCLDSTOP, + }; + + _cleanup_release_lock_file_ LockFile uid_shift_lock = LOCK_FILE_INIT; + _cleanup_close_ int etc_passwd_lock = -1; + _cleanup_close_pair_ int + kmsg_socket_pair[2] = { -1, -1 }, + rtnl_socket_pair[2] = { -1, -1 }, + pid_socket_pair[2] = { -1, -1 }, + uuid_socket_pair[2] = { -1, -1 }, + notify_socket_pair[2] = { -1, -1 }, + uid_shift_socket_pair[2] = { -1, -1 }; + _cleanup_close_ int notify_socket= -1; + _cleanup_(barrier_destroy) Barrier barrier = BARRIER_NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(pty_forward_freep) PTYForward *forward = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + ContainerStatus container_status = 0; + char last_char = 0; + int ifi = 0, r; + ssize_t l; + sigset_t mask_chld; + + assert_se(sigemptyset(&mask_chld) == 0); + assert_se(sigaddset(&mask_chld, SIGCHLD) == 0); + + if (arg_userns_mode == USER_NAMESPACE_PICK) { + /* When we shall pick the UID/GID range, let's first lock /etc/passwd, so that we can safely + * check with getpwuid() if the specific user already exists. Note that /etc might be + * read-only, in which case this will fail with EROFS. But that's really OK, as in that case we + * can be reasonably sure that no users are going to be added. Note that getpwuid() checks are + * really just an extra safety net. We kinda assume that the UID range we allocate from is + * really ours. */ + + etc_passwd_lock = take_etc_passwd_lock(NULL); + if (etc_passwd_lock < 0 && etc_passwd_lock != -EROFS) + return log_error_errno(etc_passwd_lock, "Failed to take /etc/passwd lock: %m"); + } + + r = barrier_create(&barrier); + if (r < 0) + return log_error_errno(r, "Cannot initialize IPC barrier: %m"); + + if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, kmsg_socket_pair) < 0) + return log_error_errno(errno, "Failed to create kmsg socket pair: %m"); + + if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, rtnl_socket_pair) < 0) + return log_error_errno(errno, "Failed to create rtnl socket pair: %m"); + + if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, pid_socket_pair) < 0) + return log_error_errno(errno, "Failed to create pid socket pair: %m"); + + if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, uuid_socket_pair) < 0) + return log_error_errno(errno, "Failed to create id socket pair: %m"); + + if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, notify_socket_pair) < 0) + return log_error_errno(errno, "Failed to create notify socket pair: %m"); + + if (arg_userns_mode != USER_NAMESPACE_NO) + if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, uid_shift_socket_pair) < 0) + return log_error_errno(errno, "Failed to create uid shift socket pair: %m"); + + /* Child can be killed before execv(), so handle SIGCHLD in order to interrupt + * parent's blocking calls and give it a chance to call wait() and terminate. */ + r = sigprocmask(SIG_UNBLOCK, &mask_chld, NULL); + if (r < 0) + return log_error_errno(errno, "Failed to change the signal mask: %m"); + + r = sigaction(SIGCHLD, &sa, NULL); + if (r < 0) + return log_error_errno(errno, "Failed to install SIGCHLD handler: %m"); + + *pid = raw_clone(SIGCHLD|CLONE_NEWNS); + if (*pid < 0) + return log_error_errno(errno, "clone() failed%s: %m", + errno == EINVAL ? + ", do you have namespace support enabled in your kernel? (You need UTS, IPC, PID and NET namespacing built in)" : ""); + + if (*pid == 0) { + /* The outer child only has a file system namespace. */ + barrier_set_role(&barrier, BARRIER_CHILD); + + master = safe_close(master); + + kmsg_socket_pair[0] = safe_close(kmsg_socket_pair[0]); + rtnl_socket_pair[0] = safe_close(rtnl_socket_pair[0]); + pid_socket_pair[0] = safe_close(pid_socket_pair[0]); + uuid_socket_pair[0] = safe_close(uuid_socket_pair[0]); + notify_socket_pair[0] = safe_close(notify_socket_pair[0]); + uid_shift_socket_pair[0] = safe_close(uid_shift_socket_pair[0]); + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + r = outer_child(&barrier, + arg_directory, + console, + root_device, root_device_rw, + home_device, home_device_rw, + srv_device, srv_device_rw, + esp_device, + interactive, + secondary, + pid_socket_pair[1], + uuid_socket_pair[1], + notify_socket_pair[1], + kmsg_socket_pair[1], + rtnl_socket_pair[1], + uid_shift_socket_pair[1], + fds); + if (r < 0) + _exit(EXIT_FAILURE); + + _exit(EXIT_SUCCESS); + } + + barrier_set_role(&barrier, BARRIER_PARENT); + + fds = fdset_free(fds); + + kmsg_socket_pair[1] = safe_close(kmsg_socket_pair[1]); + rtnl_socket_pair[1] = safe_close(rtnl_socket_pair[1]); + pid_socket_pair[1] = safe_close(pid_socket_pair[1]); + uuid_socket_pair[1] = safe_close(uuid_socket_pair[1]); + notify_socket_pair[1] = safe_close(notify_socket_pair[1]); + uid_shift_socket_pair[1] = safe_close(uid_shift_socket_pair[1]); + + if (arg_userns_mode != USER_NAMESPACE_NO) { + /* The child just let us know the UID shift it might have read from the image. */ + l = recv(uid_shift_socket_pair[0], &arg_uid_shift, sizeof arg_uid_shift, 0); + if (l < 0) + return log_error_errno(errno, "Failed to read UID shift: %m"); + + if (l != sizeof arg_uid_shift) { + log_error("Short read while reading UID shift."); + return -EIO; + } + + if (arg_userns_mode == USER_NAMESPACE_PICK) { + /* If we are supposed to pick the UID shift, let's try to use the shift read from the + * image, but if that's already in use, pick a new one, and report back to the child, + * which one we now picked. */ + + r = uid_shift_pick(&arg_uid_shift, &uid_shift_lock); + if (r < 0) + return log_error_errno(r, "Failed to pick suitable UID/GID range: %m"); + + l = send(uid_shift_socket_pair[0], &arg_uid_shift, sizeof arg_uid_shift, MSG_NOSIGNAL); + if (l < 0) + return log_error_errno(errno, "Failed to send UID shift: %m"); + if (l != sizeof arg_uid_shift) { + log_error("Short write while writing UID shift."); + return -EIO; + } + } + } + + /* Wait for the outer child. */ + r = wait_for_terminate_and_warn("namespace helper", *pid, NULL); + if (r != 0) + return r < 0 ? r : -EIO; + + /* And now retrieve the PID of the inner child. */ + l = recv(pid_socket_pair[0], pid, sizeof *pid, 0); + if (l < 0) + return log_error_errno(errno, "Failed to read inner child PID: %m"); + if (l != sizeof *pid) { + log_error("Short read while reading inner child PID."); + return -EIO; + } + + /* We also retrieve container UUID in case it was generated by outer child */ + l = recv(uuid_socket_pair[0], &arg_uuid, sizeof arg_uuid, 0); + if (l < 0) + return log_error_errno(errno, "Failed to read container machine ID: %m"); + if (l != sizeof(arg_uuid)) { + log_error("Short read while reading container machined ID."); + return -EIO; + } + + /* We also retrieve the socket used for notifications generated by outer child */ + notify_socket = receive_one_fd(notify_socket_pair[0], 0); + if (notify_socket < 0) + return log_error_errno(notify_socket, + "Failed to receive notification socket from the outer child: %m"); + + log_debug("Init process invoked as PID "PID_FMT, *pid); + + if (arg_userns_mode != USER_NAMESPACE_NO) { + if (!barrier_place_and_sync(&barrier)) { /* #1 */ + log_error("Child died too early."); + return -ESRCH; + } + + r = setup_uid_map(*pid); + if (r < 0) + return r; + + (void) barrier_place(&barrier); /* #2 */ + } + + if (arg_private_network) { + + r = move_network_interfaces(*pid, arg_network_interfaces); + if (r < 0) + return r; + + if (arg_network_veth) { + r = setup_veth(arg_machine, *pid, veth_name, + arg_network_bridge || arg_network_zone); + if (r < 0) + return r; + else if (r > 0) + ifi = r; + + if (arg_network_bridge) { + /* Add the interface to a bridge */ + r = setup_bridge(veth_name, arg_network_bridge, false); + if (r < 0) + return r; + if (r > 0) + ifi = r; + } else if (arg_network_zone) { + /* Add the interface to a bridge, possibly creating it */ + r = setup_bridge(veth_name, arg_network_zone, true); + if (r < 0) + return r; + if (r > 0) + ifi = r; + } + } + + r = setup_veth_extra(arg_machine, *pid, arg_network_veth_extra); + if (r < 0) + return r; + + /* We created the primary and extra veth links now; let's remember this, so that we know to + remove them later on. Note that we don't bother with removing veth links that were created + here when their setup failed half-way, because in that case the kernel should be able to + remove them on its own, since they cannot be referenced by anything yet. */ + *veth_created = true; + + r = setup_macvlan(arg_machine, *pid, arg_network_macvlan); + if (r < 0) + return r; + + r = setup_ipvlan(arg_machine, *pid, arg_network_ipvlan); + if (r < 0) + return r; + } + + if (arg_register) { + r = register_machine( + arg_machine, + *pid, + arg_directory, + arg_uuid, + ifi, + arg_slice, + arg_custom_mounts, arg_n_custom_mounts, + arg_kill_signal, + arg_property, + arg_keep_unit, + arg_container_service_name); + if (r < 0) + return r; + } + + r = sync_cgroup(*pid, arg_unified_cgroup_hierarchy, arg_uid_shift); + if (r < 0) + return r; + + if (arg_keep_unit) { + r = create_subcgroup(*pid, arg_unified_cgroup_hierarchy); + if (r < 0) + return r; + } + + r = chown_cgroup(*pid, arg_uid_shift); + if (r < 0) + return r; + + /* Notify the child that the parent is ready with all + * its setup (including cgroup-ification), and that + * the child can now hand over control to the code to + * run inside the container. */ + (void) barrier_place(&barrier); /* #3 */ + + /* Block SIGCHLD here, before notifying child. + * process_pty() will handle it with the other signals. */ + assert_se(sigprocmask(SIG_BLOCK, &mask_chld, NULL) >= 0); + + /* Reset signal to default */ + r = default_signals(SIGCHLD, -1); + if (r < 0) + return log_error_errno(r, "Failed to reset SIGCHLD: %m"); + + r = sd_event_new(&event); + if (r < 0) + return log_error_errno(r, "Failed to get default event source: %m"); + + r = setup_sd_notify_parent(event, notify_socket, PID_TO_PTR(*pid)); + if (r < 0) + return r; + + /* Let the child know that we are ready and wait that the child is completely ready now. */ + if (!barrier_place_and_sync(&barrier)) { /* #4 */ + log_error("Child died too early."); + return -ESRCH; + } + + /* At this point we have made use of the UID we picked, and thus nss-mymachines + * will make them appear in getpwuid(), thus we can release the /etc/passwd lock. */ + etc_passwd_lock = safe_close(etc_passwd_lock); + + sd_notifyf(false, + "STATUS=Container running.\n" + "X_NSPAWN_LEADER_PID=" PID_FMT, *pid); + if (!arg_notify_ready) + sd_notify(false, "READY=1\n"); + + if (arg_kill_signal > 0) { + /* Try to kill the init system on SIGINT or SIGTERM */ + sd_event_add_signal(event, NULL, SIGINT, on_orderly_shutdown, PID_TO_PTR(*pid)); + sd_event_add_signal(event, NULL, SIGTERM, on_orderly_shutdown, PID_TO_PTR(*pid)); + } else { + /* Immediately exit */ + sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); + sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); + } + + /* simply exit on sigchld */ + sd_event_add_signal(event, NULL, SIGCHLD, NULL, NULL); + + if (arg_expose_ports) { + r = expose_port_watch_rtnl(event, rtnl_socket_pair[0], on_address_change, exposed, &rtnl); + if (r < 0) + return r; + + (void) expose_port_execute(rtnl, arg_expose_ports, exposed); + } + + rtnl_socket_pair[0] = safe_close(rtnl_socket_pair[0]); + + r = pty_forward_new(event, master, + PTY_FORWARD_IGNORE_VHANGUP | (interactive ? 0 : PTY_FORWARD_READ_ONLY), + &forward); + if (r < 0) + return log_error_errno(r, "Failed to create PTY forwarder: %m"); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + pty_forward_get_last_char(forward, &last_char); + + forward = pty_forward_free(forward); + + if (!arg_quiet && last_char != '\n') + putc('\n', stdout); + + /* Kill if it is not dead yet anyway */ + if (arg_register && !arg_keep_unit) + terminate_machine(*pid); + + /* Normally redundant, but better safe than sorry */ + kill(*pid, SIGKILL); + + r = wait_for_container(*pid, &container_status); + *pid = 0; + + if (r < 0) + /* We failed to wait for the container, or the container exited abnormally. */ + return r; + if (r > 0 || container_status == CONTAINER_TERMINATED) { + /* r > 0 → The container exited with a non-zero status. + * As a special case, we need to replace 133 with a different value, + * because 133 is special-cased in the service file to reboot the container. + * otherwise → The container exited with zero status and a reboot was not requested. + */ + if (r == 133) + r = EXIT_FAILURE; /* replace 133 with the general failure code */ + *ret = r; + return 0; /* finito */ + } + + /* CONTAINER_REBOOTED, loop again */ + + if (arg_keep_unit) { + /* Special handling if we are running as a service: instead of simply + * restarting the machine we want to restart the entire service, so let's + * inform systemd about this with the special exit code 133. The service + * file uses RestartForceExitStatus=133 so that this results in a full + * nspawn restart. This is necessary since we might have cgroup parameters + * set we want to have flushed out. */ + *ret = 0; + return 133; + } + + expose_port_flush(arg_expose_ports, exposed); + + (void) remove_veth_links(veth_name, arg_network_veth_extra); + *veth_created = false; + return 1; /* loop again */ +} + int main(int argc, char *argv[]) { - _cleanup_free_ char *device_path = NULL, *root_device = NULL, *home_device = NULL, *srv_device = NULL, *console = NULL; + _cleanup_free_ char *device_path = NULL, *root_device = NULL, *home_device = NULL, *srv_device = NULL, *esp_device = NULL, *console = NULL; bool root_device_rw = true, home_device_rw = true, srv_device_rw = true; _cleanup_close_ int master = -1, image_fd = -1; _cleanup_fdset_free_ FDSet *fds = NULL; - int r, n_fd_passed, loop_nr = -1; + int r, n_fd_passed, loop_nr = -1, ret = EXIT_FAILURE; char veth_name[IFNAMSIZ] = ""; bool secondary = false, remove_subvol = false; - sigset_t mask_chld; pid_t pid = 0; - int ret = EXIT_SUCCESS; union in_addr_union exposed = {}; _cleanup_release_lock_file_ LockFile tree_global_lock = LOCK_FILE_INIT, tree_local_lock = LOCK_FILE_INIT; bool interactive, veth_created = false; @@ -3626,6 +4198,7 @@ int main(int argc, char *argv[]) { &root_device, &root_device_rw, &home_device, &home_device_rw, &srv_device, &srv_device_rw, + &esp_device, &secondary); if (r < 0) goto finish; @@ -3668,469 +4241,25 @@ int main(int argc, char *argv[]) { assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGWINCH, SIGTERM, SIGINT, -1) >= 0); - assert_se(sigemptyset(&mask_chld) == 0); - assert_se(sigaddset(&mask_chld, SIGCHLD) == 0); - if (prctl(PR_SET_CHILD_SUBREAPER, 1) < 0) { r = log_error_errno(errno, "Failed to become subreaper: %m"); goto finish; } for (;;) { - static const struct sigaction sa = { - .sa_handler = nop_signal_handler, - .sa_flags = SA_NOCLDSTOP, - }; - - _cleanup_release_lock_file_ LockFile uid_shift_lock = LOCK_FILE_INIT; - _cleanup_close_ int etc_passwd_lock = -1; - _cleanup_close_pair_ int - kmsg_socket_pair[2] = { -1, -1 }, - rtnl_socket_pair[2] = { -1, -1 }, - pid_socket_pair[2] = { -1, -1 }, - uuid_socket_pair[2] = { -1, -1 }, - notify_socket_pair[2] = { -1, -1 }, - uid_shift_socket_pair[2] = { -1, -1 }; - _cleanup_close_ int notify_socket= -1; - _cleanup_(barrier_destroy) Barrier barrier = BARRIER_NULL; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - _cleanup_(pty_forward_freep) PTYForward *forward = NULL; - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - ContainerStatus container_status; - char last_char = 0; - int ifi = 0; - ssize_t l; - - if (arg_userns_mode == USER_NAMESPACE_PICK) { - /* When we shall pick the UID/GID range, let's first lock /etc/passwd, so that we can safely - * check with getpwuid() if the specific user already exists. Note that /etc might be - * read-only, in which case this will fail with EROFS. But that's really OK, as in that case we - * can be reasonably sure that no users are going to be added. Note that getpwuid() checks are - * really just an extra safety net. We kinda assume that the UID range we allocate from is - * really ours. */ - - etc_passwd_lock = take_etc_passwd_lock(NULL); - if (etc_passwd_lock < 0 && etc_passwd_lock != -EROFS) { - log_error_errno(r, "Failed to take /etc/passwd lock: %m"); - goto finish; - } - } - - r = barrier_create(&barrier); - if (r < 0) { - log_error_errno(r, "Cannot initialize IPC barrier: %m"); - goto finish; - } - - if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, kmsg_socket_pair) < 0) { - r = log_error_errno(errno, "Failed to create kmsg socket pair: %m"); - goto finish; - } - - if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, rtnl_socket_pair) < 0) { - r = log_error_errno(errno, "Failed to create rtnl socket pair: %m"); - goto finish; - } - - if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, pid_socket_pair) < 0) { - r = log_error_errno(errno, "Failed to create pid socket pair: %m"); - goto finish; - } - - if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, uuid_socket_pair) < 0) { - r = log_error_errno(errno, "Failed to create id socket pair: %m"); - goto finish; - } - - if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, notify_socket_pair) < 0) { - r = log_error_errno(errno, "Failed to create notify socket pair: %m"); - goto finish; - } - - if (arg_userns_mode != USER_NAMESPACE_NO) - if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, uid_shift_socket_pair) < 0) { - r = log_error_errno(errno, "Failed to create uid shift socket pair: %m"); - goto finish; - } - - /* Child can be killed before execv(), so handle SIGCHLD - * in order to interrupt parent's blocking calls and - * give it a chance to call wait() and terminate. */ - r = sigprocmask(SIG_UNBLOCK, &mask_chld, NULL); - if (r < 0) { - r = log_error_errno(errno, "Failed to change the signal mask: %m"); - goto finish; - } - - r = sigaction(SIGCHLD, &sa, NULL); - if (r < 0) { - r = log_error_errno(errno, "Failed to install SIGCHLD handler: %m"); - goto finish; - } - - pid = raw_clone(SIGCHLD|CLONE_NEWNS); - if (pid < 0) { - if (errno == EINVAL) - r = log_error_errno(errno, "clone() failed, do you have namespace support enabled in your kernel? (You need UTS, IPC, PID and NET namespacing built in): %m"); - else - r = log_error_errno(errno, "clone() failed: %m"); - - goto finish; - } - - if (pid == 0) { - /* The outer child only has a file system namespace. */ - barrier_set_role(&barrier, BARRIER_CHILD); - - master = safe_close(master); - - kmsg_socket_pair[0] = safe_close(kmsg_socket_pair[0]); - rtnl_socket_pair[0] = safe_close(rtnl_socket_pair[0]); - pid_socket_pair[0] = safe_close(pid_socket_pair[0]); - uuid_socket_pair[0] = safe_close(uuid_socket_pair[0]); - notify_socket_pair[0] = safe_close(notify_socket_pair[0]); - uid_shift_socket_pair[0] = safe_close(uid_shift_socket_pair[0]); - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - r = outer_child(&barrier, - arg_directory, - console, - root_device, root_device_rw, - home_device, home_device_rw, - srv_device, srv_device_rw, - interactive, - secondary, - pid_socket_pair[1], - uuid_socket_pair[1], - notify_socket_pair[1], - kmsg_socket_pair[1], - rtnl_socket_pair[1], - uid_shift_socket_pair[1], - fds); - if (r < 0) - _exit(EXIT_FAILURE); - - _exit(EXIT_SUCCESS); - } - - barrier_set_role(&barrier, BARRIER_PARENT); - - fds = fdset_free(fds); - - kmsg_socket_pair[1] = safe_close(kmsg_socket_pair[1]); - rtnl_socket_pair[1] = safe_close(rtnl_socket_pair[1]); - pid_socket_pair[1] = safe_close(pid_socket_pair[1]); - uuid_socket_pair[1] = safe_close(uuid_socket_pair[1]); - notify_socket_pair[1] = safe_close(notify_socket_pair[1]); - uid_shift_socket_pair[1] = safe_close(uid_shift_socket_pair[1]); - - if (arg_userns_mode != USER_NAMESPACE_NO) { - /* The child just let us know the UID shift it might have read from the image. */ - l = recv(uid_shift_socket_pair[0], &arg_uid_shift, sizeof(arg_uid_shift), 0); - if (l < 0) { - r = log_error_errno(errno, "Failed to read UID shift: %m"); - goto finish; - } - if (l != sizeof(arg_uid_shift)) { - log_error("Short read while reading UID shift."); - r = EIO; - goto finish; - } - - if (arg_userns_mode == USER_NAMESPACE_PICK) { - /* If we are supposed to pick the UID shift, let's try to use the shift read from the - * image, but if that's already in use, pick a new one, and report back to the child, - * which one we now picked. */ - - r = uid_shift_pick(&arg_uid_shift, &uid_shift_lock); - if (r < 0) { - log_error_errno(r, "Failed to pick suitable UID/GID range: %m"); - goto finish; - } - - l = send(uid_shift_socket_pair[0], &arg_uid_shift, sizeof(arg_uid_shift), MSG_NOSIGNAL); - if (l < 0) { - r = log_error_errno(errno, "Failed to send UID shift: %m"); - goto finish; - } - if (l != sizeof(arg_uid_shift)) { - log_error("Short write while writing UID shift."); - r = -EIO; - goto finish; - } - } - } - - /* Wait for the outer child. */ - r = wait_for_terminate_and_warn("namespace helper", pid, NULL); - if (r < 0) - goto finish; - if (r != 0) { - r = -EIO; - goto finish; - } - pid = 0; - - /* And now retrieve the PID of the inner child. */ - l = recv(pid_socket_pair[0], &pid, sizeof(pid), 0); - if (l < 0) { - r = log_error_errno(errno, "Failed to read inner child PID: %m"); - goto finish; - } - if (l != sizeof(pid)) { - log_error("Short read while reading inner child PID."); - r = EIO; - goto finish; - } - - /* We also retrieve container UUID in case it was generated by outer child */ - l = recv(uuid_socket_pair[0], &arg_uuid, sizeof(arg_uuid), 0); - if (l < 0) { - r = log_error_errno(errno, "Failed to read container machine ID: %m"); - goto finish; - } - if (l != sizeof(arg_uuid)) { - log_error("Short read while reading container machined ID."); - r = EIO; - goto finish; - } - - /* We also retrieve the socket used for notifications generated by outer child */ - notify_socket = receive_one_fd(notify_socket_pair[0], 0); - if (notify_socket < 0) { - r = log_error_errno(errno, "Failed to receive notification socket from the outer child: %m"); - goto finish; - } - - log_debug("Init process invoked as PID " PID_FMT, pid); - - if (arg_userns_mode != USER_NAMESPACE_NO) { - if (!barrier_place_and_sync(&barrier)) { /* #1 */ - log_error("Child died too early."); - r = -ESRCH; - goto finish; - } - - r = setup_uid_map(pid); - if (r < 0) - goto finish; - - (void) barrier_place(&barrier); /* #2 */ - } - - if (arg_private_network) { - - r = move_network_interfaces(pid, arg_network_interfaces); - if (r < 0) - goto finish; - - if (arg_network_veth) { - r = setup_veth(arg_machine, pid, veth_name, - arg_network_bridge || arg_network_zone); - if (r < 0) - goto finish; - else if (r > 0) - ifi = r; - - if (arg_network_bridge) { - /* Add the interface to a bridge */ - r = setup_bridge(veth_name, arg_network_bridge, false); - if (r < 0) - goto finish; - if (r > 0) - ifi = r; - } else if (arg_network_zone) { - /* Add the interface to a bridge, possibly creating it */ - r = setup_bridge(veth_name, arg_network_zone, true); - if (r < 0) - goto finish; - if (r > 0) - ifi = r; - } - } - - r = setup_veth_extra(arg_machine, pid, arg_network_veth_extra); - if (r < 0) - goto finish; - - /* We created the primary and extra veth links now; let's remember this, so that we know to - remove them later on. Note that we don't bother with removing veth links that were created - here when their setup failed half-way, because in that case the kernel should be able to - remove them on its own, since they cannot be referenced by anything yet. */ - veth_created = true; - - r = setup_macvlan(arg_machine, pid, arg_network_macvlan); - if (r < 0) - goto finish; - - r = setup_ipvlan(arg_machine, pid, arg_network_ipvlan); - if (r < 0) - goto finish; - } - - if (arg_register) { - r = register_machine( - arg_machine, - pid, - arg_directory, - arg_uuid, - ifi, - arg_slice, - arg_custom_mounts, arg_n_custom_mounts, - arg_kill_signal, - arg_property, - arg_keep_unit, - arg_container_service_name); - if (r < 0) - goto finish; - } - - r = sync_cgroup(pid, arg_unified_cgroup_hierarchy); - if (r < 0) - goto finish; - - if (arg_keep_unit) { - r = create_subcgroup(pid, arg_unified_cgroup_hierarchy); - if (r < 0) - goto finish; - } - - r = chown_cgroup(pid, arg_uid_shift); - if (r < 0) - goto finish; - - /* Notify the child that the parent is ready with all - * its setup (including cgroup-ification), and that - * the child can now hand over control to the code to - * run inside the container. */ - (void) barrier_place(&barrier); /* #3 */ - - /* Block SIGCHLD here, before notifying child. - * process_pty() will handle it with the other signals. */ - assert_se(sigprocmask(SIG_BLOCK, &mask_chld, NULL) >= 0); - - /* Reset signal to default */ - r = default_signals(SIGCHLD, -1); - if (r < 0) { - log_error_errno(r, "Failed to reset SIGCHLD: %m"); - goto finish; - } - - r = sd_event_new(&event); - if (r < 0) { - log_error_errno(r, "Failed to get default event source: %m"); - goto finish; - } - - r = setup_sd_notify_parent(event, notify_socket, PID_TO_PTR(pid)); - if (r < 0) - goto finish; - - /* Let the child know that we are ready and wait that the child is completely ready now. */ - if (!barrier_place_and_sync(&barrier)) { /* #4 */ - log_error("Child died too early."); - r = -ESRCH; - goto finish; - } - - /* At this point we have made use of the UID we picked, and thus nss-mymachines will make them appear - * in getpwuid(), thus we can release the /etc/passwd lock. */ - etc_passwd_lock = safe_close(etc_passwd_lock); - - sd_notifyf(false, - "STATUS=Container running.\n" - "X_NSPAWN_LEADER_PID=" PID_FMT, pid); - if (!arg_notify_ready) - sd_notify(false, "READY=1\n"); - - if (arg_kill_signal > 0) { - /* Try to kill the init system on SIGINT or SIGTERM */ - sd_event_add_signal(event, NULL, SIGINT, on_orderly_shutdown, PID_TO_PTR(pid)); - sd_event_add_signal(event, NULL, SIGTERM, on_orderly_shutdown, PID_TO_PTR(pid)); - } else { - /* Immediately exit */ - sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); - sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); - } - - /* simply exit on sigchld */ - sd_event_add_signal(event, NULL, SIGCHLD, NULL, NULL); - - if (arg_expose_ports) { - r = expose_port_watch_rtnl(event, rtnl_socket_pair[0], on_address_change, &exposed, &rtnl); - if (r < 0) - goto finish; - - (void) expose_port_execute(rtnl, arg_expose_ports, &exposed); - } - - rtnl_socket_pair[0] = safe_close(rtnl_socket_pair[0]); - - r = pty_forward_new(event, master, PTY_FORWARD_IGNORE_VHANGUP | (interactive ? 0 : PTY_FORWARD_READ_ONLY), &forward); - if (r < 0) { - log_error_errno(r, "Failed to create PTY forwarder: %m"); - goto finish; - } - - r = sd_event_loop(event); - if (r < 0) { - log_error_errno(r, "Failed to run event loop: %m"); - goto finish; - } - - pty_forward_get_last_char(forward, &last_char); - - forward = pty_forward_free(forward); - - if (!arg_quiet && last_char != '\n') - putc('\n', stdout); - - /* Kill if it is not dead yet anyway */ - if (arg_register && !arg_keep_unit) - terminate_machine(pid); - - /* Normally redundant, but better safe than sorry */ - kill(pid, SIGKILL); - - r = wait_for_container(pid, &container_status); - pid = 0; - - if (r < 0) - /* We failed to wait for the container, or the - * container exited abnormally */ - goto finish; - else if (r > 0 || container_status == CONTAINER_TERMINATED) { - /* The container exited with a non-zero - * status, or with zero status and no reboot - * was requested. */ - ret = r; + r = run(master, + console, + root_device, root_device_rw, + home_device, home_device_rw, + srv_device, srv_device_rw, + esp_device, + interactive, secondary, + fds, + veth_name, &veth_created, + &exposed, + &pid, &ret); + if (r <= 0) break; - } - - /* CONTAINER_REBOOTED, loop again */ - - if (arg_keep_unit) { - /* Special handling if we are running as a - * service: instead of simply restarting the - * machine we want to restart the entire - * service, so let's inform systemd about this - * with the special exit code 133. The service - * file uses RestartForceExitStatus=133 so - * that this results in a full nspawn - * restart. This is necessary since we might - * have cgroup parameters set we want to have - * flushed out. */ - ret = 133; - r = 0; - break; - } - - expose_port_flush(arg_expose_ports, &exposed); - - (void) remove_veth_links(veth_name, arg_network_veth_extra); - veth_created = false; } finish: diff --git a/src/nss-mymachines/nss-mymachines.c b/src/nss-mymachines/nss-mymachines.c index 8d57b26cbc..895f61c462 100644 --- a/src/nss-mymachines/nss-mymachines.c +++ b/src/nss-mymachines/nss-mymachines.c @@ -25,6 +25,7 @@ #include "alloc-util.h" #include "bus-common-errors.h" +#include "env-util.h" #include "hostname-util.h" #include "in-addr-util.h" #include "macro.h" @@ -434,6 +435,12 @@ enum nss_status _nss_mymachines_getpwnam_r( if (!machine_name_is_valid(machine)) goto not_found; + if (getenv_bool("SYSTEMD_NSS_BYPASS_BUS") > 0) + /* Make sure we can't deadlock if we are invoked by dbus-daemon. This way, it won't be able to resolve + * these UIDs, but that should be unproblematic as containers should never be able to connect to a bus + * running on the host. */ + goto not_found; + r = sd_bus_open_system(&bus); if (r < 0) goto fail; @@ -514,6 +521,9 @@ enum nss_status _nss_mymachines_getpwuid_r( if (uid < HOST_UID_LIMIT) goto not_found; + if (getenv_bool("SYSTEMD_NSS_BYPASS_BUS") > 0) + goto not_found; + r = sd_bus_open_system(&bus); if (r < 0) goto fail; @@ -605,6 +615,9 @@ enum nss_status _nss_mymachines_getgrnam_r( if (!machine_name_is_valid(machine)) goto not_found; + if (getenv_bool("SYSTEMD_NSS_BYPASS_BUS") > 0) + goto not_found; + r = sd_bus_open_system(&bus); if (r < 0) goto fail; @@ -682,6 +695,9 @@ enum nss_status _nss_mymachines_getgrgid_r( if (gid < HOST_GID_LIMIT) goto not_found; + if (getenv_bool("SYSTEMD_NSS_BYPASS_BUS") > 0) + goto not_found; + r = sd_bus_open_system(&bus); if (r < 0) goto fail; diff --git a/src/nss-resolve/nss-resolve.c b/src/nss-resolve/nss-resolve.c index 5ce10f1cbd..d46a3afe91 100644 --- a/src/nss-resolve/nss-resolve.c +++ b/src/nss-resolve/nss-resolve.c @@ -121,6 +121,7 @@ enum nss_status _nss_resolve_gethostbyname4_r( _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; struct gaih_addrtuple *r_tuple, *r_tuple_first = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + enum nss_status ret = NSS_STATUS_UNAVAIL; const char *canonical = NULL; size_t l, ms, idx; char *r_name; @@ -167,6 +168,10 @@ enum nss_status _nss_resolve_gethostbyname4_r( if (bus_error_shall_fallback(&error)) goto fallback; + /* Treat all other error conditions as NOTFOUND, and fail. This includes DNSSEC errors and + suchlike. (We don't use UNAVAIL in this case so that the nsswitch.conf configuration can distuingish + such executed but negative replies from complete failure to talk to resolved. */ + ret = NSS_STATUS_NOTFOUND; goto fail; } @@ -281,7 +286,7 @@ fallback: fail: *errnop = -r; *h_errnop = NO_RECOVERY; - return NSS_STATUS_UNAVAIL; + return ret; } enum nss_status _nss_resolve_gethostbyname3_r( @@ -297,6 +302,7 @@ enum nss_status _nss_resolve_gethostbyname3_r( _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; char *r_name, *r_aliases, *r_addr, *r_addr_list; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + enum nss_status ret = NSS_STATUS_UNAVAIL; size_t l, idx, ms, alen; const char *canonical; int c, r, i = 0; @@ -350,6 +356,7 @@ enum nss_status _nss_resolve_gethostbyname3_r( if (bus_error_shall_fallback(&error)) goto fallback; + ret = NSS_STATUS_NOTFOUND; goto fail; } @@ -476,7 +483,7 @@ fallback: fail: *errnop = -r; *h_errnop = NO_RECOVERY; - return NSS_STATUS_UNAVAIL; + return ret; } enum nss_status _nss_resolve_gethostbyaddr2_r( @@ -491,6 +498,7 @@ enum nss_status _nss_resolve_gethostbyaddr2_r( _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; char *r_name, *r_aliases, *r_addr, *r_addr_list; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + enum nss_status ret = NSS_STATUS_UNAVAIL; unsigned c = 0, i = 0; size_t ms = 0, idx; const char *n; @@ -557,10 +565,8 @@ enum nss_status _nss_resolve_gethostbyaddr2_r( if (bus_error_shall_fallback(&error)) goto fallback; - - *errnop = -r; - *h_errnop = NO_RECOVERY; - return NSS_STATUS_UNAVAIL; + ret = NSS_STATUS_NOTFOUND; + goto fail; } r = sd_bus_message_enter_container(reply, 'a', "(is)"); @@ -668,7 +674,7 @@ fallback: fail: *errnop = -r; *h_errnop = NO_RECOVERY; - return NSS_STATUS_UNAVAIL; + return ret; } NSS_GETHOSTBYNAME_FALLBACKS(resolve); diff --git a/src/nss-systemd/Makefile b/src/nss-systemd/Makefile new file mode 120000 index 0000000000..d0b0e8e008 --- /dev/null +++ b/src/nss-systemd/Makefile @@ -0,0 +1 @@ +../Makefile
\ No newline at end of file diff --git a/src/nss-systemd/nss-systemd.c b/src/nss-systemd/nss-systemd.c new file mode 100644 index 0000000000..17d04e958d --- /dev/null +++ b/src/nss-systemd/nss-systemd.c @@ -0,0 +1,523 @@ +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <nss.h> + +#include "sd-bus.h" + +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "env-util.h" +#include "fs-util.h" +#include "macro.h" +#include "nss-util.h" +#include "signal-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "user-util.h" +#include "util.h" + +#ifndef NOBODY_USER_NAME +#define NOBODY_USER_NAME "nobody" +#endif + +#ifndef NOBODY_GROUP_NAME +#define NOBODY_GROUP_NAME "nobody" +#endif + +static const struct passwd root_passwd = { + .pw_name = (char*) "root", + .pw_passwd = (char*) "x", /* see shadow file */ + .pw_uid = 0, + .pw_gid = 0, + .pw_gecos = (char*) "Super User", + .pw_dir = (char*) "/root", + .pw_shell = (char*) "/bin/sh", +}; + +static const struct passwd nobody_passwd = { + .pw_name = (char*) NOBODY_USER_NAME, + .pw_passwd = (char*) "*", /* locked */ + .pw_uid = 65534, + .pw_gid = 65534, + .pw_gecos = (char*) "User Nobody", + .pw_dir = (char*) "/", + .pw_shell = (char*) "/sbin/nologin", +}; + +static const struct group root_group = { + .gr_name = (char*) "root", + .gr_gid = 0, + .gr_passwd = (char*) "x", /* see shadow file */ + .gr_mem = (char*[]) { NULL }, +}; + +static const struct group nobody_group = { + .gr_name = (char*) NOBODY_GROUP_NAME, + .gr_gid = 65534, + .gr_passwd = (char*) "*", /* locked */ + .gr_mem = (char*[]) { NULL }, +}; + +NSS_GETPW_PROTOTYPES(systemd); +NSS_GETGR_PROTOTYPES(systemd); + +static int direct_lookup_name(const char *name, uid_t *ret) { + _cleanup_free_ char *s = NULL; + const char *path; + int r; + + assert(name); + + /* Normally, we go via the bus to resolve names. That has the benefit that it is available from any mount + * namespace and subject to proper authentication. However, there's one problem: if our module is called from + * dbus-daemon itself we really can't use D-Bus to communicate. In this case, resort to a client-side hack, + * and look for the dynamic names directly. This is pretty ugly, but breaks the cyclic dependency. */ + + path = strjoina("/run/systemd/dynamic-uid/direct:", name); + r = readlink_malloc(path, &s); + if (r < 0) + return r; + + return parse_uid(s, ret); +} + +static int direct_lookup_uid(uid_t uid, char **ret) { + char path[strlen("/run/systemd/dynamic-uid/direct:") + DECIMAL_STR_MAX(uid_t) + 1], *s; + int r; + + xsprintf(path, "/run/systemd/dynamic-uid/direct:" UID_FMT, uid); + + r = readlink_malloc(path, &s); + if (r < 0) + return r; + if (!valid_user_group_name(s)) { /* extra safety check */ + free(s); + return -EINVAL; + } + + *ret = s; + return 0; +} + +enum nss_status _nss_systemd_getpwnam_r( + const char *name, + struct passwd *pwd, + char *buffer, size_t buflen, + int *errnop) { + + uint32_t translated; + size_t l; + int r; + + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + + assert(name); + assert(pwd); + + if (!valid_user_group_name(name)) { + r = -EINVAL; + goto fail; + } + + /* Synthesize entries for the root and nobody users, in case they are missing in /etc/passwd */ + if (streq(name, root_passwd.pw_name)) { + *pwd = root_passwd; + *errnop = 0; + return NSS_STATUS_SUCCESS; + } + if (streq(name, nobody_passwd.pw_name)) { + *pwd = nobody_passwd; + *errnop = 0; + return NSS_STATUS_SUCCESS; + } + + /* Make sure that we don't go in circles when allocating a dynamic UID by checking our own database */ + if (getenv_bool("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0) + goto not_found; + + if (getenv_bool("SYSTEMD_NSS_BYPASS_BUS") > 0) { + + /* Access the dynamic UID allocation directly if we are called from dbus-daemon, see above. */ + r = direct_lookup_name(name, (uid_t*) &translated); + if (r == -ENOENT) + goto not_found; + if (r < 0) + goto fail; + + } else { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "LookupDynamicUserByName", + &error, + &reply, + "s", + name); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER)) + goto not_found; + + goto fail; + } + + r = sd_bus_message_read(reply, "u", &translated); + if (r < 0) + goto fail; + } + + l = strlen(name); + if (buflen < l+1) { + *errnop = ENOMEM; + return NSS_STATUS_TRYAGAIN; + } + + memcpy(buffer, name, l+1); + + pwd->pw_name = buffer; + pwd->pw_uid = (uid_t) translated; + pwd->pw_gid = (uid_t) translated; + pwd->pw_gecos = (char*) "Dynamic User"; + pwd->pw_passwd = (char*) "*"; /* locked */ + pwd->pw_dir = (char*) "/"; + pwd->pw_shell = (char*) "/sbin/nologin"; + + *errnop = 0; + return NSS_STATUS_SUCCESS; + +not_found: + *errnop = 0; + return NSS_STATUS_NOTFOUND; + +fail: + *errnop = -r; + return NSS_STATUS_UNAVAIL; +} + +enum nss_status _nss_systemd_getpwuid_r( + uid_t uid, + struct passwd *pwd, + char *buffer, size_t buflen, + int *errnop) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_free_ char *direct = NULL; + const char *translated; + size_t l; + int r; + + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + + if (!uid_is_valid(uid)) { + r = -EINVAL; + goto fail; + } + + /* Synthesize data for the root user and for nobody in case they are missing from /etc/passwd */ + if (uid == root_passwd.pw_uid) { + *pwd = root_passwd; + *errnop = 0; + return NSS_STATUS_SUCCESS; + } + if (uid == nobody_passwd.pw_uid) { + *pwd = nobody_passwd; + *errnop = 0; + return NSS_STATUS_SUCCESS; + } + + if (uid <= SYSTEM_UID_MAX) + goto not_found; + + if (getenv_bool("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0) + goto not_found; + + if (getenv_bool("SYSTEMD_NSS_BYPASS_BUS") > 0) { + + r = direct_lookup_uid(uid, &direct); + if (r == -ENOENT) + goto not_found; + if (r < 0) + goto fail; + + translated = direct; + + } else { + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "LookupDynamicUserByUID", + &error, + &reply, + "u", + (uint32_t) uid); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER)) + goto not_found; + + goto fail; + } + + r = sd_bus_message_read(reply, "s", &translated); + if (r < 0) + goto fail; + } + + l = strlen(translated) + 1; + if (buflen < l) { + *errnop = ENOMEM; + return NSS_STATUS_TRYAGAIN; + } + + memcpy(buffer, translated, l); + + pwd->pw_name = buffer; + pwd->pw_uid = uid; + pwd->pw_gid = uid; + pwd->pw_gecos = (char*) "Dynamic User"; + pwd->pw_passwd = (char*) "*"; /* locked */ + pwd->pw_dir = (char*) "/"; + pwd->pw_shell = (char*) "/sbin/nologin"; + + *errnop = 0; + return NSS_STATUS_SUCCESS; + +not_found: + *errnop = 0; + return NSS_STATUS_NOTFOUND; + +fail: + *errnop = -r; + return NSS_STATUS_UNAVAIL; +} + +enum nss_status _nss_systemd_getgrnam_r( + const char *name, + struct group *gr, + char *buffer, size_t buflen, + int *errnop) { + + uint32_t translated; + size_t l; + int r; + + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + + assert(name); + assert(gr); + + if (!valid_user_group_name(name)) { + r = -EINVAL; + goto fail; + } + + /* Synthesize records for root and nobody, in case they are missing form /etc/group */ + if (streq(name, root_group.gr_name)) { + *gr = root_group; + *errnop = 0; + return NSS_STATUS_SUCCESS; + } + if (streq(name, nobody_group.gr_name)) { + *gr = nobody_group; + *errnop = 0; + return NSS_STATUS_SUCCESS; + } + + if (getenv_bool("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0) + goto not_found; + + if (getenv_bool("SYSTEMD_NSS_BYPASS_BUS") > 0) { + + /* Access the dynamic GID allocation directly if we are called from dbus-daemon, see above. */ + r = direct_lookup_name(name, (uid_t*) &translated); + if (r == -ENOENT) + goto not_found; + if (r < 0) + goto fail; + } else { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "LookupDynamicUserByName", + &error, + &reply, + "s", + name); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER)) + goto not_found; + + goto fail; + } + + r = sd_bus_message_read(reply, "u", &translated); + if (r < 0) + goto fail; + } + + l = sizeof(char*) + strlen(name) + 1; + if (buflen < l) { + *errnop = ENOMEM; + return NSS_STATUS_TRYAGAIN; + } + + memzero(buffer, sizeof(char*)); + strcpy(buffer + sizeof(char*), name); + + gr->gr_name = buffer + sizeof(char*); + gr->gr_gid = (gid_t) translated; + gr->gr_passwd = (char*) "*"; /* locked */ + gr->gr_mem = (char**) buffer; + + *errnop = 0; + return NSS_STATUS_SUCCESS; + +not_found: + *errnop = 0; + return NSS_STATUS_NOTFOUND; + +fail: + *errnop = -r; + return NSS_STATUS_UNAVAIL; +} + +enum nss_status _nss_systemd_getgrgid_r( + gid_t gid, + struct group *gr, + char *buffer, size_t buflen, + int *errnop) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_free_ char *direct = NULL; + const char *translated; + size_t l; + int r; + + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + + if (!gid_is_valid(gid)) { + r = -EINVAL; + goto fail; + } + + /* Synthesize records for root and nobody, in case they are missing from /etc/group */ + if (gid == root_group.gr_gid) { + *gr = root_group; + *errnop = 0; + return NSS_STATUS_SUCCESS; + } + if (gid == nobody_group.gr_gid) { + *gr = nobody_group; + *errnop = 0; + return NSS_STATUS_SUCCESS; + } + + if (gid <= SYSTEM_GID_MAX) + goto not_found; + + if (getenv_bool("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0) + goto not_found; + + if (getenv_bool("SYSTEMD_NSS_BYPASS_BUS") > 0) { + + r = direct_lookup_uid(gid, &direct); + if (r == -ENOENT) + goto not_found; + if (r < 0) + goto fail; + + translated = direct; + } else { + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "LookupDynamicUserByUID", + &error, + &reply, + "u", + (uint32_t) gid); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER)) + goto not_found; + + goto fail; + } + + r = sd_bus_message_read(reply, "s", &translated); + if (r < 0) + goto fail; + } + + l = sizeof(char*) + strlen(translated) + 1; + if (buflen < l) { + *errnop = ENOMEM; + return NSS_STATUS_TRYAGAIN; + } + + memzero(buffer, sizeof(char*)); + strcpy(buffer + sizeof(char*), translated); + + gr->gr_name = buffer + sizeof(char*); + gr->gr_gid = gid; + gr->gr_passwd = (char*) "*"; /* locked */ + gr->gr_mem = (char**) buffer; + + *errnop = 0; + return NSS_STATUS_SUCCESS; + +not_found: + *errnop = 0; + return NSS_STATUS_NOTFOUND; + +fail: + *errnop = -r; + return NSS_STATUS_UNAVAIL; +} diff --git a/src/nss-systemd/nss-systemd.sym b/src/nss-systemd/nss-systemd.sym new file mode 100644 index 0000000000..955078788a --- /dev/null +++ b/src/nss-systemd/nss-systemd.sym @@ -0,0 +1,17 @@ +/*** + This file is part of systemd. + + 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. +***/ + +{ +global: + _nss_systemd_getpwnam_r; + _nss_systemd_getpwuid_r; + _nss_systemd_getgrnam_r; + _nss_systemd_getgrgid_r; +local: *; +}; diff --git a/src/quotacheck/quotacheck.c b/src/quotacheck/quotacheck.c index 6d8c05f046..2714cde5c7 100644 --- a/src/quotacheck/quotacheck.c +++ b/src/quotacheck/quotacheck.c @@ -32,7 +32,7 @@ static bool arg_skip = false; static bool arg_force = false; -static int parse_proc_cmdline_item(const char *key, const char *value) { +static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { if (streq(key, "quotacheck.mode") && value) { @@ -88,7 +88,7 @@ int main(int argc, char *argv[]) { umask(0022); - r = parse_proc_cmdline(parse_proc_cmdline_item); + r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, false); if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); diff --git a/src/remount-fs/remount-fs.c b/src/remount-fs/remount-fs.c index 6468d1eecd..c3bdcaf1da 100644 --- a/src/remount-fs/remount-fs.c +++ b/src/remount-fs/remount-fs.c @@ -137,7 +137,7 @@ int main(int argc, char *argv[]) { s = hashmap_remove(pids, PID_TO_PTR(si.si_pid)); if (s) { - if (!is_clean_exit(si.si_code, si.si_status, NULL)) { + if (!is_clean_exit(si.si_code, si.si_status, EXIT_CLEAN_COMMAND, NULL)) { if (si.si_code == CLD_EXITED) log_error(MOUNT_PATH " for %s exited with exit status %i.", s, si.si_status); else diff --git a/src/resolve/resolve-tool.c b/src/resolve/resolve-tool.c index 6ae3750417..9d4d04220c 100644 --- a/src/resolve/resolve-tool.c +++ b/src/resolve/resolve-tool.c @@ -395,7 +395,7 @@ static int output_rr_packet(const void *d, size_t l, int ifindex) { return 0; } -static int resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_t type) { +static int resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_t type, bool warn_missing) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; char ifname[IF_NAMESIZE] = ""; @@ -430,7 +430,8 @@ static int resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_ r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); if (r < 0) { - log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r)); + if (warn_missing || r != -ENXIO) + log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r)); return r; } @@ -488,7 +489,8 @@ static int resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_ return bus_log_parse_error(r); if (n == 0) { - log_error("%s: no records found", name); + if (warn_missing) + log_error("%s: no records found", name); return -ESRCH; } @@ -618,7 +620,7 @@ static int resolve_rfc4501(sd_bus *bus, const char *name) { if (type == 0) type = arg_type ?: DNS_TYPE_A; - return resolve_record(bus, n, class, type); + return resolve_record(bus, n, class, type, true); invalid: log_error("Invalid DNS URI: %s", name); @@ -778,7 +780,6 @@ static int resolve_service(sd_bus *bus, const char *name, const char *type, cons if (r < 0) return bus_log_parse_error(r); - c = 0; while ((r = sd_bus_message_read_array(reply, 'y', (const void**) &p, &sz)) > 0) { _cleanup_free_ char *escaped = NULL; @@ -787,7 +788,6 @@ static int resolve_service(sd_bus *bus, const char *name, const char *type, cons return log_oom(); printf("%*s%s\n", (int) indent, "", escaped); - c++; } if (r < 0) return bus_log_parse_error(r); @@ -840,16 +840,34 @@ static int resolve_openpgp(sd_bus *bus, const char *address) { } domain++; - r = string_hashsum_sha224(address, domain - 1 - address, &hashed); + r = string_hashsum_sha256(address, domain - 1 - address, &hashed); if (r < 0) return log_error_errno(r, "Hashing failed: %m"); + strshorten(hashed, 56); + full = strjoina(hashed, "._openpgpkey.", domain); log_debug("Looking up \"%s\".", full); - return resolve_record(bus, full, - arg_class ?: DNS_CLASS_IN, - arg_type ?: DNS_TYPE_OPENPGPKEY); + r = resolve_record(bus, full, + arg_class ?: DNS_CLASS_IN, + arg_type ?: DNS_TYPE_OPENPGPKEY, false); + + if (IN_SET(r, -ENXIO, -ESRCH)) { /* NXDOMAIN or NODATA? */ + hashed = NULL; + r = string_hashsum_sha224(address, domain - 1 - address, &hashed); + if (r < 0) + return log_error_errno(r, "Hashing failed: %m"); + + full = strjoina(hashed, "._openpgpkey.", domain); + log_debug("Looking up \"%s\".", full); + + return resolve_record(bus, full, + arg_class ?: DNS_CLASS_IN, + arg_type ?: DNS_TYPE_OPENPGPKEY, true); + } + + return r; } static int resolve_tlsa(sd_bus *bus, const char *address) { @@ -881,7 +899,7 @@ static int resolve_tlsa(sd_bus *bus, const char *address) { return resolve_record(bus, full, arg_class ?: DNS_CLASS_IN, - arg_type ?: DNS_TYPE_TLSA); + arg_type ?: DNS_TYPE_TLSA, true); } static int show_statistics(sd_bus *bus) { @@ -1542,7 +1560,7 @@ static void help(void) { "%1$s [OPTIONS...] --statistics\n" "%1$s [OPTIONS...] --reset-statistics\n" "\n" - "Resolve domain names, IPv4 and IPv6 addresses, DNS resource records, and services.\n\n" + "Resolve domain names, IPv4 and IPv6 addresses, DNS records, and services.\n\n" " -h --help Show this help\n" " --version Show package version\n" " --no-pager Do not pipe output into a pager\n" @@ -1877,7 +1895,7 @@ int main(int argc, char **argv) { while (argv[optind]) { int k; - k = resolve_record(bus, argv[optind], arg_class, arg_type); + k = resolve_record(bus, argv[optind], arg_class, arg_type, true); if (r == 0) r = k; diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c index dd233e7c4a..abf3263178 100644 --- a/src/resolve/resolved-conf.c +++ b/src/resolve/resolved-conf.c @@ -23,8 +23,19 @@ #include "extract-word.h" #include "parse-util.h" #include "resolved-conf.h" +#include "string-table.h" #include "string-util.h" +DEFINE_CONFIG_PARSE_ENUM(config_parse_dns_stub_listener_mode, dns_stub_listener_mode, DnsStubListenerMode, "Failed to parse DNS stub listener mode setting"); + +static const char* const dns_stub_listener_mode_table[_DNS_STUB_LISTENER_MODE_MAX] = { + [DNS_STUB_LISTENER_NO] = "no", + [DNS_STUB_LISTENER_UDP] = "udp", + [DNS_STUB_LISTENER_TCP] = "tcp", + [DNS_STUB_LISTENER_YES] = "yes", +}; +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(dns_stub_listener_mode, DnsStubListenerMode, DNS_STUB_LISTENER_YES); + int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char *word) { union in_addr_union address; int family, r, ifindex = 0; @@ -221,7 +232,7 @@ int manager_parse_config_file(Manager *m) { assert(m); - r = config_parse_many(PKGSYSCONFDIR "/resolved.conf", + r = config_parse_many_nulstr(PKGSYSCONFDIR "/resolved.conf", CONF_PATHS_NULSTR("systemd/resolved.conf.d"), "Resolve\0", config_item_perf_lookup, resolved_gperf_lookup, diff --git a/src/resolve/resolved-conf.h b/src/resolve/resolved-conf.h index e1fd2cceec..fc425a36b2 100644 --- a/src/resolve/resolved-conf.h +++ b/src/resolve/resolved-conf.h @@ -19,7 +19,19 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +typedef enum DnsStubListenerMode DnsStubListenerMode; + +enum DnsStubListenerMode { + DNS_STUB_LISTENER_NO, + DNS_STUB_LISTENER_UDP, + DNS_STUB_LISTENER_TCP, + DNS_STUB_LISTENER_YES, + _DNS_STUB_LISTENER_MODE_MAX, + _DNS_STUB_LISTENER_MODE_INVALID = -1 +}; + #include "resolved-manager.h" +#include "resolved-dns-server.h" int manager_parse_config_file(Manager *m); @@ -33,4 +45,7 @@ const struct ConfigPerfItem* resolved_gperf_lookup(const char *key, unsigned len int config_parse_dns_servers(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); int config_parse_search_domains(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); -int config_parse_dnssec(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); +int config_parse_dns_stub_listener_mode(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); + +const char* dns_stub_listener_mode_to_string(DnsStubListenerMode p) _const_; +DnsStubListenerMode dns_stub_listener_mode_from_string(const char *s) _pure_; diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index a8ad8fe342..337a8c473f 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -2143,7 +2143,7 @@ int dns_packet_extract(DnsPacket *p) { for (i = 0; i < n; i++) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - bool cache_flush; + bool cache_flush = false; r = dns_packet_read_rr(p, &rr, &cache_flush, NULL); if (r < 0) @@ -2289,6 +2289,7 @@ static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = { [DNS_RCODE_BADNAME] = "BADNAME", [DNS_RCODE_BADALG] = "BADALG", [DNS_RCODE_BADTRUNC] = "BADTRUNC", + [DNS_RCODE_BADCOOKIE] = "BADCOOKIE", }; DEFINE_STRING_TABLE_LOOKUP(dns_rcode, int); diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h index 7b7d4e14c9..054dc88a85 100644 --- a/src/resolve/resolved-dns-packet.h +++ b/src/resolve/resolved-dns-packet.h @@ -263,6 +263,7 @@ enum { DNS_RCODE_BADNAME = 20, DNS_RCODE_BADALG = 21, DNS_RCODE_BADTRUNC = 22, + DNS_RCODE_BADCOOKIE = 23, _DNS_RCODE_MAX_DEFINED, _DNS_RCODE_MAX = 4095 /* 4 bit rcode in the header plus 8 bit rcode in OPT, makes 12 bit */ }; diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index 53be18efc6..e03db4d003 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -83,9 +83,7 @@ DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c) { if (c->scope) LIST_REMOVE(candidates_by_scope, c->scope->query_candidates, c); - free(c); - - return NULL; + return mfree(c); } static int dns_query_candidate_next_search_domain(DnsQueryCandidate *c) { @@ -421,9 +419,7 @@ DnsQuery *dns_query_free(DnsQuery *q) { q->manager->n_dns_queries--; } - free(q); - - return NULL; + return mfree(q); } int dns_query_new( diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index 5687588a7d..87e4abec6e 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -73,10 +73,8 @@ DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const D return dns_resource_key_ref((DnsResourceKey*) key); k = dns_resource_key_new_consume(key->class, key->type, destination); - if (!k) { - free(destination); - return NULL; - } + if (!k) + return mfree(destination); return k; } @@ -513,9 +511,7 @@ DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr) { } free(rr->to_string); - free(rr); - - return NULL; + return mfree(rr); } int dns_resource_record_new_reverse(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *hostname) { diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index ed0c6aa105..8dbc7f623b 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -128,9 +128,7 @@ DnsScope* dns_scope_free(DnsScope *s) { dns_zone_flush(&s->zone); LIST_REMOVE(scopes, s->manager->dns_scopes, s); - free(s); - - return NULL; + return mfree(s); } DnsServer *dns_scope_get_dns_server(DnsScope *s) { @@ -407,6 +405,7 @@ int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *add DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain) { DnsSearchDomain *d; + DnsServer *dns_server; assert(s); assert(domain); @@ -447,6 +446,13 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co if (dns_name_endswith(domain, d->name) > 0) return DNS_SCOPE_YES; + /* If the DNS server has route-only domains, don't send other requests + * to it. This would be a privacy violation, will most probably fail + * anyway, and adds unnecessary load. */ + dns_server = dns_scope_get_dns_server(s); + if (dns_server && dns_server_limited_domains(dns_server)) + return DNS_SCOPE_NO; + switch (s->protocol) { case DNS_PROTOCOL_DNS: diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index 538bc61f81..01a83a76b2 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -26,7 +26,10 @@ typedef struct DnsScope DnsScope; #include "resolved-dns-cache.h" #include "resolved-dns-dnssec.h" #include "resolved-dns-packet.h" +#include "resolved-dns-query.h" +#include "resolved-dns-search-domain.h" #include "resolved-dns-server.h" +#include "resolved-dns-stream.h" #include "resolved-dns-zone.h" #include "resolved-link.h" diff --git a/src/resolve/resolved-dns-search-domain.c b/src/resolve/resolved-dns-search-domain.c index 732471027b..1386e6a17b 100644 --- a/src/resolve/resolved-dns-search-domain.c +++ b/src/resolve/resolved-dns-search-domain.c @@ -104,9 +104,7 @@ DnsSearchDomain* dns_search_domain_unref(DnsSearchDomain *d) { return NULL; free(d->name); - free(d); - - return NULL; + return mfree(d); } void dns_search_domain_unlink(DnsSearchDomain *d) { diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index 9b7b471600..22c64e8491 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -139,8 +139,7 @@ DnsServer* dns_server_unref(DnsServer *s) { return NULL; free(s->server_string); - free(s); - return NULL; + return mfree(s); } void dns_server_unlink(DnsServer *s) { @@ -576,6 +575,26 @@ void dns_server_warn_downgrade(DnsServer *server) { server->warned_downgrade = true; } +bool dns_server_limited_domains(DnsServer *server) { + DnsSearchDomain *domain; + bool domain_restricted = false; + + /* Check if the server has route-only domains without ~., i. e. whether + * it should only be used for particular domains */ + if (!server->link) + return false; + + LIST_FOREACH(domains, domain, server->link->search_domains) + if (domain->route_only) { + domain_restricted = true; + /* ~. means "any domain", thus it is a global server */ + if (dns_name_is_root(DNS_SEARCH_DOMAIN_NAME(domain))) + return false; + } + + return domain_restricted; +} + static void dns_server_hash_func(const void *p, struct siphash *state) { const DnsServer *s = p; diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h index c1732faffd..83e288a202 100644 --- a/src/resolve/resolved-dns-server.h +++ b/src/resolve/resolved-dns-server.h @@ -128,6 +128,8 @@ bool dns_server_dnssec_supported(DnsServer *server); void dns_server_warn_downgrade(DnsServer *server); +bool dns_server_limited_domains(DnsServer *server); + DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr, int ifindex); void dns_server_unlink_all(DnsServer *first); diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c index dd0e0b90e3..878bae47dc 100644 --- a/src/resolve/resolved-dns-stream.c +++ b/src/resolve/resolved-dns-stream.c @@ -343,9 +343,7 @@ DnsStream *dns_stream_unref(DnsStream *s) { dns_packet_unref(s->write_packet); dns_packet_unref(s->read_packet); - free(s); - - return NULL; + return mfree(s); } DEFINE_TRIVIAL_CLEANUP_FUNC(DnsStream*, dns_stream_unref); diff --git a/src/resolve/resolved-dns-stream.h b/src/resolve/resolved-dns-stream.h index e6569678fa..4cdb4f6806 100644 --- a/src/resolve/resolved-dns-stream.h +++ b/src/resolve/resolved-dns-stream.h @@ -25,6 +25,7 @@ typedef struct DnsStream DnsStream; #include "resolved-dns-packet.h" #include "resolved-dns-transaction.h" +#include "resolved-manager.h" /* Streams are used by three subsystems: * diff --git a/src/resolve/resolved-dns-stub.c b/src/resolve/resolved-dns-stub.c index d263cedcd9..e76de6c06a 100644 --- a/src/resolve/resolved-dns-stub.c +++ b/src/resolve/resolved-dns-stub.c @@ -25,6 +25,9 @@ * IP and UDP header sizes */ #define ADVERTISE_DATAGRAM_SIZE_MAX (65536U-14U-20U-8U) +static int manager_dns_stub_udp_fd(Manager *m); +static int manager_dns_stub_tcp_fd(Manager *m); + static int dns_stub_make_reply_packet( uint16_t id, int rcode, @@ -354,66 +357,48 @@ static int on_dns_stub_packet(sd_event_source *s, int fd, uint32_t revents, void return 0; } -int manager_dns_stub_udp_fd(Manager *m) { +static int manager_dns_stub_udp_fd(Manager *m) { static const int one = 1; - union sockaddr_union sa = { .in.sin_family = AF_INET, .in.sin_port = htobe16(53), .in.sin_addr.s_addr = htobe32(INADDR_DNS_STUB), }; - + _cleanup_close_ int fd = -1; int r; if (m->dns_stub_udp_fd >= 0) return m->dns_stub_udp_fd; - m->dns_stub_udp_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->dns_stub_udp_fd < 0) + fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (fd < 0) return -errno; - r = setsockopt(m->dns_stub_udp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one) < 0) + return -errno; - r = setsockopt(m->dns_stub_udp_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } + if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof one) < 0) + return -errno; - r = setsockopt(m->dns_stub_udp_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } + if (setsockopt(fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof one) < 0) + return -errno; /* Make sure no traffic from outside the local host can leak to onto this socket */ - r = setsockopt(m->dns_stub_udp_fd, SOL_SOCKET, SO_BINDTODEVICE, "lo", 3); - if (r < 0) { - r = -errno; - goto fail; - } + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, "lo", 3) < 0) + return -errno; - r = bind(m->dns_stub_udp_fd, &sa.sa, sizeof(sa.in)); - if (r < 0) { - r = -errno; - goto fail; - } + if (bind(fd, &sa.sa, sizeof(sa.in)) < 0) + return -errno; - r = sd_event_add_io(m->event, &m->dns_stub_udp_event_source, m->dns_stub_udp_fd, EPOLLIN, on_dns_stub_packet, m); + r = sd_event_add_io(m->event, &m->dns_stub_udp_event_source, fd, EPOLLIN, on_dns_stub_packet, m); if (r < 0) - goto fail; + return r; (void) sd_event_source_set_description(m->dns_stub_udp_event_source, "dns-stub-udp"); + m->dns_stub_udp_fd = fd; + fd = -1; return m->dns_stub_udp_fd; - -fail: - m->dns_stub_udp_fd = safe_close(m->dns_stub_udp_fd); - return r; } static int on_dns_stub_stream_packet(DnsStream *s) { @@ -461,102 +446,83 @@ static int on_dns_stub_stream(sd_event_source *s, int fd, uint32_t revents, void return 0; } -int manager_dns_stub_tcp_fd(Manager *m) { +static int manager_dns_stub_tcp_fd(Manager *m) { static const int one = 1; - union sockaddr_union sa = { .in.sin_family = AF_INET, .in.sin_addr.s_addr = htobe32(INADDR_DNS_STUB), .in.sin_port = htobe16(53), }; - + _cleanup_close_ int fd = -1; int r; if (m->dns_stub_tcp_fd >= 0) return m->dns_stub_tcp_fd; - m->dns_stub_tcp_fd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->dns_stub_tcp_fd < 0) + fd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (fd < 0) return -errno; - r = setsockopt(m->dns_stub_tcp_fd, IPPROTO_IP, IP_TTL, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } + if (setsockopt(fd, IPPROTO_IP, IP_TTL, &one, sizeof one) < 0) + return -errno; - r = setsockopt(m->dns_stub_tcp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one) < 0) + return -errno; - r = setsockopt(m->dns_stub_tcp_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } + if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof one) < 0) + return -errno; - r = setsockopt(m->dns_stub_tcp_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } + if (setsockopt(fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof one) < 0) + return -errno; /* Make sure no traffic from outside the local host can leak to onto this socket */ - r = setsockopt(m->dns_stub_tcp_fd, SOL_SOCKET, SO_BINDTODEVICE, "lo", 3); - if (r < 0) { - r = -errno; - goto fail; - } + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, "lo", 3) < 0) + return -errno; - r = bind(m->dns_stub_tcp_fd, &sa.sa, sizeof(sa.in)); - if (r < 0) { - r = -errno; - goto fail; - } + if (bind(fd, &sa.sa, sizeof(sa.in)) < 0) + return -errno; - r = listen(m->dns_stub_tcp_fd, SOMAXCONN); - if (r < 0) { - r = -errno; - goto fail; - } + if (listen(fd, SOMAXCONN) < 0) + return -errno; - r = sd_event_add_io(m->event, &m->dns_stub_tcp_event_source, m->dns_stub_tcp_fd, EPOLLIN, on_dns_stub_stream, m); + r = sd_event_add_io(m->event, &m->dns_stub_tcp_event_source, fd, EPOLLIN, on_dns_stub_stream, m); if (r < 0) - goto fail; + return r; (void) sd_event_source_set_description(m->dns_stub_tcp_event_source, "dns-stub-tcp"); + m->dns_stub_tcp_fd = fd; + fd = -1; return m->dns_stub_tcp_fd; - -fail: - m->dns_stub_tcp_fd = safe_close(m->dns_stub_tcp_fd); - return r; } int manager_dns_stub_start(Manager *m) { - int r; + const char *t = "UDP"; + int r = 0; assert(m); - r = manager_dns_stub_udp_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - - r = manager_dns_stub_tcp_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; + if (IN_SET(m->dns_stub_listener_mode, DNS_STUB_LISTENER_YES, DNS_STUB_LISTENER_UDP)) + r = manager_dns_stub_udp_fd(m); - return 0; + if (r >= 0 && + IN_SET(m->dns_stub_listener_mode, DNS_STUB_LISTENER_YES, DNS_STUB_LISTENER_TCP)) { + t = "TCP"; + r = manager_dns_stub_tcp_fd(m); + } -eaddrinuse: - log_warning("Another process is already listening on 127.0.0.53:53. Turning off local DNS stub support."); - manager_dns_stub_stop(m); + if (IN_SET(r, -EADDRINUSE, -EPERM)) { + if (r == -EADDRINUSE) + log_warning_errno(r, + "Another process is already listening on %s socket 127.0.0.53:53.\n" + "Turning off local DNS stub support.", t); + else + log_warning_errno(r, + "Failed to listen on %s socket 127.0.0.53:53: %m.\n" + "Turning off local DNS stub support.", t); + manager_dns_stub_stop(m); + } else if (r < 0) + return log_error_errno(r, "Failed to listen on %s socket 127.0.0.53:53: %m", t); return 0; } diff --git a/src/resolve/resolved-dns-stub.h b/src/resolve/resolved-dns-stub.h index fce4d25ede..12b86f6753 100644 --- a/src/resolve/resolved-dns-stub.h +++ b/src/resolve/resolved-dns-stub.h @@ -24,8 +24,5 @@ /* 127.0.0.53 in native endian */ #define INADDR_DNS_STUB ((in_addr_t) 0x7f000035U) -int manager_dns_stub_udp_fd(Manager *m); -int manager_dns_stub_tcp_fd(Manager *m); - void manager_dns_stub_stop(Manager *m); int manager_dns_stub_start(Manager *m); diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index d455b6b1fa..2fce44ec8b 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -134,8 +134,7 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) { dns_answer_unref(t->validated_keys); dns_resource_key_unref(t->key); - free(t); - return NULL; + return mfree(t); } DEFINE_TRIVIAL_CLEANUP_FUNC(DnsTransaction*, dns_transaction_free); diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h index 96b066845d..5a1df70422 100644 --- a/src/resolve/resolved-dns-transaction.h +++ b/src/resolve/resolved-dns-transaction.h @@ -59,6 +59,8 @@ enum DnsTransactionSource { #include "resolved-dns-packet.h" #include "resolved-dns-question.h" #include "resolved-dns-scope.h" +#include "resolved-dns-server.h" +#include "resolved-dns-stream.h" struct DnsTransaction { DnsScope *scope; diff --git a/src/resolve/resolved-dns-trust-anchor.c b/src/resolve/resolved-dns-trust-anchor.c index 77370e7dd5..9917b9e984 100644 --- a/src/resolve/resolved-dns-trust-anchor.c +++ b/src/resolve/resolved-dns-trust-anchor.c @@ -127,6 +127,9 @@ static int dns_trust_anchor_add_builtin_negative(DnsTrustAnchor *d) { "31.172.in-addr.arpa\0" "168.192.in-addr.arpa\0" + /* The same, but for IPv6. */ + "d.f.ip6.arpa\0" + /* RFC 6762 reserves the .local domain for Multicast * DNS, it hence cannot appear in the root zone. (Note * that we by default do not route .local traffic to diff --git a/src/resolve/resolved-gperf.gperf b/src/resolve/resolved-gperf.gperf index 2fd56bce26..446f85cdf4 100644 --- a/src/resolve/resolved-gperf.gperf +++ b/src/resolve/resolved-gperf.gperf @@ -14,9 +14,10 @@ struct ConfigPerfItem; %struct-type %includes %% -Resolve.DNS, config_parse_dns_servers, DNS_SERVER_SYSTEM, 0 -Resolve.FallbackDNS, config_parse_dns_servers, DNS_SERVER_FALLBACK, 0 -Resolve.Domains, config_parse_search_domains, 0, 0 -Resolve.LLMNR, config_parse_resolve_support, 0, offsetof(Manager, llmnr_support) -Resolve.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Manager, dnssec_mode) -Resolve.Cache, config_parse_bool, 0, offsetof(Manager, enable_cache) +Resolve.DNS, config_parse_dns_servers, DNS_SERVER_SYSTEM, 0 +Resolve.FallbackDNS, config_parse_dns_servers, DNS_SERVER_FALLBACK, 0 +Resolve.Domains, config_parse_search_domains, 0, 0 +Resolve.LLMNR, config_parse_resolve_support, 0, offsetof(Manager, llmnr_support) +Resolve.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Manager, dnssec_mode) +Resolve.Cache, config_parse_bool, 0, offsetof(Manager, enable_cache) +Resolve.DNSStubListener, config_parse_dns_stub_listener_mode, 0, offsetof(Manager, dns_stub_listener_mode) diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index ea4a007139..13e1f91192 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -101,8 +101,7 @@ Link *link_free(Link *l) { free(l->state_file); - free(l); - return NULL; + return mfree(l); } void link_allocate_scopes(Link *l) { @@ -698,8 +697,7 @@ LinkAddress *link_address_free(LinkAddress *a) { dns_resource_record_unref(a->llmnr_address_rr); dns_resource_record_unref(a->llmnr_ptr_rr); - free(a); - return NULL; + return mfree(a); } void link_address_add_rrs(LinkAddress *a, bool force_remove) { diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h index 6a2343f9f7..c9b2a58c34 100644 --- a/src/resolve/resolved-link.h +++ b/src/resolve/resolved-link.h @@ -29,6 +29,7 @@ typedef struct Link Link; typedef struct LinkAddress LinkAddress; #include "resolved-dns-rr.h" +#include "resolved-dns-scope.h" #include "resolved-dns-search-domain.h" #include "resolved-dns-server.h" #include "resolved-manager.h" diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 9bb623c321..6630585d13 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -501,6 +501,7 @@ int manager_new(Manager **ret) { m->mdns_support = RESOLVE_SUPPORT_NO; m->dnssec_mode = DEFAULT_DNSSEC_MODE; m->enable_cache = true; + m->dns_stub_listener_mode = DNS_STUB_LISTENER_UDP; m->read_resolv_conf = true; m->need_builtin_fallbacks = true; m->etc_hosts_last = m->etc_hosts_mtime = USEC_INFINITY; @@ -629,9 +630,7 @@ Manager *manager_free(Manager *m) { dns_trust_anchor_flush(&m->trust_anchor); manager_etc_hosts_flush(m); - free(m); - - return NULL; + return mfree(m); } int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) { diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index deebd8e484..6b2208ed94 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -30,6 +30,7 @@ typedef struct Manager Manager; +#include "resolved-conf.h" #include "resolved-dns-query.h" #include "resolved-dns-search-domain.h" #include "resolved-dns-server.h" @@ -47,6 +48,7 @@ struct Manager { ResolveSupport mdns_support; DnssecMode dnssec_mode; bool enable_cache; + DnsStubListenerMode dns_stub_listener_mode; /* Network */ Hashmap *links; diff --git a/src/resolve/resolved-resolv-conf.c b/src/resolve/resolved-resolv-conf.c index 31b25ca50f..801014caf5 100644 --- a/src/resolve/resolved-resolv-conf.c +++ b/src/resolve/resolved-resolv-conf.c @@ -154,6 +154,16 @@ static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) { return; } + /* Check if the DNS server is limited to particular domains; + * resolv.conf does not have a syntax to express that, so it must not + * appear as a global name server to avoid routing unrelated domains to + * it (which is a privacy violation, will most probably fail anyway, + * and adds unnecessary load) */ + if (dns_server_limited_domains(s)) { + log_debug("DNS server %s has route-only domains, not using as global name server", dns_server_string(s)); + return; + } + if (*count == MAXNS) fputs("# Too many DNS servers configured, the following entries may be ignored.\n", f); (*count)++; diff --git a/src/resolve/resolved.conf.in b/src/resolve/resolved.conf.in index 3bd8389c88..60afa151e3 100644 --- a/src/resolve/resolved.conf.in +++ b/src/resolve/resolved.conf.in @@ -18,3 +18,4 @@ #LLMNR=yes #DNSSEC=@DEFAULT_DNSSEC_MODE@ #Cache=yes +#DNSStubListener=udp diff --git a/src/run/run.c b/src/run/run.c index 58fa49a4d1..81b53fdfab 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -33,6 +33,7 @@ #include "formats-util.h" #include "parse-util.h" #include "path-util.h" +#include "process-util.h" #include "ptyfwd.h" #include "signal-util.h" #include "spawn-polkit-agent.h" @@ -45,6 +46,7 @@ static bool arg_ask_password = true; static bool arg_scope = false; static bool arg_remain_after_exit = false; static bool arg_no_block = false; +static bool arg_wait = false; static const char *arg_unit = NULL; static const char *arg_description = NULL; static const char *arg_slice = NULL; @@ -83,9 +85,7 @@ static void polkit_agent_open_if_enabled(void) { static void help(void) { printf("%s [OPTIONS...] {COMMAND} [ARGS...]\n\n" - "Run the specified command in a transient scope or service or timer\n" - "unit. If a timer option is specified and the unit specified with\n" - "the --unit option exists, the command can be omitted.\n\n" + "Run the specified command in a transient scope or service.\n\n" " -h --help Show this help\n" " --version Show package version\n" " --no-ask-password Do not prompt for password\n" @@ -94,11 +94,12 @@ static void help(void) { " -M --machine=CONTAINER Operate on local container\n" " --scope Run this as scope rather than service\n" " --unit=UNIT Run under the specified unit name\n" - " -p --property=NAME=VALUE Set unit property\n" + " -p --property=NAME=VALUE Set service or scope unit property\n" " --description=TEXT Description for unit\n" " --slice=SLICE Run in the specified slice\n" " --no-block Do not wait until operation finished\n" " -r --remain-after-exit Leave service around until explicitly stopped\n" + " --wait Wait until service stopped again\n" " --send-sighup Send SIGHUP when terminating\n" " --service-type=TYPE Service type\n" " --uid=USER Run as system user\n" @@ -107,15 +108,15 @@ static void help(void) { " -E --setenv=NAME=VALUE Set environment\n" " -t --pty Run service on pseudo tty\n" " -q --quiet Suppress information messages during runtime\n\n" - "Timer options:\n\n" + "Timer options:\n" " --on-active=SECONDS Run after SECONDS delay\n" " --on-boot=SECONDS Run SECONDS after machine was booted up\n" " --on-startup=SECONDS Run SECONDS after systemd activation\n" " --on-unit-active=SECONDS Run SECONDS after the last activation\n" " --on-unit-inactive=SECONDS Run SECONDS after the last deactivation\n" " --on-calendar=SPEC Realtime timer\n" - " --timer-property=NAME=VALUE Set timer unit property\n", - program_invocation_short_name); + " --timer-property=NAME=VALUE Set timer unit property\n" + , program_invocation_short_name); } static bool with_timer(void) { @@ -146,6 +147,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_TIMER_PROPERTY, ARG_NO_BLOCK, ARG_NO_ASK_PASSWORD, + ARG_WAIT, }; static const struct option options[] = { @@ -162,6 +164,7 @@ static int parse_argv(int argc, char *argv[]) { { "host", required_argument, NULL, 'H' }, { "machine", required_argument, NULL, 'M' }, { "service-type", required_argument, NULL, ARG_SERVICE_TYPE }, + { "wait", no_argument, NULL, ARG_WAIT }, { "uid", required_argument, NULL, ARG_EXEC_USER }, { "gid", required_argument, NULL, ARG_EXEC_GROUP }, { "nice", required_argument, NULL, ARG_NICE }, @@ -178,7 +181,7 @@ static int parse_argv(int argc, char *argv[]) { { "on-calendar", required_argument, NULL, ARG_ON_CALENDAR }, { "timer-property", required_argument, NULL, ARG_TIMER_PROPERTY }, { "no-block", no_argument, NULL, ARG_NO_BLOCK }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, + { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, {}, }; @@ -195,13 +198,13 @@ static int parse_argv(int argc, char *argv[]) { help(); return 0; + case ARG_VERSION: + return version(); + case ARG_NO_ASK_PASSWORD: arg_ask_password = false; break; - case ARG_VERSION: - return version(); - case ARG_USER: arg_user = true; break; @@ -257,11 +260,9 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_NICE: - r = safe_atoi(optarg, &arg_nice); - if (r < 0 || arg_nice < PRIO_MIN || arg_nice >= PRIO_MAX) { - log_error("Failed to parse nice value"); - return -EINVAL; - } + r = parse_nice(optarg, &arg_nice); + if (r < 0) + return log_error_errno(r, "Failed to parse nice value: %s", optarg); arg_nice_set = true; break; @@ -361,6 +362,10 @@ static int parse_argv(int argc, char *argv[]) { arg_no_block = true; break; + case ARG_WAIT: + arg_wait = true; + break; + case '?': return -EINVAL; @@ -408,22 +413,36 @@ static int parse_argv(int argc, char *argv[]) { return -EINVAL; } + if (arg_wait) { + if (arg_no_block) { + log_error("--wait may not be combined with --no-block."); + return -EINVAL; + } + + if (with_timer()) { + log_error("--wait may not be combined with timer operations."); + return -EINVAL; + } + + if (arg_scope) { + log_error("--wait may not be combined with --scope."); + return -EINVAL; + } + } + return 1; } static int transient_unit_set_properties(sd_bus_message *m, char **properties) { - char **i; int r; r = sd_bus_message_append(m, "(sv)", "Description", "s", arg_description); if (r < 0) return r; - STRV_FOREACH(i, properties) { - r = bus_append_unit_property_assignment(m, *i); - if (r < 0) - return r; - } + r = bus_append_unit_property_assignment_many(m, properties); + if (r < 0) + return r; return 0; } @@ -473,6 +492,12 @@ static int transient_service_set_properties(sd_bus_message *m, char **argv, cons if (r < 0) return r; + if (arg_wait) { + r = sd_bus_message_append(m, "(sv)", "AddRef", "b", 1); + if (r < 0) + return r; + } + if (arg_remain_after_exit) { r = sd_bus_message_append(m, "(sv)", "RemainAfterExit", "b", arg_remain_after_exit); if (r < 0) @@ -730,9 +755,97 @@ static int make_unit_name(sd_bus *bus, UnitType t, char **ret) { return 0; } +typedef struct RunContext { + sd_bus *bus; + sd_event *event; + PTYForward *forward; + sd_bus_slot *match; + + /* The exit data of the unit */ + char *active_state; + uint64_t inactive_exit_usec; + uint64_t inactive_enter_usec; + char *result; + uint64_t cpu_usage_nsec; + uint32_t exit_code; + uint32_t exit_status; +} RunContext; + +static void run_context_free(RunContext *c) { + assert(c); + + c->forward = pty_forward_free(c->forward); + c->match = sd_bus_slot_unref(c->match); + c->bus = sd_bus_unref(c->bus); + c->event = sd_event_unref(c->event); + + free(c->active_state); + free(c->result); +} + +static void run_context_check_done(RunContext *c) { + bool done = true; + + assert(c); + + if (c->match) + done = done && (c->active_state && STR_IN_SET(c->active_state, "inactive", "failed")); + + if (c->forward) + done = done && pty_forward_is_done(c->forward); + + if (done) + sd_event_exit(c->event, EXIT_SUCCESS); +} + +static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error *error) { + + static const struct bus_properties_map map[] = { + { "ActiveState", "s", NULL, offsetof(RunContext, active_state) }, + { "InactiveExitTimestampMonotonic", "t", NULL, offsetof(RunContext, inactive_exit_usec) }, + { "InactiveEnterTimestampMonotonic", "t", NULL, offsetof(RunContext, inactive_enter_usec) }, + { "Result", "s", NULL, offsetof(RunContext, result) }, + { "ExecMainCode", "i", NULL, offsetof(RunContext, exit_code) }, + { "ExecMainStatus", "i", NULL, offsetof(RunContext, exit_status) }, + { "CPUUsageNSec", "t", NULL, offsetof(RunContext, cpu_usage_nsec) }, + {} + }; + + RunContext *c = userdata; + int r; + + r = bus_map_all_properties(c->bus, + "org.freedesktop.systemd1", + sd_bus_message_get_path(m), + map, + c); + if (r < 0) { + sd_event_exit(c->event, EXIT_FAILURE); + return log_error_errno(r, "Failed to query unit state: %m"); + } + + run_context_check_done(c); + return 0; +} + +static int pty_forward_handler(PTYForward *f, int rcode, void *userdata) { + RunContext *c = userdata; + + assert(f); + + if (rcode < 0) { + sd_event_exit(c->event, EXIT_FAILURE); + return log_error_errno(rcode, "Error on PTY forwarding logic: %m"); + } + + run_context_check_done(c); + return 0; +} + static int start_transient_service( sd_bus *bus, - char **argv) { + char **argv, + int *retval) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -743,6 +856,7 @@ static int start_transient_service( assert(bus); assert(argv); + assert(retval); if (arg_pty) { @@ -866,40 +980,95 @@ static int start_transient_service( return r; } - if (master >= 0) { - _cleanup_(pty_forward_freep) PTYForward *forward = NULL; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - char last_char = 0; + if (!arg_quiet) + log_info("Running as unit: %s", service); + + if (arg_wait || master >= 0) { + _cleanup_(run_context_free) RunContext c = {}; - r = sd_event_default(&event); + c.bus = sd_bus_ref(bus); + + r = sd_event_default(&c.event); if (r < 0) return log_error_errno(r, "Failed to get event loop: %m"); - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGWINCH, SIGTERM, SIGINT, -1) >= 0); + if (master >= 0) { + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGWINCH, SIGTERM, SIGINT, -1) >= 0); + (void) sd_event_add_signal(c.event, NULL, SIGINT, NULL, NULL); + (void) sd_event_add_signal(c.event, NULL, SIGTERM, NULL, NULL); - (void) sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); - (void) sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); + if (!arg_quiet) + log_info("Press ^] three times within 1s to disconnect TTY."); - if (!arg_quiet) - log_info("Running as unit: %s\nPress ^] three times within 1s to disconnect TTY.", service); + r = pty_forward_new(c.event, master, PTY_FORWARD_IGNORE_INITIAL_VHANGUP, &c.forward); + if (r < 0) + return log_error_errno(r, "Failed to create PTY forwarder: %m"); - r = pty_forward_new(event, master, PTY_FORWARD_IGNORE_INITIAL_VHANGUP, &forward); - if (r < 0) - return log_error_errno(r, "Failed to create PTY forwarder: %m"); + pty_forward_set_handler(c.forward, pty_forward_handler, &c); + } + + if (arg_wait) { + _cleanup_free_ char *path = NULL; + const char *mt; + + path = unit_dbus_path_from_name(service); + 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); + if (r < 0) + return log_error_errno(r, "Failed to add properties changed signal."); + + r = sd_bus_attach_event(bus, c.event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop."); + } - r = sd_event_loop(event); + r = sd_event_loop(c.event); if (r < 0) return log_error_errno(r, "Failed to run event loop: %m"); - pty_forward_get_last_char(forward, &last_char); + if (c.forward) { + char last_char = 0; - forward = pty_forward_free(forward); + r = pty_forward_get_last_char(c.forward, &last_char); + if (r >= 0 && !arg_quiet && last_char != '\n') + fputc('\n', stdout); + } - if (!arg_quiet && last_char != '\n') - fputc('\n', stdout); + if (!arg_quiet) { + if (!isempty(c.result)) + log_info("Finished with result: %s", strna(c.result)); - } else if (!arg_quiet) - log_info("Running as unit: %s", service); + if (c.exit_code == CLD_EXITED) + log_info("Main processes terminated with: code=%s/status=%i", sigchld_code_to_string(c.exit_code), c.exit_status); + else if (c.exit_code > 0) + log_info("Main processes terminated with: code=%s/status=%s", sigchld_code_to_string(c.exit_code), signal_to_string(c.exit_status)); + + if (c.inactive_enter_usec > 0 && c.inactive_enter_usec != USEC_INFINITY && + c.inactive_exit_usec > 0 && c.inactive_exit_usec != USEC_INFINITY && + c.inactive_enter_usec > c.inactive_exit_usec) { + char ts[FORMAT_TIMESPAN_MAX]; + log_info("Service runtime: %s", format_timespan(ts, sizeof(ts), c.inactive_enter_usec - c.inactive_exit_usec, USEC_PER_MSEC)); + } + + if (c.cpu_usage_nsec > 0 && c.cpu_usage_nsec != NSEC_INFINITY) { + char ts[FORMAT_TIMESPAN_MAX]; + log_info("CPU time consumed: %s", format_timespan(ts, sizeof(ts), (c.cpu_usage_nsec + NSEC_PER_USEC - 1) / NSEC_PER_USEC, USEC_PER_MSEC)); + } + } + + /* Try to propagate the service's return value */ + if (c.result && STR_IN_SET(c.result, "success", "exit-code") && c.exit_code == CLD_EXITED) + *retval = c.exit_status; + else + *retval = EXIT_FAILURE; + } return 0; } @@ -999,17 +1168,21 @@ static int start_transient_scope( uid_t uid; gid_t gid; - r = get_user_creds(&arg_exec_user, &uid, &gid, &home, &shell); + r = get_user_creds_clean(&arg_exec_user, &uid, &gid, &home, &shell); if (r < 0) return log_error_errno(r, "Failed to resolve user %s: %m", arg_exec_user); - r = strv_extendf(&user_env, "HOME=%s", home); - if (r < 0) - return log_oom(); + if (home) { + r = strv_extendf(&user_env, "HOME=%s", home); + if (r < 0) + return log_oom(); + } - r = strv_extendf(&user_env, "SHELL=%s", shell); - if (r < 0) - return log_oom(); + if (shell) { + r = strv_extendf(&user_env, "SHELL=%s", shell); + if (r < 0) + return log_oom(); + } r = strv_extendf(&user_env, "USER=%s", arg_exec_user); if (r < 0) @@ -1146,7 +1319,7 @@ static int start_transient_timer( if (r < 0) return bus_log_create_error(r); - if (argv[0]) { + if (!strv_isempty(argv)) { r = sd_bus_message_open_container(m, 'r', "sa(sv)"); if (r < 0) return bus_log_create_error(r); @@ -1192,9 +1365,11 @@ static int start_transient_timer( if (r < 0) return r; - log_info("Running timer as unit: %s", timer); - if (argv[0]) - log_info("Will run service as unit: %s", service); + if (!arg_quiet) { + log_info("Running timer as unit: %s", timer); + if (argv[0]) + log_info("Will run service as unit: %s", service); + } return 0; } @@ -1202,7 +1377,7 @@ static int start_transient_timer( int main(int argc, char* argv[]) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_free_ char *description = NULL, *command = NULL; - int r; + int r, retval = EXIT_SUCCESS; log_parse_environment(); log_open(); @@ -1239,7 +1414,12 @@ int main(int argc, char* argv[]) { arg_description = description; } - r = bus_connect_transport_systemd(arg_transport, arg_host, arg_user, &bus); + /* If --wait is used connect via the bus, unconditionally, as ref/unref is not supported via the limited direct + * connection */ + if (arg_wait) + r = bus_connect_transport(arg_transport, arg_host, arg_user, &bus); + else + r = bus_connect_transport_systemd(arg_transport, arg_host, arg_user, &bus); if (r < 0) { log_error_errno(r, "Failed to create bus connection: %m"); goto finish; @@ -1250,12 +1430,12 @@ int main(int argc, char* argv[]) { else if (with_timer()) r = start_transient_timer(bus, argv + optind); else - r = start_transient_service(bus, argv + optind); + r = start_transient_service(bus, argv + optind, &retval); finish: strv_free(arg_environment); strv_free(arg_property); strv_free(arg_timer_property); - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + return r < 0 ? EXIT_FAILURE : retval; } diff --git a/src/shared/ask-password-api.c b/src/shared/ask-password-api.c index 65151b19a6..2597cfc648 100644 --- a/src/shared/ask-password-api.c +++ b/src/shared/ask-password-api.c @@ -484,7 +484,7 @@ int ask_password_agent( (void) mkdir_p_label("/run/systemd/ask-password", 0755); - fd = mkostemp_safe(temp, O_WRONLY|O_CLOEXEC); + fd = mkostemp_safe(temp); if (fd < 0) { r = fd; goto finish; diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index ea020b517b..f639e0e832 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -84,7 +84,7 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen if (isempty(eq)) r = sd_bus_message_append(m, "sv", "CPUQuotaPerSecUSec", "t", USEC_INFINITY); else { - r = parse_percent(eq); + r = parse_percent_unbounded(eq); if (r <= 0) { log_error_errno(r, "CPU quota '%s' invalid.", eq); return -EINVAL; @@ -199,11 +199,13 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen r = sd_bus_message_append(m, "sv", sn, "t", l.rlim_cur); } else if (STR_IN_SET(field, - "CPUAccounting", "MemoryAccounting", "IOAccounting", "BlockIOAccounting", "TasksAccounting", - "SendSIGHUP", "SendSIGKILL", "WakeSystem", "DefaultDependencies", - "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", "RemainAfterExit", - "PrivateTmp", "PrivateDevices", "PrivateNetwork", "NoNewPrivileges", - "SyslogLevelPrefix", "Delegate", "RemainAfterElapse", "MemoryDenyWriteExecute")) { + "CPUAccounting", "MemoryAccounting", "IOAccounting", "BlockIOAccounting", "TasksAccounting", + "SendSIGHUP", "SendSIGKILL", "WakeSystem", "DefaultDependencies", + "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", "RemainAfterExit", + "PrivateTmp", "PrivateDevices", "PrivateNetwork", "PrivateUsers", "NoNewPrivileges", + "SyslogLevelPrefix", "Delegate", "RemainAfterElapse", "MemoryDenyWriteExecute", + "RestrictRealtime", "DynamicUser", "RemoveIPC", "ProtectKernelTunables", + "ProtectKernelModules", "ProtectControlGroups")) { r = parse_boolean(eq); if (r < 0) @@ -211,6 +213,17 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen r = sd_bus_message_append(m, "v", "b", r); + } else if (STR_IN_SET(field, "CPUWeight", "StartupCPUWeight")) { + uint64_t u; + + r = cg_weight_parse(eq, &u); + if (r < 0) { + log_error("Failed to parse %s value %s.", field, eq); + return -EINVAL; + } + + r = sd_bus_message_append(m, "v", "t", u); + } else if (STR_IN_SET(field, "CPUShares", "StartupCPUShares")) { uint64_t u; @@ -291,7 +304,7 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen rwm = ""; } - if (!path_startswith(path, "/dev")) { + if (!is_deviceallow_pattern(path)) { log_error("%s is not a device file in /dev.", path); return -EINVAL; } @@ -365,15 +378,13 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen } } else if (streq(field, "Nice")) { - int32_t i; + int n; - r = safe_atoi32(eq, &i); - if (r < 0) { - log_error("Failed to parse %s value %s.", field, eq); - return -EINVAL; - } + r = parse_nice(eq, &n); + if (r < 0) + return log_error_errno(r, "Failed to parse nice value: %s", eq); - r = sd_bus_message_append(m, "v", "i", i); + r = sd_bus_message_append(m, "v", "i", (int32_t) n); } else if (STR_IN_SET(field, "Environment", "PassEnvironment")) { const char *p; @@ -558,6 +569,21 @@ finish: return 0; } +int bus_append_unit_property_assignment_many(sd_bus_message *m, char **l) { + char **i; + int r; + + assert(m); + + STRV_FOREACH(i, l) { + r = bus_append_unit_property_assignment(m, *i); + if (r < 0) + return r; + } + + return 0; +} + typedef struct BusWaitForJobs { sd_bus *bus; Set *jobs; diff --git a/src/shared/bus-unit-util.h b/src/shared/bus-unit-util.h index c0c172f336..d102ea180e 100644 --- a/src/shared/bus-unit-util.h +++ b/src/shared/bus-unit-util.h @@ -41,6 +41,7 @@ typedef struct UnitInfo { int bus_parse_unit_info(sd_bus_message *message, UnitInfo *u); int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignment); +int bus_append_unit_property_assignment_many(sd_bus_message *m, char **l); typedef struct BusWaitForJobs BusWaitForJobs; diff --git a/src/shared/bus-util.c b/src/shared/bus-util.c index 52410999cf..bb90c89cc2 100644 --- a/src/shared/bus-util.c +++ b/src/shared/bus-util.c @@ -1016,19 +1016,19 @@ static int map_basic(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_ return r; switch (type) { + case SD_BUS_TYPE_STRING: { - const char *s; char **p = userdata; + const char *s; r = sd_bus_message_read_basic(m, type, &s); if (r < 0) - break; + return r; if (isempty(s)) - break; + s = NULL; - r = free_and_strdup(p, s); - break; + return free_and_strdup(p, s); } case SD_BUS_TYPE_ARRAY: { @@ -1037,13 +1037,12 @@ static int map_basic(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_ r = bus_message_read_strv_extend(m, &l); if (r < 0) - break; + return r; strv_free(*p); *p = l; l = NULL; - - break; + return 0; } case SD_BUS_TYPE_BOOLEAN: { @@ -1052,57 +1051,48 @@ static int map_basic(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_ r = sd_bus_message_read_basic(m, type, &b); if (r < 0) - break; + return r; *p = b; - - break; + return 0; } + case SD_BUS_TYPE_INT32: case SD_BUS_TYPE_UINT32: { - uint32_t u; - uint32_t *p = userdata; + uint32_t u, *p = userdata; r = sd_bus_message_read_basic(m, type, &u); if (r < 0) - break; + return r; *p = u; - - break; + return 0; } + case SD_BUS_TYPE_INT64: case SD_BUS_TYPE_UINT64: { - uint64_t t; - uint64_t *p = userdata; + uint64_t t, *p = userdata; r = sd_bus_message_read_basic(m, type, &t); if (r < 0) - break; + return r; *p = t; - - break; + return 0; } case SD_BUS_TYPE_DOUBLE: { - double d; - double *p = userdata; + double d, *p = userdata; r = sd_bus_message_read_basic(m, type, &d); if (r < 0) - break; + return r; *p = d; + return 0; + }} - break; - } - - default: - break; - } - - return r; + return -EOPNOTSUPP; } int bus_message_map_all_properties( @@ -1240,12 +1230,13 @@ int bus_map_all_properties( return bus_message_map_all_properties(m, map, userdata); } -int bus_connect_transport(BusTransport transport, const char *host, bool user, sd_bus **bus) { +int bus_connect_transport(BusTransport transport, const char *host, bool user, sd_bus **ret) { + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; int r; assert(transport >= 0); assert(transport < _BUS_TRANSPORT_MAX); - assert(bus); + assert(ret); assert_return((transport == BUS_TRANSPORT_LOCAL) == !host, -EINVAL); assert_return(transport == BUS_TRANSPORT_LOCAL || !user, -EOPNOTSUPP); @@ -1254,25 +1245,34 @@ int bus_connect_transport(BusTransport transport, const char *host, bool user, s case BUS_TRANSPORT_LOCAL: if (user) - r = sd_bus_default_user(bus); + r = sd_bus_default_user(&bus); else - r = sd_bus_default_system(bus); + r = sd_bus_default_system(&bus); break; case BUS_TRANSPORT_REMOTE: - r = sd_bus_open_system_remote(bus, host); + r = sd_bus_open_system_remote(&bus, host); break; case BUS_TRANSPORT_MACHINE: - r = sd_bus_open_system_machine(bus, host); + r = sd_bus_open_system_machine(&bus, host); break; default: assert_not_reached("Hmm, unknown transport type."); } + if (r < 0) + return r; - return r; + r = sd_bus_set_exit_on_disconnect(bus, true); + if (r < 0) + return r; + + *ret = bus; + bus = NULL; + + return 0; } int bus_connect_transport_systemd(BusTransport transport, const char *host, bool user, sd_bus **bus) { @@ -1324,6 +1324,23 @@ int bus_property_get_bool( return sd_bus_message_append_basic(reply, 'b', &b); } +int bus_property_get_id128( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + sd_id128_t *id = userdata; + + if (sd_id128_is_null(*id)) /* Add an empty array if the ID is zero */ + return sd_bus_message_append(reply, "ay", 0); + else + return sd_bus_message_append_array(reply, 'y', id->bytes, 16); +} + #if __SIZEOF_SIZE_T__ != 8 int bus_property_get_size( sd_bus *bus, diff --git a/src/shared/bus-util.h b/src/shared/bus-util.h index db6b1acba2..934e0b5b77 100644 --- a/src/shared/bus-util.h +++ b/src/shared/bus-util.h @@ -79,6 +79,7 @@ int bus_print_property(const char *name, sd_bus_message *property, bool value, b int bus_print_all_properties(sd_bus *bus, const char *dest, const char *path, char **filter, bool value, bool all); int bus_property_get_bool(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); +int bus_property_get_id128(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); #define bus_property_get_usec ((sd_bus_property_get_t) NULL) #define bus_property_set_usec ((sd_bus_property_set_t) NULL) diff --git a/src/shared/clean-ipc.c b/src/shared/clean-ipc.c index a3ac7aeb82..d5db604f03 100644 --- a/src/shared/clean-ipc.c +++ b/src/shared/clean-ipc.c @@ -41,8 +41,20 @@ #include "macro.h" #include "string-util.h" #include "strv.h" +#include "user-util.h" -static int clean_sysvipc_shm(uid_t delete_uid) { +static bool match_uid_gid(uid_t subject_uid, gid_t subject_gid, uid_t delete_uid, gid_t delete_gid) { + + if (uid_is_valid(delete_uid) && subject_uid == delete_uid) + return true; + + if (gid_is_valid(delete_gid) && subject_gid == delete_gid) + return true; + + return false; +} + +static int clean_sysvipc_shm(uid_t delete_uid, gid_t delete_gid) { _cleanup_fclose_ FILE *f = NULL; char line[LINE_MAX]; bool first = true; @@ -77,7 +89,7 @@ static int clean_sysvipc_shm(uid_t delete_uid) { if (n_attached > 0) continue; - if (uid != delete_uid) + if (!match_uid_gid(uid, gid, delete_uid, delete_gid)) continue; if (shmctl(shmid, IPC_RMID, NULL) < 0) { @@ -89,7 +101,8 @@ static int clean_sysvipc_shm(uid_t delete_uid) { ret = log_warning_errno(errno, "Failed to remove SysV shared memory segment %i: %m", shmid); - } + } else + log_debug("Removed SysV shared memory segment %i.", shmid); } return ret; @@ -98,7 +111,7 @@ fail: return log_warning_errno(errno, "Failed to read /proc/sysvipc/shm: %m"); } -static int clean_sysvipc_sem(uid_t delete_uid) { +static int clean_sysvipc_sem(uid_t delete_uid, gid_t delete_gid) { _cleanup_fclose_ FILE *f = NULL; char line[LINE_MAX]; bool first = true; @@ -128,7 +141,7 @@ static int clean_sysvipc_sem(uid_t delete_uid) { &semid, &uid, &gid, &cuid, &cgid) != 5) continue; - if (uid != delete_uid) + if (!match_uid_gid(uid, gid, delete_uid, delete_gid)) continue; if (semctl(semid, 0, IPC_RMID) < 0) { @@ -140,7 +153,8 @@ static int clean_sysvipc_sem(uid_t delete_uid) { ret = log_warning_errno(errno, "Failed to remove SysV semaphores object %i: %m", semid); - } + } else + log_debug("Removed SysV semaphore %i.", semid); } return ret; @@ -149,7 +163,7 @@ fail: return log_warning_errno(errno, "Failed to read /proc/sysvipc/sem: %m"); } -static int clean_sysvipc_msg(uid_t delete_uid) { +static int clean_sysvipc_msg(uid_t delete_uid, gid_t delete_gid) { _cleanup_fclose_ FILE *f = NULL; char line[LINE_MAX]; bool first = true; @@ -180,7 +194,7 @@ static int clean_sysvipc_msg(uid_t delete_uid) { &msgid, &cpid, &lpid, &uid, &gid, &cuid, &cgid) != 7) continue; - if (uid != delete_uid) + if (!match_uid_gid(uid, gid, delete_uid, delete_gid)) continue; if (msgctl(msgid, IPC_RMID, NULL) < 0) { @@ -192,7 +206,8 @@ static int clean_sysvipc_msg(uid_t delete_uid) { ret = log_warning_errno(errno, "Failed to remove SysV message queue %i: %m", msgid); - } + } else + log_debug("Removed SysV message queue %i.", msgid); } return ret; @@ -201,13 +216,13 @@ fail: return log_warning_errno(errno, "Failed to read /proc/sysvipc/msg: %m"); } -static int clean_posix_shm_internal(DIR *dir, uid_t uid) { +static int clean_posix_shm_internal(DIR *dir, uid_t uid, gid_t gid) { struct dirent *de; int ret = 0, r; assert(dir); - FOREACH_DIRENT(de, dir, goto fail) { + FOREACH_DIRENT_ALL(de, dir, goto fail) { struct stat st; if (STR_IN_SET(de->d_name, "..", ".")) @@ -217,12 +232,11 @@ static int clean_posix_shm_internal(DIR *dir, uid_t uid) { if (errno == ENOENT) continue; - log_warning_errno(errno, "Failed to stat() POSIX shared memory segment %s: %m", de->d_name); - ret = -errno; + ret = log_warning_errno(errno, "Failed to stat() POSIX shared memory segment %s: %m", de->d_name); continue; } - if (st.st_uid != uid) + if (!match_uid_gid(st.st_uid, st.st_gid, uid, gid)) continue; if (S_ISDIR(st.st_mode)) { @@ -230,12 +244,10 @@ static int clean_posix_shm_internal(DIR *dir, uid_t uid) { kid = xopendirat(dirfd(dir), de->d_name, O_NOFOLLOW|O_NOATIME); if (!kid) { - if (errno != ENOENT) { - log_warning_errno(errno, "Failed to enter shared memory directory %s: %m", de->d_name); - ret = -errno; - } + if (errno != ENOENT) + ret = log_warning_errno(errno, "Failed to enter shared memory directory %s: %m", de->d_name); } else { - r = clean_posix_shm_internal(kid, uid); + r = clean_posix_shm_internal(kid, uid, gid); if (r < 0) ret = r; } @@ -245,9 +257,9 @@ static int clean_posix_shm_internal(DIR *dir, uid_t uid) { if (errno == ENOENT) continue; - log_warning_errno(errno, "Failed to remove POSIX shared memory directory %s: %m", de->d_name); - ret = -errno; - } + ret = log_warning_errno(errno, "Failed to remove POSIX shared memory directory %s: %m", de->d_name); + } else + log_debug("Removed POSIX shared memory directory %s", de->d_name); } else { if (unlinkat(dirfd(dir), de->d_name, 0) < 0) { @@ -255,20 +267,19 @@ static int clean_posix_shm_internal(DIR *dir, uid_t uid) { if (errno == ENOENT) continue; - log_warning_errno(errno, "Failed to remove POSIX shared memory segment %s: %m", de->d_name); - ret = -errno; - } + ret = log_warning_errno(errno, "Failed to remove POSIX shared memory segment %s: %m", de->d_name); + } else + log_debug("Removed POSIX shared memory segment %s", de->d_name); } } return ret; fail: - log_warning_errno(errno, "Failed to read /dev/shm: %m"); - return -errno; + return log_warning_errno(errno, "Failed to read /dev/shm: %m"); } -static int clean_posix_shm(uid_t uid) { +static int clean_posix_shm(uid_t uid, gid_t gid) { _cleanup_closedir_ DIR *dir = NULL; dir = opendir("/dev/shm"); @@ -279,10 +290,10 @@ static int clean_posix_shm(uid_t uid) { return log_warning_errno(errno, "Failed to open /dev/shm: %m"); } - return clean_posix_shm_internal(dir, uid); + return clean_posix_shm_internal(dir, uid, gid); } -static int clean_posix_mq(uid_t uid) { +static int clean_posix_mq(uid_t uid, gid_t gid) { _cleanup_closedir_ DIR *dir = NULL; struct dirent *de; int ret = 0; @@ -295,7 +306,7 @@ static int clean_posix_mq(uid_t uid) { return log_warning_errno(errno, "Failed to open /dev/mqueue: %m"); } - FOREACH_DIRENT(de, dir, goto fail) { + FOREACH_DIRENT_ALL(de, dir, goto fail) { struct stat st; char fn[1+strlen(de->d_name)+1]; @@ -312,7 +323,7 @@ static int clean_posix_mq(uid_t uid) { continue; } - if (st.st_uid != uid) + if (!match_uid_gid(st.st_uid, st.st_gid, uid, gid)) continue; fn[0] = '/'; @@ -325,7 +336,8 @@ static int clean_posix_mq(uid_t uid) { ret = log_warning_errno(errno, "Failed to unlink POSIX message queue %s: %m", fn); - } + } else + log_debug("Removed POSIX message queue %s", fn); } return ret; @@ -334,32 +346,44 @@ fail: return log_warning_errno(errno, "Failed to read /dev/mqueue: %m"); } -int clean_ipc(uid_t uid) { +int clean_ipc(uid_t uid, gid_t gid) { int ret = 0, r; - /* Refuse to clean IPC of the root and system users */ - if (uid <= SYSTEM_UID_MAX) + /* Anything to do? */ + if (!uid_is_valid(uid) && !gid_is_valid(gid)) + return 0; + + /* Refuse to clean IPC of the root user */ + if (uid == 0 && gid == 0) return 0; - r = clean_sysvipc_shm(uid); + r = clean_sysvipc_shm(uid, gid); if (r < 0) ret = r; - r = clean_sysvipc_sem(uid); + r = clean_sysvipc_sem(uid, gid); if (r < 0) ret = r; - r = clean_sysvipc_msg(uid); + r = clean_sysvipc_msg(uid, gid); if (r < 0) ret = r; - r = clean_posix_shm(uid); + r = clean_posix_shm(uid, gid); if (r < 0) ret = r; - r = clean_posix_mq(uid); + r = clean_posix_mq(uid, gid); if (r < 0) ret = r; return ret; } + +int clean_ipc_by_uid(uid_t uid) { + return clean_ipc(uid, GID_INVALID); +} + +int clean_ipc_by_gid(gid_t gid) { + return clean_ipc(UID_INVALID, gid); +} diff --git a/src/shared/clean-ipc.h b/src/shared/clean-ipc.h index 44a83afcf7..6ca57f44fd 100644 --- a/src/shared/clean-ipc.h +++ b/src/shared/clean-ipc.h @@ -21,4 +21,6 @@ #include <sys/types.h> -int clean_ipc(uid_t uid); +int clean_ipc(uid_t uid, gid_t gid); +int clean_ipc_by_uid(uid_t uid); +int clean_ipc_by_gid(gid_t gid); diff --git a/src/shared/condition.c b/src/shared/condition.c index 6bb42c0692..8bd6a51a99 100644 --- a/src/shared/condition.c +++ b/src/shared/condition.c @@ -37,6 +37,7 @@ #include "condition.h" #include "extract-word.h" #include "fd-util.h" +#include "fileio.h" #include "glob-util.h" #include "hostname-util.h" #include "ima-util.h" @@ -145,25 +146,24 @@ static int condition_test_virtualization(Condition *c) { assert(c->parameter); assert(c->type == CONDITION_VIRTUALIZATION); + if (streq(c->parameter, "private-users")) + return running_in_userns(); + v = detect_virtualization(); if (v < 0) return v; /* First, compare with yes/no */ b = parse_boolean(c->parameter); - - if (v > 0 && b > 0) - return true; - - if (v == 0 && b == 0) - return true; + if (b >= 0) + return b == !!v; /* Then, compare categorization */ - if (VIRTUALIZATION_IS_VM(v) && streq(c->parameter, "vm")) - return true; + if (streq(c->parameter, "vm")) + return VIRTUALIZATION_IS_VM(v); - if (VIRTUALIZATION_IS_CONTAINER(v) && streq(c->parameter, "container")) - return true; + if (streq(c->parameter, "container")) + return VIRTUALIZATION_IS_CONTAINER(v); /* Finally compare id */ return v != VIRTUALIZATION_NONE && streq(c->parameter, virtualization_to_string(v)); @@ -309,8 +309,44 @@ static int condition_test_needs_update(Condition *c) { if (lstat("/usr/", &usr) < 0) return true; - return usr.st_mtim.tv_sec > other.st_mtim.tv_sec || - (usr.st_mtim.tv_sec == other.st_mtim.tv_sec && usr.st_mtim.tv_nsec > other.st_mtim.tv_nsec); + /* + * First, compare seconds as they are always accurate... + */ + if (usr.st_mtim.tv_sec != other.st_mtim.tv_sec) + return usr.st_mtim.tv_sec > other.st_mtim.tv_sec; + + /* + * ...then compare nanoseconds. + * + * A false positive is only possible when /usr's nanoseconds > 0 + * (otherwise /usr cannot be strictly newer than the target file) + * AND the target file's nanoseconds == 0 + * (otherwise the filesystem supports nsec timestamps, see stat(2)). + */ + if (usr.st_mtim.tv_nsec > 0 && other.st_mtim.tv_nsec == 0) { + _cleanup_free_ char *timestamp_str = NULL; + uint64_t timestamp; + int r; + + r = parse_env_file(p, NULL, "TIMESTAMP_NSEC", ×tamp_str, NULL); + if (r < 0) { + log_error_errno(r, "Failed to parse timestamp file '%s', using mtime: %m", p); + return true; + } else if (r == 0) { + log_debug("No data in timestamp file '%s', using mtime", p); + return true; + } + + r = safe_atou64(timestamp_str, ×tamp); + if (r < 0) { + log_error_errno(r, "Failed to parse timestamp value '%s' in file '%s', using mtime: %m", timestamp_str, p); + return true; + } + + timespec_store(&other.st_mtim, timestamp); + } + + return usr.st_mtim.tv_nsec > other.st_mtim.tv_nsec; } static int condition_test_first_boot(Condition *c) { diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c index 7cf222e4d2..2ec0155b71 100644 --- a/src/shared/conf-parser.c +++ b/src/shared/conf-parser.c @@ -396,22 +396,18 @@ int config_parse(const char *unit, return 0; } -/* Parse each config file in the specified directories. */ -int config_parse_many(const char *conf_file, - const char *conf_file_dirs, - const char *sections, - ConfigItemLookup lookup, - const void *table, - bool relaxed, - void *userdata) { - _cleanup_strv_free_ char **files = NULL; +static int config_parse_many_files( + const char *conf_file, + char **files, + const char *sections, + ConfigItemLookup lookup, + const void *table, + bool relaxed, + void *userdata) { + char **fn; int r; - r = conf_files_list_nulstr(&files, ".conf", NULL, conf_file_dirs); - if (r < 0) - return r; - if (conf_file) { r = config_parse(NULL, conf_file, NULL, sections, lookup, table, relaxed, false, true, userdata); if (r < 0) @@ -427,6 +423,56 @@ int config_parse_many(const char *conf_file, return 0; } +/* Parse each config file in the directories specified as nulstr. */ +int config_parse_many_nulstr( + const char *conf_file, + const char *conf_file_dirs, + const char *sections, + ConfigItemLookup lookup, + const void *table, + bool relaxed, + void *userdata) { + + _cleanup_strv_free_ char **files = NULL; + int r; + + r = conf_files_list_nulstr(&files, ".conf", NULL, conf_file_dirs); + if (r < 0) + return r; + + return config_parse_many_files(conf_file, files, + sections, lookup, table, relaxed, userdata); +} + +/* Parse each config file in the directories specified as strv. */ +int config_parse_many( + const char *conf_file, + const char* const* conf_file_dirs, + const char *dropin_dirname, + const char *sections, + ConfigItemLookup lookup, + const void *table, + bool relaxed, + void *userdata) { + + _cleanup_strv_free_ char **dropin_dirs = NULL; + _cleanup_strv_free_ char **files = NULL; + const char *suffix; + int r; + + suffix = strjoina("/", dropin_dirname); + r = strv_extend_strv_concat(&dropin_dirs, (char**) conf_file_dirs, suffix); + if (r < 0) + return r; + + r = conf_files_list_strv(&files, ".conf", NULL, (const char* const*) dropin_dirs); + if (r < 0) + return r; + + return config_parse_many_files(conf_file, files, + sections, lookup, table, relaxed, userdata); +} + #define DEFINE_PARSER(type, vartype, conv_func) \ int config_parse_##type( \ const char *unit, \ @@ -460,6 +506,7 @@ int config_parse_many(const char *conf_file, DEFINE_PARSER(int, int, safe_atoi); DEFINE_PARSER(long, long, safe_atoli); +DEFINE_PARSER(uint16, uint16_t, safe_atou16); DEFINE_PARSER(uint32, uint32_t, safe_atou32); DEFINE_PARSER(uint64, uint64_t, safe_atou64); DEFINE_PARSER(unsigned, unsigned, safe_atou); diff --git a/src/shared/conf-parser.h b/src/shared/conf-parser.h index f6964e3fd4..26ff3df16f 100644 --- a/src/shared/conf-parser.h +++ b/src/shared/conf-parser.h @@ -84,29 +84,42 @@ int config_item_table_lookup(const void *table, const char *section, const char * ConfigPerfItem tables */ int config_item_perf_lookup(const void *table, const char *section, const char *lvalue, ConfigParserCallback *func, int *ltype, void **data, void *userdata); -int config_parse(const char *unit, - const char *filename, - FILE *f, - const char *sections, /* nulstr */ - ConfigItemLookup lookup, - const void *table, - bool relaxed, - bool allow_include, - bool warn, - void *userdata); - -int config_parse_many(const char *conf_file, /* possibly NULL */ - const char *conf_file_dirs, /* nulstr */ - const char *sections, /* nulstr */ - ConfigItemLookup lookup, - const void *table, - bool relaxed, - void *userdata); +int config_parse( + const char *unit, + const char *filename, + FILE *f, + const char *sections, /* nulstr */ + ConfigItemLookup lookup, + const void *table, + bool relaxed, + bool allow_include, + bool warn, + void *userdata); + +int config_parse_many_nulstr( + const char *conf_file, /* possibly NULL */ + const char *conf_file_dirs, /* nulstr */ + const char *sections, /* nulstr */ + ConfigItemLookup lookup, + const void *table, + bool relaxed, + void *userdata); + +int config_parse_many( + const char *conf_file, /* possibly NULL */ + const char* const* conf_file_dirs, + const char *dropin_dirname, + const char *sections, /* nulstr */ + ConfigItemLookup lookup, + const void *table, + bool relaxed, + void *userdata); /* Generic parsers */ int config_parse_int(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); int config_parse_unsigned(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); int config_parse_long(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); +int config_parse_uint16(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); int config_parse_uint32(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); int config_parse_uint64(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); int config_parse_double(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); diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c index 835557c6b2..892f0aadf5 100644 --- a/src/shared/dns-domain.c +++ b/src/shared/dns-domain.c @@ -131,6 +131,10 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) { if (r == 0 && *n) return -EINVAL; + /* More than one trailing dot? */ + if (*n == '.') + return -EINVAL; + if (sz >= 1 && d) *d = 0; diff --git a/src/shared/gcrypt-util.h b/src/shared/gcrypt-util.h index cf33b3c59c..1da12a32be 100644 --- a/src/shared/gcrypt-util.h +++ b/src/shared/gcrypt-util.h @@ -37,3 +37,11 @@ static inline int string_hashsum_sha224(const char *s, size_t len, char **out) { return -EOPNOTSUPP; #endif } + +static inline int string_hashsum_sha256(const char *s, size_t len, char **out) { +#ifdef HAVE_GCRYPT + return string_hashsum(s, len, GCRY_MD_SHA256, out); +#else + return -EOPNOTSUPP; +#endif +} diff --git a/src/shared/install-printf.c b/src/shared/install-printf.c index 88143361da..cbdf66827f 100644 --- a/src/shared/install-printf.c +++ b/src/shared/install-printf.c @@ -27,19 +27,54 @@ #include "install.h" #include "macro.h" #include "specifier.h" +#include "string-util.h" #include "unit-name.h" #include "user-util.h" static int specifier_prefix_and_instance(char specifier, void *data, void *userdata, char **ret) { - UnitFileInstallInfo *i = userdata; + const UnitFileInstallInfo *i = userdata; + _cleanup_free_ char *prefix = NULL; + int r; assert(i); - return unit_name_to_prefix_and_instance(i->name, ret); + r = unit_name_to_prefix_and_instance(i->name, &prefix); + if (r < 0) + return r; + + if (endswith(prefix, "@") && i->default_instance) { + char *ans; + + ans = strjoin(prefix, i->default_instance, NULL); + if (!ans) + return -ENOMEM; + *ret = ans; + } else { + *ret = prefix; + prefix = NULL; + } + + return 0; +} + +static int specifier_name(char specifier, void *data, void *userdata, char **ret) { + const UnitFileInstallInfo *i = userdata; + char *ans; + + assert(i); + + if (unit_name_is_valid(i->name, UNIT_NAME_TEMPLATE) && i->default_instance) + return unit_name_replace_instance(i->name, i->default_instance, ret); + + ans = strdup(i->name); + if (!ans) + return -ENOMEM; + *ret = ans; + return 0; } static int specifier_prefix(char specifier, void *data, void *userdata, char **ret) { - UnitFileInstallInfo *i = userdata; + const UnitFileInstallInfo *i = userdata; assert(i); @@ -47,7 +82,7 @@ static int specifier_prefix(char specifier, void *data, void *userdata, char **r } static int specifier_instance(char specifier, void *data, void *userdata, char **ret) { - UnitFileInstallInfo *i = userdata; + const UnitFileInstallInfo *i = userdata; char *instance; int r; @@ -57,8 +92,8 @@ static int specifier_instance(char specifier, void *data, void *userdata, char * if (r < 0) return r; - if (!instance) { - instance = strdup(""); + if (isempty(instance)) { + instance = strdup(i->default_instance ?: ""); if (!instance) return -ENOMEM; } @@ -73,9 +108,13 @@ static int specifier_user_name(char specifier, void *data, void *userdata, char /* If we are UID 0 (root), this will not result in NSS, * otherwise it might. This is good, as we want to be able to * run this in PID 1, where our user ID is 0, but where NSS - * lookups are not allowed. */ + * lookups are not allowed. + + * We don't user getusername_malloc() here, because we don't want to look + * at $USER, to remain consistent with specifer_user_id() below. + */ - t = getusername_malloc(); + t = uid_to_name(getuid()); if (!t) return -ENOMEM; @@ -110,7 +149,7 @@ int install_full_printf(UnitFileInstallInfo *i, const char *format, char **ret) */ const Specifier table[] = { - { 'n', specifier_string, i->name }, + { 'n', specifier_name, NULL }, { 'N', specifier_prefix_and_instance, NULL }, { 'p', specifier_prefix, NULL }, { 'i', specifier_instance, NULL }, diff --git a/src/shared/install.c b/src/shared/install.c index 7b49e1ece9..96fba6e25b 100644 --- a/src/shared/install.c +++ b/src/shared/install.c @@ -214,8 +214,8 @@ static int path_is_config(const LookupPaths *p, const char *path) { assert(p); assert(path); - /* Note that we do *not* have generic checks for /etc or /run in place, since with them we couldn't discern - * configuration from transient or generated units */ + /* Note that we do *not* have generic checks for /etc or /run in place, since with + * them we couldn't discern configuration from transient or generated units */ parent = dirname_malloc(path); if (!parent) @@ -232,8 +232,8 @@ static int path_is_runtime(const LookupPaths *p, const char *path) { assert(p); assert(path); - /* Everything in /run is considered runtime. On top of that we also add explicit checks for the various runtime - * directories, as safety net. */ + /* Everything in /run is considered runtime. On top of that we also add + * explicit checks for the various runtime directories, as safety net. */ rpath = skip_root(p, path); if (rpath && path_startswith(rpath, "/run")) @@ -393,19 +393,43 @@ void unit_file_dump_changes(int r, const char *verb, const UnitFileChange *chang log_error_errno(r, "Failed to %s: %m.", verb); } +/** + * Checks if two paths or symlinks from wd are the same, when root is the root of the filesystem. + * wc should be the full path in the host file system. + */ +static bool chroot_symlinks_same(const char *root, const char *wd, const char *a, const char *b) { + assert(path_is_absolute(wd)); + + /* This will give incorrect results if the paths are relative and go outside + * of the chroot. False negatives are possible. */ + + if (!root) + root = "/"; + + a = strjoina(path_is_absolute(a) ? root : wd, "/", a); + b = strjoina(path_is_absolute(b) ? root : wd, "/", b); + return path_equal_or_files_same(a, b); +} + static int create_symlink( + const LookupPaths *paths, const char *old_path, const char *new_path, bool force, UnitFileChange **changes, unsigned *n_changes) { - _cleanup_free_ char *dest = NULL; + _cleanup_free_ char *dest = NULL, *dirname = NULL; + const char *rp; int r; assert(old_path); assert(new_path); + rp = skip_root(paths, old_path); + if (rp) + old_path = rp; + /* Actually create a symlink, and remember that we did. Is * smart enough to check if there's already a valid symlink in * place. @@ -436,7 +460,11 @@ static int create_symlink( return r; } - if (path_equal(dest, old_path)) + dirname = dirname_malloc(new_path); + if (!dirname) + return -ENOMEM; + + if (chroot_symlinks_same(paths->root_dir, dirname, dest, old_path)) return 1; if (!force) { @@ -490,6 +518,7 @@ static int remove_marked_symlinks_fd( const char *path, const char *config_path, const LookupPaths *lp, + bool dry_run, bool *restart, UnitFileChange **changes, unsigned *n_changes) { @@ -538,7 +567,7 @@ static int remove_marked_symlinks_fd( } /* This will close nfd, regardless whether it succeeds or not */ - q = remove_marked_symlinks_fd(remove_symlinks_to, nfd, p, config_path, lp, restart, changes, n_changes); + q = remove_marked_symlinks_fd(remove_symlinks_to, nfd, p, config_path, lp, dry_run, restart, changes, n_changes); if (q < 0 && r == 0) r = q; @@ -575,14 +604,16 @@ static int remove_marked_symlinks_fd( if (!found) continue; - if (unlinkat(fd, de->d_name, 0) < 0 && errno != ENOENT) { - if (r == 0) - r = -errno; - unit_file_changes_add(changes, n_changes, -errno, p, NULL); - continue; - } + if (!dry_run) { + if (unlinkat(fd, de->d_name, 0) < 0 && errno != ENOENT) { + if (r == 0) + r = -errno; + unit_file_changes_add(changes, n_changes, -errno, p, NULL); + continue; + } - (void) rmdir_parents(p, config_path); + (void) rmdir_parents(p, config_path); + } unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, p, NULL); @@ -593,7 +624,7 @@ static int remove_marked_symlinks_fd( q = mark_symlink_for_removal(&remove_symlinks_to, rp ?: p); if (q < 0) return q; - if (q > 0) + if (q > 0 && !dry_run) *restart = true; } } @@ -605,6 +636,7 @@ static int remove_marked_symlinks( Set *remove_symlinks_to, const char *config_path, const LookupPaths *lp, + bool dry_run, UnitFileChange **changes, unsigned *n_changes) { @@ -618,9 +650,9 @@ static int remove_marked_symlinks( if (set_size(remove_symlinks_to) <= 0) return 0; - fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC); if (fd < 0) - return -errno; + return errno == ENOENT ? 0 : -errno; do { int q, cfd; @@ -631,7 +663,7 @@ static int remove_marked_symlinks( return -errno; /* This takes possession of cfd and closes it */ - q = remove_marked_symlinks_fd(remove_symlinks_to, cfd, config_path, config_path, lp, &restart, changes, n_changes); + q = remove_marked_symlinks_fd(remove_symlinks_to, cfd, config_path, config_path, lp, dry_run, &restart, changes, n_changes); if (r == 0) r = q; } while (restart); @@ -777,7 +809,7 @@ static int find_symlinks( assert(config_path); assert(same_name_link); - fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC); if (fd < 0) { if (IN_SET(errno, ENOENT, ENOTDIR, EACCES)) return 0; @@ -887,8 +919,8 @@ static int install_info_may_process( assert(i); assert(paths); - /* Checks whether the loaded unit file is one we should process, or is masked, transient or generated and thus - * not subject to enable/disable operations. */ + /* Checks whether the loaded unit file is one we should process, or is masked, + * transient or generated and thus not subject to enable/disable operations. */ if (i->type == UNIT_FILE_TYPE_MASKED) { unit_file_changes_add(changes, n_changes, -ERFKILL, i->path, NULL); @@ -903,10 +935,17 @@ static int install_info_may_process( return 0; } +/** + * Adds a new UnitFileInstallInfo entry under name in the InstallContext.will_process + * hashmap, or retrieves the existing one if already present. + * + * Returns negative on error, 0 if the unit was already known, 1 otherwise. + */ static int install_info_add( InstallContext *c, const char *name, const char *path, + bool auxiliary, UnitFileInstallInfo **ret) { UnitFileInstallInfo *i = NULL; @@ -923,6 +962,8 @@ static int install_info_add( i = install_info_find(c, name); if (i) { + i->auxiliary = i->auxiliary && auxiliary; + if (ret) *ret = i; return 0; @@ -936,6 +977,7 @@ static int install_info_add( if (!i) return -ENOMEM; i->type = _UNIT_FILE_TYPE_INVALID; + i->auxiliary = auxiliary; i->name = strdup(name); if (!i->name) { @@ -958,7 +1000,7 @@ static int install_info_add( if (ret) *ret = i; - return 0; + return 1; fail: install_info_free(i); @@ -988,7 +1030,7 @@ static int config_parse_alias( type = unit_name_to_type(name); if (!unit_type_may_alias(type)) return log_syntax(unit, LOG_WARNING, filename, line, 0, - "Aliases are not allowed for %s units, ignoring.", + "Alias= is not allowed for %s units, ignoring.", unit_type_to_string(type)); return config_parse_strv(unit, filename, line, section, section_line, @@ -1007,7 +1049,7 @@ static int config_parse_also( void *data, void *userdata) { - UnitFileInstallInfo *i = userdata; + UnitFileInstallInfo *info = userdata, *alsoinfo = NULL; InstallContext *c = data; int r; @@ -1016,7 +1058,7 @@ static int config_parse_also( assert(rvalue); for (;;) { - _cleanup_free_ char *word = NULL; + _cleanup_free_ char *word = NULL, *printed = NULL; r = extract_first_word(&rvalue, &word, NULL, 0); if (r < 0) @@ -1024,15 +1066,22 @@ static int config_parse_also( if (r == 0) break; - r = install_info_add(c, word, NULL, NULL); + r = install_full_printf(info, word, &printed); if (r < 0) return r; - r = strv_push(&i->also, word); + if (!unit_name_is_valid(printed, UNIT_NAME_ANY)) + return -EINVAL; + + r = install_info_add(c, printed, NULL, true, &alsoinfo); if (r < 0) return r; - word = NULL; + r = strv_push(&info->also, printed); + if (r < 0) + return r; + + printed = NULL; } return 0; @@ -1052,7 +1101,7 @@ static int config_parse_default_instance( UnitFileInstallInfo *i = data; const char *name; - char *printed; + _cleanup_free_ char *printed = NULL; int r; assert(filename); @@ -1066,21 +1115,16 @@ static int config_parse_default_instance( return 0; if (!unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) return log_syntax(unit, LOG_WARNING, filename, line, 0, - "DefaultInstance only makes sense for template units, ignoring."); + "DefaultInstance= only makes sense for template units, ignoring."); r = install_full_printf(i, rvalue, &printed); if (r < 0) return r; - if (!unit_instance_is_valid(printed)) { - free(printed); + if (!unit_instance_is_valid(printed)) return -EINVAL; - } - - free(i->default_instance); - i->default_instance = printed; - return 0; + return free_and_replace(i->default_instance, printed); } static int unit_file_load( @@ -1105,7 +1149,6 @@ static int unit_file_load( struct stat st; int r; - assert(c); assert(info); assert(path); @@ -1134,6 +1177,9 @@ static int unit_file_load( return 0; } + /* c is only needed if we actually load the file */ + assert(c); + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); if (fd < 0) return -errno; @@ -1158,7 +1204,7 @@ static int unit_file_load( config_item_table_lookup, items, true, true, false, info); if (r < 0) - return r; + return log_debug_errno(r, "Failed to parse %s: %m", info->name); info->type = UNIT_FILE_TYPE_REGULAR; @@ -1246,7 +1292,6 @@ static int unit_file_search( char **p; int r; - assert(c); assert(info); assert(paths); @@ -1325,18 +1370,15 @@ static int install_info_follow( if (!streq(basename(i->symlink_target), i->name)) return -EXDEV; - free(i->path); - i->path = i->symlink_target; - i->symlink_target = NULL; + free_and_replace(i->path, i->symlink_target); i->type = _UNIT_FILE_TYPE_INVALID; return unit_file_load_or_readlink(c, i, i->path, root_dir, flags); } /** - * Search for the unit file. If the unit name is a symlink, - * follow the symlink to the target, maybe more than once. - * Propagate the instance name if present. + * Search for the unit file. If the unit name is a symlink, follow the symlink to the + * target, maybe more than once. Propagate the instance name if present. */ static int install_info_traverse( UnitFileScope scope, @@ -1400,7 +1442,7 @@ static int install_info_traverse( bn = buffer; } - r = install_info_add(c, bn, NULL, &i); + r = install_info_add(c, bn, NULL, false, &i); if (r < 0) return r; @@ -1421,6 +1463,10 @@ static int install_info_traverse( return 0; } +/** + * Call install_info_add() with name_or_path as the path (if name_or_path starts with "/") + * or the name (otherwise). root_dir is prepended to the path. + */ static int install_info_add_auto( InstallContext *c, const LookupPaths *paths, @@ -1435,9 +1481,9 @@ static int install_info_add_auto( pp = prefix_roota(paths->root_dir, name_or_path); - return install_info_add(c, NULL, pp, ret); + return install_info_add(c, NULL, pp, false, ret); } else - return install_info_add(c, name_or_path, NULL, ret); + return install_info_add(c, name_or_path, NULL, false, ret); } static int install_info_discover( @@ -1446,7 +1492,9 @@ static int install_info_discover( const LookupPaths *paths, const char *name, SearchFlags flags, - UnitFileInstallInfo **ret) { + UnitFileInstallInfo **ret, + UnitFileChange **changes, + unsigned *n_changes) { UnitFileInstallInfo *i; int r; @@ -1456,10 +1504,12 @@ static int install_info_discover( assert(name); r = install_info_add_auto(c, paths, name, &i); - if (r < 0) - return r; + if (r >= 0) + r = install_info_traverse(scope, c, paths, i, flags, ret); - return install_info_traverse(scope, c, paths, i, flags, ret); + if (r < 0) + unit_file_changes_add(changes, n_changes, r, name, NULL); + return r; } static int install_info_symlink_alias( @@ -1479,7 +1529,6 @@ static int install_info_symlink_alias( STRV_FOREACH(s, i->aliases) { _cleanup_free_ char *alias_path = NULL, *dst = NULL; - const char *rp; q = install_full_printf(i, *s, &dst); if (q < 0) @@ -1489,9 +1538,7 @@ static int install_info_symlink_alias( if (!alias_path) return -ENOMEM; - rp = skip_root(paths, i->path); - - q = create_symlink(rp ?: i->path, alias_path, force, changes, n_changes); + q = create_symlink(paths, i->path, alias_path, force, changes, n_changes); if (r == 0) r = q; } @@ -1517,7 +1564,14 @@ static int install_info_symlink_wants( assert(paths); assert(config_path); + if (strv_isempty(list)) + return 0; + if (unit_name_is_valid(i->name, UNIT_NAME_TEMPLATE)) { + UnitFileInstallInfo instance = { + .type = _UNIT_FILE_TYPE_INVALID, + }; + _cleanup_free_ char *path = NULL; /* Don't install any symlink if there's no default * instance configured */ @@ -1529,13 +1583,25 @@ static int install_info_symlink_wants( if (r < 0) return r; + instance.name = buf; + r = unit_file_search(NULL, &instance, paths, SEARCH_FOLLOW_CONFIG_SYMLINKS); + if (r < 0) + return r; + + path = instance.path; + instance.path = NULL; + + if (instance.type == UNIT_FILE_TYPE_MASKED) { + unit_file_changes_add(changes, n_changes, -ERFKILL, path, NULL); + return -ERFKILL; + } + n = buf; } else n = i->name; STRV_FOREACH(s, list) { _cleanup_free_ char *path = NULL, *dst = NULL; - const char *rp; q = install_full_printf(i, *s, &dst); if (q < 0) @@ -1550,9 +1616,7 @@ static int install_info_symlink_wants( if (!path) return -ENOMEM; - rp = skip_root(paths, i->path); - - q = create_symlink(rp ?: i->path, path, true, changes, n_changes); + q = create_symlink(paths, i->path, path, true, changes, n_changes); if (r == 0) r = q; } @@ -1569,7 +1633,6 @@ static int install_info_symlink_link( unsigned *n_changes) { _cleanup_free_ char *path = NULL; - const char *rp; int r; assert(i); @@ -1587,9 +1650,7 @@ static int install_info_symlink_link( if (!path) return -ENOMEM; - rp = skip_root(paths, i->path); - - return create_symlink(rp ?: i->path, path, force, changes, n_changes); + return create_symlink(paths, i->path, path, force, changes, n_changes); } static int install_info_apply( @@ -1660,8 +1721,21 @@ static int install_context_apply( return q; r = install_info_traverse(scope, c, paths, i, flags, NULL); - if (r < 0) + if (r < 0) { + unit_file_changes_add(changes, n_changes, r, i->name, NULL); return r; + } + + /* We can attempt to process a masked unit when a different unit + * that we were processing specifies it in Also=. */ + if (i->type == UNIT_FILE_TYPE_MASKED) { + unit_file_changes_add(changes, n_changes, UNIT_FILE_IS_MASKED, i->path, NULL); + if (r >= 0) + /* Assume that something *could* have been enabled here, + * avoid "empty [Install] section" warning. */ + r += 1; + continue; + } if (i->type != UNIT_FILE_TYPE_REGULAR) continue; @@ -1708,10 +1782,15 @@ static int install_context_mark_for_removal( return r; r = install_info_traverse(scope, c, paths, i, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, NULL); - if (r == -ENOLINK) - return 0; - else if (r < 0) - return r; + if (r == -ENOLINK) { + log_debug_errno(r, "Name %s leads to a dangling symlink, ignoring.", i->name); + continue; + } else if (r == -ENOENT && i->auxiliary) { + /* some unit specified in Also= or similar is missing */ + log_debug_errno(r, "Auxiliary unit %s not found, ignoring.", i->name); + continue; + } else if (r < 0) + return log_debug_errno(r, "Failed to find unit %s: %m", i->name); if (i->type != UNIT_FILE_TYPE_REGULAR) { log_debug("Unit %s has type %s, ignoring.", @@ -1730,10 +1809,9 @@ static int install_context_mark_for_removal( int unit_file_mask( UnitFileScope scope, - bool runtime, + UnitFileFlags flags, const char *root_dir, char **files, - bool force, UnitFileChange **changes, unsigned *n_changes) { @@ -1749,7 +1827,7 @@ int unit_file_mask( if (r < 0) return r; - config_path = runtime ? paths.runtime_config : paths.persistent_config; + config_path = (flags & UNIT_FILE_RUNTIME) ? paths.runtime_config : paths.persistent_config; STRV_FOREACH(i, files) { _cleanup_free_ char *path = NULL; @@ -1765,7 +1843,7 @@ int unit_file_mask( if (!path) return -ENOMEM; - q = create_symlink("/dev/null", path, force, changes, n_changes); + q = create_symlink(&paths, "/dev/null", path, !!(flags & UNIT_FILE_FORCE), changes, n_changes); if (q < 0 && r >= 0) r = q; } @@ -1775,7 +1853,7 @@ int unit_file_mask( int unit_file_unmask( UnitFileScope scope, - bool runtime, + UnitFileFlags flags, const char *root_dir, char **files, UnitFileChange **changes, @@ -1787,6 +1865,7 @@ int unit_file_unmask( size_t n_todo = 0, n_allocated = 0; const char *config_path; char **i; + bool dry_run; int r, q; assert(scope >= 0); @@ -1796,7 +1875,8 @@ int unit_file_unmask( if (r < 0) return r; - config_path = runtime ? paths.runtime_config : paths.persistent_config; + config_path = (flags & UNIT_FILE_RUNTIME) ? paths.runtime_config : paths.persistent_config; + dry_run = !!(flags & UNIT_FILE_DRY_RUN); STRV_FOREACH(i, files) { _cleanup_free_ char *path = NULL; @@ -1833,7 +1913,7 @@ int unit_file_unmask( if (!path) return -ENOMEM; - if (unlink(path) < 0) { + if (!dry_run && unlink(path) < 0) { if (errno != ENOENT) { if (r >= 0) r = -errno; @@ -1851,7 +1931,7 @@ int unit_file_unmask( return q; } - q = remove_marked_symlinks(remove_symlinks_to, config_path, &paths, changes, n_changes); + q = remove_marked_symlinks(remove_symlinks_to, config_path, &paths, dry_run, changes, n_changes); if (r >= 0) r = q; @@ -1860,10 +1940,9 @@ int unit_file_unmask( int unit_file_link( UnitFileScope scope, - bool runtime, + UnitFileFlags flags, const char *root_dir, char **files, - bool force, UnitFileChange **changes, unsigned *n_changes) { @@ -1881,7 +1960,7 @@ int unit_file_link( if (r < 0) return r; - config_path = runtime ? paths.runtime_config : paths.persistent_config; + config_path = (flags & UNIT_FILE_RUNTIME) ? paths.runtime_config : paths.persistent_config; STRV_FOREACH(i, files) { _cleanup_free_ char *full = NULL; @@ -1925,14 +2004,12 @@ int unit_file_link( r = 0; STRV_FOREACH(i, todo) { _cleanup_free_ char *new_path = NULL; - const char *old_path; - old_path = skip_root(&paths, *i); new_path = path_make_absolute(basename(*i), config_path); if (!new_path) return -ENOMEM; - q = create_symlink(old_path ?: *i, new_path, force, changes, n_changes); + q = create_symlink(&paths, *i, new_path, !!(flags & UNIT_FILE_FORCE), changes, n_changes); if (q < 0 && r >= 0) r = q; } @@ -1967,7 +2044,6 @@ int unit_file_revert( unsigned *n_changes) { _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; - /* _cleanup_(install_context_done) InstallContext c = {}; */ _cleanup_lookup_paths_free_ LookupPaths paths = {}; _cleanup_strv_free_ char **todo = NULL; size_t n_todo = 0, n_allocated = 0; @@ -2105,11 +2181,11 @@ int unit_file_revert( return q; } - q = remove_marked_symlinks(remove_symlinks_to, paths.runtime_config, &paths, changes, n_changes); + q = remove_marked_symlinks(remove_symlinks_to, paths.runtime_config, &paths, false, changes, n_changes); if (r >= 0) r = q; - q = remove_marked_symlinks(remove_symlinks_to, paths.persistent_config, &paths, changes, n_changes); + q = remove_marked_symlinks(remove_symlinks_to, paths.persistent_config, &paths, false, changes, n_changes); if (r >= 0) r = q; @@ -2118,12 +2194,11 @@ int unit_file_revert( int unit_file_add_dependency( UnitFileScope scope, - bool runtime, + UnitFileFlags flags, const char *root_dir, char **files, const char *target, UnitDependency dep, - bool force, UnitFileChange **changes, unsigned *n_changes) { @@ -2148,9 +2223,10 @@ int unit_file_add_dependency( if (r < 0) return r; - config_path = runtime ? paths.runtime_config : paths.persistent_config; + config_path = (flags & UNIT_FILE_RUNTIME) ? paths.runtime_config : paths.persistent_config; - r = install_info_discover(scope, &c, &paths, target, SEARCH_FOLLOW_CONFIG_SYMLINKS, &target_info); + r = install_info_discover(scope, &c, &paths, target, SEARCH_FOLLOW_CONFIG_SYMLINKS, + &target_info, changes, n_changes); if (r < 0) return r; r = install_info_may_process(target_info, &paths, changes, n_changes); @@ -2162,7 +2238,8 @@ int unit_file_add_dependency( STRV_FOREACH(f, files) { char ***l; - r = install_info_discover(scope, &c, &paths, *f, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); + r = install_info_discover(scope, &c, &paths, *f, SEARCH_FOLLOW_CONFIG_SYMLINKS, + &i, changes, n_changes); if (r < 0) return r; r = install_info_may_process(i, &paths, changes, n_changes); @@ -2186,15 +2263,14 @@ int unit_file_add_dependency( return -ENOMEM; } - return install_context_apply(scope, &c, &paths, config_path, force, SEARCH_FOLLOW_CONFIG_SYMLINKS, changes, n_changes); + return install_context_apply(scope, &c, &paths, config_path, !!(flags & UNIT_FILE_FORCE), SEARCH_FOLLOW_CONFIG_SYMLINKS, changes, n_changes); } int unit_file_enable( UnitFileScope scope, - bool runtime, + UnitFileFlags flags, const char *root_dir, char **files, - bool force, UnitFileChange **changes, unsigned *n_changes) { @@ -2212,10 +2288,11 @@ int unit_file_enable( if (r < 0) return r; - config_path = runtime ? paths.runtime_config : paths.persistent_config; + config_path = (flags & UNIT_FILE_RUNTIME) ? paths.runtime_config : paths.persistent_config; STRV_FOREACH(f, files) { - r = install_info_discover(scope, &c, &paths, *f, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); + r = install_info_discover(scope, &c, &paths, *f, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, + &i, changes, n_changes); if (r < 0) return r; r = install_info_may_process(i, &paths, changes, n_changes); @@ -2230,12 +2307,12 @@ int unit_file_enable( is useful to determine whether the passed files had any installation data at all. */ - return install_context_apply(scope, &c, &paths, config_path, force, SEARCH_LOAD, changes, n_changes); + return install_context_apply(scope, &c, &paths, config_path, !!(flags & UNIT_FILE_FORCE), SEARCH_LOAD, changes, n_changes); } int unit_file_disable( UnitFileScope scope, - bool runtime, + UnitFileFlags flags, const char *root_dir, char **files, UnitFileChange **changes, @@ -2255,13 +2332,13 @@ int unit_file_disable( if (r < 0) return r; - config_path = runtime ? paths.runtime_config : paths.persistent_config; + config_path = (flags & UNIT_FILE_RUNTIME) ? paths.runtime_config : paths.persistent_config; STRV_FOREACH(i, files) { if (!unit_name_is_valid(*i, UNIT_NAME_ANY)) return -EINVAL; - r = install_info_add(&c, *i, NULL, NULL); + r = install_info_add(&c, *i, NULL, false, NULL); if (r < 0) return r; } @@ -2270,15 +2347,14 @@ int unit_file_disable( if (r < 0) return r; - return remove_marked_symlinks(remove_symlinks_to, config_path, &paths, changes, n_changes); + return remove_marked_symlinks(remove_symlinks_to, config_path, &paths, !!(flags & UNIT_FILE_DRY_RUN), changes, n_changes); } int unit_file_reenable( UnitFileScope scope, - bool runtime, + UnitFileFlags flags, const char *root_dir, char **files, - bool force, UnitFileChange **changes, unsigned *n_changes) { @@ -2293,26 +2369,26 @@ int unit_file_reenable( n[i] = basename(files[i]); n[i] = NULL; - r = unit_file_disable(scope, runtime, root_dir, n, changes, n_changes); + r = unit_file_disable(scope, flags, root_dir, n, changes, n_changes); if (r < 0) return r; /* But the enable command with the full name */ - return unit_file_enable(scope, runtime, root_dir, files, force, changes, n_changes); + return unit_file_enable(scope, flags, root_dir, files, changes, n_changes); } int unit_file_set_default( UnitFileScope scope, + UnitFileFlags flags, const char *root_dir, const char *name, - bool force, UnitFileChange **changes, unsigned *n_changes) { _cleanup_lookup_paths_free_ LookupPaths paths = {}; _cleanup_(install_context_done) InstallContext c = {}; UnitFileInstallInfo *i; - const char *new_path, *old_path; + const char *new_path; int r; assert(scope >= 0); @@ -2328,17 +2404,15 @@ int unit_file_set_default( if (r < 0) return r; - r = install_info_discover(scope, &c, &paths, name, 0, &i); + r = install_info_discover(scope, &c, &paths, name, 0, &i, changes, n_changes); if (r < 0) return r; r = install_info_may_process(i, &paths, changes, n_changes); if (r < 0) return r; - old_path = skip_root(&paths, i->path); new_path = strjoina(paths.persistent_config, "/" SPECIAL_DEFAULT_TARGET); - - return create_symlink(old_path ?: i->path, new_path, force, changes, n_changes); + return create_symlink(&paths, i->path, new_path, !!(flags & UNIT_FILE_FORCE), changes, n_changes); } int unit_file_get_default( @@ -2360,7 +2434,8 @@ int unit_file_get_default( if (r < 0) return r; - r = install_info_discover(scope, &c, &paths, SPECIAL_DEFAULT_TARGET, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); + r = install_info_discover(scope, &c, &paths, SPECIAL_DEFAULT_TARGET, SEARCH_FOLLOW_CONFIG_SYMLINKS, + &i, NULL, NULL); if (r < 0) return r; r = install_info_may_process(i, &paths, NULL, 0); @@ -2392,7 +2467,8 @@ static int unit_file_lookup_state( if (!unit_name_is_valid(name, UNIT_NAME_ANY)) return -EINVAL; - r = install_info_discover(scope, &c, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); + r = install_info_discover(scope, &c, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, + &i, NULL, NULL); if (r < 0) return r; @@ -2479,7 +2555,7 @@ int unit_file_exists(UnitFileScope scope, const LookupPaths *paths, const char * if (!unit_name_is_valid(name, UNIT_NAME_ANY)) return -EINVAL; - r = install_info_discover(scope, &c, paths, name, 0, NULL); + r = install_info_discover(scope, &c, paths, name, 0, NULL, NULL, NULL); if (r == -ENOENT) return 0; if (r < 0) @@ -2660,7 +2736,7 @@ static int execute_preset( if (r < 0) return r; - r = remove_marked_symlinks(remove_symlinks_to, config_path, paths, changes, n_changes); + r = remove_marked_symlinks(remove_symlinks_to, config_path, paths, false, changes, n_changes); } else r = 0; @@ -2685,25 +2761,34 @@ static int preset_prepare_one( InstallContext *plus, InstallContext *minus, LookupPaths *paths, - UnitFilePresetMode mode, const char *name, Presets presets, UnitFileChange **changes, unsigned *n_changes) { + _cleanup_(install_context_done) InstallContext tmp = {}; UnitFileInstallInfo *i; int r; - if (install_info_find(plus, name) || - install_info_find(minus, name)) + if (install_info_find(plus, name) || install_info_find(minus, name)) return 0; + r = install_info_discover(scope, &tmp, paths, name, SEARCH_FOLLOW_CONFIG_SYMLINKS, + &i, changes, n_changes); + if (r < 0) + return r; + if (!streq(name, i->name)) { + log_debug("Skipping %s because is an alias for %s", name, i->name); + return 0; + } + r = query_presets(name, presets); if (r < 0) return r; if (r > 0) { - r = install_info_discover(scope, plus, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); + r = install_info_discover(scope, plus, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, + &i, changes, n_changes); if (r < 0) return r; @@ -2711,18 +2796,18 @@ static int preset_prepare_one( if (r < 0) return r; } else - r = install_info_discover(scope, minus, paths, name, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); + r = install_info_discover(scope, minus, paths, name, SEARCH_FOLLOW_CONFIG_SYMLINKS, + &i, changes, n_changes); return r; } int unit_file_preset( UnitFileScope scope, - bool runtime, + UnitFileFlags flags, const char *root_dir, char **files, UnitFilePresetMode mode, - bool force, UnitFileChange **changes, unsigned *n_changes) { @@ -2741,27 +2826,26 @@ int unit_file_preset( if (r < 0) return r; - config_path = runtime ? paths.runtime_config : paths.persistent_config; + config_path = (flags & UNIT_FILE_RUNTIME) ? paths.runtime_config : paths.persistent_config; r = read_presets(scope, root_dir, &presets); if (r < 0) return r; STRV_FOREACH(i, files) { - r = preset_prepare_one(scope, &plus, &minus, &paths, mode, *i, presets, changes, n_changes); + r = preset_prepare_one(scope, &plus, &minus, &paths, *i, presets, changes, n_changes); if (r < 0) return r; } - return execute_preset(scope, &plus, &minus, &paths, config_path, files, mode, force, changes, n_changes); + return execute_preset(scope, &plus, &minus, &paths, config_path, files, mode, !!(flags & UNIT_FILE_FORCE), changes, n_changes); } int unit_file_preset_all( UnitFileScope scope, - bool runtime, + UnitFileFlags flags, const char *root_dir, UnitFilePresetMode mode, - bool force, UnitFileChange **changes, unsigned *n_changes) { @@ -2780,7 +2864,7 @@ int unit_file_preset_all( if (r < 0) return r; - config_path = runtime ? paths.runtime_config : paths.persistent_config; + config_path = (flags & UNIT_FILE_RUNTIME) ? paths.runtime_config : paths.persistent_config; r = read_presets(scope, root_dir, &presets); if (r < 0) @@ -2809,7 +2893,7 @@ int unit_file_preset_all( continue; /* we don't pass changes[] in, because we want to handle errors on our own */ - r = preset_prepare_one(scope, &plus, &minus, &paths, mode, de->d_name, presets, NULL, 0); + r = preset_prepare_one(scope, &plus, &minus, &paths, de->d_name, presets, NULL, 0); if (r == -ERFKILL) r = unit_file_changes_add(changes, n_changes, UNIT_FILE_IS_MASKED, de->d_name, NULL); @@ -2821,7 +2905,7 @@ int unit_file_preset_all( } } - return execute_preset(scope, &plus, &minus, &paths, config_path, NULL, mode, force, changes, n_changes); + return execute_preset(scope, &plus, &minus, &paths, config_path, NULL, mode, !!(flags & UNIT_FILE_FORCE), changes, n_changes); } static void unit_file_list_free_one(UnitFileList *f) { diff --git a/src/shared/install.h b/src/shared/install.h index c6aa4f6ef1..7a5859e729 100644 --- a/src/shared/install.h +++ b/src/shared/install.h @@ -23,6 +23,7 @@ typedef enum UnitFileScope UnitFileScope; typedef enum UnitFileState UnitFileState; typedef enum UnitFilePresetMode UnitFilePresetMode; typedef enum UnitFileChangeType UnitFileChangeType; +typedef enum UnitFileFlags UnitFileFlags; typedef enum UnitFileType UnitFileType; typedef struct UnitFileChange UnitFileChange; typedef struct UnitFileList UnitFileList; @@ -78,6 +79,12 @@ enum UnitFileChangeType { _UNIT_FILE_CHANGE_INVALID = INT_MIN }; +enum UnitFileFlags { + UNIT_FILE_RUNTIME = 1, + UNIT_FILE_FORCE = 1 << 1, + UNIT_FILE_DRY_RUN = 1 << 2, +}; + /* type can either one of the UnitFileChangeTypes listed above, or a negative error. * If source is specified, it should be the contents of the path symlink. * In case of an error, source should be the existing symlink contents or NULL @@ -119,10 +126,10 @@ struct UnitFileInstallInfo { char **also; char *default_instance; + char *symlink_target; UnitFileType type; - - char *symlink_target; + bool auxiliary; }; static inline bool UNIT_FILE_INSTALL_INFO_HAS_RULES(UnitFileInstallInfo *i) { @@ -144,65 +151,59 @@ bool unit_type_may_template(UnitType type) _const_; int unit_file_enable( UnitFileScope scope, - bool runtime, + UnitFileFlags flags, const char *root_dir, char **files, - bool force, UnitFileChange **changes, unsigned *n_changes); int unit_file_disable( UnitFileScope scope, - bool runtime, + UnitFileFlags flags, const char *root_dir, char **files, UnitFileChange **changes, unsigned *n_changes); int unit_file_reenable( UnitFileScope scope, - bool runtime, + UnitFileFlags flags, const char *root_dir, char **files, - bool force, UnitFileChange **changes, unsigned *n_changes); int unit_file_preset( UnitFileScope scope, - bool runtime, + UnitFileFlags flags, const char *root_dir, char **files, UnitFilePresetMode mode, - bool force, UnitFileChange **changes, unsigned *n_changes); int unit_file_preset_all( UnitFileScope scope, - bool runtime, + UnitFileFlags flags, const char *root_dir, UnitFilePresetMode mode, - bool force, UnitFileChange **changes, unsigned *n_changes); int unit_file_mask( UnitFileScope scope, - bool runtime, + UnitFileFlags flags, const char *root_dir, char **files, - bool force, UnitFileChange **changes, unsigned *n_changes); int unit_file_unmask( UnitFileScope scope, - bool runtime, + UnitFileFlags flags, const char *root_dir, char **files, UnitFileChange **changes, unsigned *n_changes); int unit_file_link( UnitFileScope scope, - bool runtime, + UnitFileFlags flags, const char *root_dir, char **files, - bool force, UnitFileChange **changes, unsigned *n_changes); int unit_file_revert( @@ -213,9 +214,9 @@ int unit_file_revert( unsigned *n_changes); int unit_file_set_default( UnitFileScope scope, + UnitFileFlags flags, const char *root_dir, const char *file, - bool force, UnitFileChange **changes, unsigned *n_changes); int unit_file_get_default( @@ -224,12 +225,11 @@ int unit_file_get_default( char **name); int unit_file_add_dependency( UnitFileScope scope, - bool runtime, + UnitFileFlags flags, const char *root_dir, char **files, const char *target, UnitDependency dep, - bool force, UnitFileChange **changes, unsigned *n_changes); diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index d04728f505..f9d9c4ed62 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -45,6 +45,7 @@ #include "parse-util.h" #include "process-util.h" #include "sparse-endian.h" +#include "stdio-util.h" #include "string-table.h" #include "string-util.h" #include "terminal-util.h" @@ -206,6 +207,108 @@ static bool print_multiline(FILE *f, unsigned prefix, unsigned n_columns, Output return ellipsized; } +static int output_timestamp_monotonic(FILE *f, sd_journal *j, const char *monotonic) { + sd_id128_t boot_id; + uint64_t t; + int r; + + assert(f); + assert(j); + + r = -ENXIO; + if (monotonic) + r = safe_atou64(monotonic, &t); + if (r < 0) + r = sd_journal_get_monotonic_usec(j, &t, &boot_id); + if (r < 0) + return log_error_errno(r, "Failed to get monotonic timestamp: %m"); + + fprintf(f, "[%5llu.%06llu]", + (unsigned long long) (t / USEC_PER_SEC), + (unsigned long long) (t % USEC_PER_SEC)); + + return 1 + 5 + 1 + 6 + 1; +} + +static int output_timestamp_realtime(FILE *f, sd_journal *j, OutputMode mode, OutputFlags flags, const char *realtime) { + char buf[MAX(FORMAT_TIMESTAMP_MAX, 64)]; + struct tm *(*gettime_r)(const time_t *, struct tm *); + struct tm tm; + uint64_t x; + time_t t; + int r; + + assert(f); + assert(j); + + r = -ENXIO; + if (realtime) + r = safe_atou64(realtime, &x); + if (r < 0) + r = sd_journal_get_realtime_usec(j, &x); + if (r < 0) + return log_error_errno(r, "Failed to get realtime timestamp: %m"); + + if (mode == OUTPUT_SHORT_FULL) { + const char *k; + + if (flags & OUTPUT_UTC) + k = format_timestamp_utc(buf, sizeof(buf), x); + else + k = format_timestamp(buf, sizeof(buf), x); + if (!k) { + log_error("Failed to format timestamp."); + return -EINVAL; + } + + } else { + gettime_r = (flags & OUTPUT_UTC) ? gmtime_r : localtime_r; + t = (time_t) (x / USEC_PER_SEC); + + switch (mode) { + + case OUTPUT_SHORT_UNIX: + xsprintf(buf, "%10llu.%06llu", (unsigned long long) t, (unsigned long long) (x % USEC_PER_SEC)); + break; + + case OUTPUT_SHORT_ISO: + if (strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", gettime_r(&t, &tm)) <= 0) { + log_error("Failed for format ISO time"); + return -EINVAL; + } + break; + + case OUTPUT_SHORT: + case OUTPUT_SHORT_PRECISE: + + if (strftime(buf, sizeof(buf), "%b %d %H:%M:%S", gettime_r(&t, &tm)) <= 0) { + log_error("Failed to format syslog time"); + return -EINVAL; + } + + if (mode == OUTPUT_SHORT_PRECISE) { + size_t k; + + assert(sizeof(buf) > strlen(buf)); + k = sizeof(buf) - strlen(buf); + + r = snprintf(buf + strlen(buf), k, ".%06llu", (unsigned long long) (x % USEC_PER_SEC)); + if (r <= 0 || (size_t) r >= k) { /* too long? */ + log_error("Failed to format precise time"); + return -EINVAL; + } + } + break; + + default: + assert_not_reached("Unknown time format"); + } + } + + fputs(buf, f); + return (int) strlen(buf); +} + static int output_short( FILE *f, sd_journal *j, @@ -305,78 +408,15 @@ static int output_short( if (priority_len == 1 && *priority >= '0' && *priority <= '7') p = *priority - '0'; - if (mode == OUTPUT_SHORT_MONOTONIC) { - uint64_t t; - sd_id128_t boot_id; - - r = -ENOENT; - - if (monotonic) - r = safe_atou64(monotonic, &t); - - if (r < 0) - r = sd_journal_get_monotonic_usec(j, &t, &boot_id); - - if (r < 0) - return log_error_errno(r, "Failed to get monotonic timestamp: %m"); - - fprintf(f, "[%5llu.%06llu]", - (unsigned long long) (t / USEC_PER_SEC), - (unsigned long long) (t % USEC_PER_SEC)); - - n += 1 + 5 + 1 + 6 + 1; - - } else { - char buf[64]; - uint64_t x; - time_t t; - struct tm tm; - struct tm *(*gettime_r)(const time_t *, struct tm *); - - r = -ENOENT; - gettime_r = (flags & OUTPUT_UTC) ? gmtime_r : localtime_r; - - if (realtime) - r = safe_atou64(realtime, &x); - - if (r < 0) - r = sd_journal_get_realtime_usec(j, &x); - - if (r < 0) - return log_error_errno(r, "Failed to get realtime timestamp: %m"); - - t = (time_t) (x / USEC_PER_SEC); - - switch (mode) { - - case OUTPUT_SHORT_UNIX: - r = snprintf(buf, sizeof(buf), "%10llu.%06llu", (unsigned long long) t, (unsigned long long) (x % USEC_PER_SEC)); - break; - - case OUTPUT_SHORT_ISO: - r = strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", gettime_r(&t, &tm)); - break; - - case OUTPUT_SHORT_PRECISE: - r = strftime(buf, sizeof(buf), "%b %d %H:%M:%S", gettime_r(&t, &tm)); - if (r > 0) - snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ".%06llu", (unsigned long long) (x % USEC_PER_SEC)); - break; - - default: - r = strftime(buf, sizeof(buf), "%b %d %H:%M:%S", gettime_r(&t, &tm)); - } - - if (r <= 0) { - log_error("Failed to format time."); - return -EINVAL; - } - - fputs(buf, f); - n += strlen(buf); - } + if (mode == OUTPUT_SHORT_MONOTONIC) + r = output_timestamp_monotonic(f, j, monotonic); + else + r = output_timestamp_realtime(f, j, mode, flags, realtime); + if (r < 0) + return r; + n += r; - if (hostname && (flags & OUTPUT_NO_HOSTNAME)) { + if (flags & OUTPUT_NO_HOSTNAME) { /* Suppress display of the hostname if this is requested. */ hostname = NULL; hostname_len = 0; @@ -910,6 +950,7 @@ static int (*output_funcs[_OUTPUT_MODE_MAX])( [OUTPUT_SHORT_PRECISE] = output_short, [OUTPUT_SHORT_MONOTONIC] = output_short, [OUTPUT_SHORT_UNIX] = output_short, + [OUTPUT_SHORT_FULL] = output_short, [OUTPUT_VERBOSE] = output_verbose, [OUTPUT_EXPORT] = output_export, [OUTPUT_JSON] = output_json, diff --git a/src/shared/machine-image.c b/src/shared/machine-image.c index 529d89ee2a..060f8d50c7 100644 --- a/src/shared/machine-image.c +++ b/src/shared/machine-image.c @@ -62,8 +62,7 @@ Image *image_unref(Image *i) { free(i->name); free(i->path); - free(i); - return NULL; + return mfree(i); } static char **image_settings_path(Image *image) { diff --git a/src/shared/output-mode.c b/src/shared/output-mode.c index bec53ee0ae..67d8208ad2 100644 --- a/src/shared/output-mode.c +++ b/src/shared/output-mode.c @@ -22,6 +22,7 @@ static const char *const output_mode_table[_OUTPUT_MODE_MAX] = { [OUTPUT_SHORT] = "short", + [OUTPUT_SHORT_FULL] = "short-full", [OUTPUT_SHORT_ISO] = "short-iso", [OUTPUT_SHORT_PRECISE] = "short-precise", [OUTPUT_SHORT_MONOTONIC] = "short-monotonic", diff --git a/src/shared/output-mode.h b/src/shared/output-mode.h index f37189e57f..ff29dafcb5 100644 --- a/src/shared/output-mode.h +++ b/src/shared/output-mode.h @@ -23,6 +23,7 @@ typedef enum OutputMode { OUTPUT_SHORT, + OUTPUT_SHORT_FULL, OUTPUT_SHORT_ISO, OUTPUT_SHORT_PRECISE, OUTPUT_SHORT_MONOTONIC, diff --git a/src/shared/pager.c b/src/shared/pager.c index a2524d4420..09672a4abf 100644 --- a/src/shared/pager.c +++ b/src/shared/pager.c @@ -36,6 +36,7 @@ #include "process-util.h" #include "signal-util.h" #include "string-util.h" +#include "strv.h" #include "terminal-util.h" static pid_t pager_pid = 0; @@ -71,7 +72,7 @@ int pager_open(bool no_pager, bool jump_to_end) { pager = getenv("PAGER"); /* If the pager is explicitly turned off, honour it */ - if (pager && (pager[0] == 0 || streq(pager, "cat"))) + if (pager && STR_IN_SET(pager, "", "cat")) return 0; /* Determine and cache number of columns before we spawn the diff --git a/src/shared/ptyfwd.c b/src/shared/ptyfwd.c index 02c03b98d8..293c6673fc 100644 --- a/src/shared/ptyfwd.c +++ b/src/shared/ptyfwd.c @@ -68,6 +68,8 @@ struct PTYForward { bool read_from_master:1; + bool done:1; + bool last_char_set:1; char last_char; @@ -76,10 +78,54 @@ struct PTYForward { usec_t escape_timestamp; unsigned escape_counter; + + PTYForwardHandler handler; + void *userdata; }; #define ESCAPE_USEC (1*USEC_PER_SEC) +static void pty_forward_disconnect(PTYForward *f) { + + if (f) { + f->stdin_event_source = sd_event_source_unref(f->stdin_event_source); + f->stdout_event_source = sd_event_source_unref(f->stdout_event_source); + + f->master_event_source = sd_event_source_unref(f->master_event_source); + f->sigwinch_event_source = sd_event_source_unref(f->sigwinch_event_source); + f->event = sd_event_unref(f->event); + + if (f->saved_stdout) + tcsetattr(STDOUT_FILENO, TCSANOW, &f->saved_stdout_attr); + if (f->saved_stdin) + tcsetattr(STDIN_FILENO, TCSANOW, &f->saved_stdin_attr); + + f->saved_stdout = f->saved_stdin = false; + } + + /* STDIN/STDOUT should not be nonblocking normally, so let's unconditionally reset it */ + fd_nonblock(STDIN_FILENO, false); + fd_nonblock(STDOUT_FILENO, false); +} + +static int pty_forward_done(PTYForward *f, int rcode) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + assert(f); + + if (f->done) + return 0; + + e = sd_event_ref(f->event); + + f->done = true; + pty_forward_disconnect(f); + + if (f->handler) + return f->handler(f, rcode, f->userdata); + else + return sd_event_exit(e, rcode < 0 ? EXIT_FAILURE : rcode); +} + static bool look_for_escape(PTYForward *f, const char *buffer, size_t n) { const char *p; @@ -147,7 +193,7 @@ static int shovel(PTYForward *f) { f->stdin_event_source = sd_event_source_unref(f->stdin_event_source); } else { log_error_errno(errno, "read(): %m"); - return sd_event_exit(f->event, EXIT_FAILURE); + return pty_forward_done(f, -errno); } } else if (k == 0) { /* EOF on stdin */ @@ -156,12 +202,10 @@ static int shovel(PTYForward *f) { f->stdin_event_source = sd_event_source_unref(f->stdin_event_source); } else { - /* Check if ^] has been - * pressed three times within - * one second. If we get this - * we quite immediately. */ + /* Check if ^] has been pressed three times within one second. If we get this we quite + * immediately. */ if (look_for_escape(f, f->in_buffer + f->in_buffer_full, k)) - return sd_event_exit(f->event, EXIT_FAILURE); + return pty_forward_done(f, -ECANCELED); f->in_buffer_full += (size_t) k; } @@ -181,7 +225,7 @@ static int shovel(PTYForward *f) { f->master_event_source = sd_event_source_unref(f->master_event_source); } else { log_error_errno(errno, "write(): %m"); - return sd_event_exit(f->event, EXIT_FAILURE); + return pty_forward_done(f, -errno); } } else { assert(f->in_buffer_full >= (size_t) k); @@ -211,7 +255,7 @@ static int shovel(PTYForward *f) { f->master_event_source = sd_event_source_unref(f->master_event_source); } else { log_error_errno(errno, "read(): %m"); - return sd_event_exit(f->event, EXIT_FAILURE); + return pty_forward_done(f, -errno); } } else { f->read_from_master = true; @@ -232,7 +276,7 @@ static int shovel(PTYForward *f) { f->stdout_event_source = sd_event_source_unref(f->stdout_event_source); } else { log_error_errno(errno, "write(): %m"); - return sd_event_exit(f->event, EXIT_FAILURE); + return pty_forward_done(f, -errno); } } else { @@ -255,7 +299,7 @@ static int shovel(PTYForward *f) { if ((f->out_buffer_full <= 0 || f->stdout_hangup) && (f->in_buffer_full <= 0 || f->master_hangup)) - return sd_event_exit(f->event, EXIT_SUCCESS); + return pty_forward_done(f, 0); } return 0; @@ -418,28 +462,8 @@ int pty_forward_new( } PTYForward *pty_forward_free(PTYForward *f) { - - if (f) { - sd_event_source_unref(f->stdin_event_source); - sd_event_source_unref(f->stdout_event_source); - sd_event_source_unref(f->master_event_source); - sd_event_source_unref(f->sigwinch_event_source); - sd_event_unref(f->event); - - if (f->saved_stdout) - tcsetattr(STDOUT_FILENO, TCSANOW, &f->saved_stdout_attr); - if (f->saved_stdin) - tcsetattr(STDIN_FILENO, TCSANOW, &f->saved_stdin_attr); - - free(f); - } - - /* STDIN/STDOUT should not be nonblocking normally, so let's - * unconditionally reset it */ - fd_nonblock(STDIN_FILENO, false); - fd_nonblock(STDOUT_FILENO, false); - - return NULL; + pty_forward_disconnect(f); + return mfree(f); } int pty_forward_get_last_char(PTYForward *f, char *ch) { @@ -477,8 +501,21 @@ int pty_forward_set_ignore_vhangup(PTYForward *f, bool b) { return 0; } -int pty_forward_get_ignore_vhangup(PTYForward *f) { +bool pty_forward_get_ignore_vhangup(PTYForward *f) { assert(f); return !!(f->flags & PTY_FORWARD_IGNORE_VHANGUP); } + +bool pty_forward_is_done(PTYForward *f) { + assert(f); + + return f->done; +} + +void pty_forward_set_handler(PTYForward *f, PTYForwardHandler cb, void *userdata) { + assert(f); + + f->handler = cb; + f->userdata = userdata; +} diff --git a/src/shared/ptyfwd.h b/src/shared/ptyfwd.h index a046eb4e5e..bd5d5fec0d 100644 --- a/src/shared/ptyfwd.h +++ b/src/shared/ptyfwd.h @@ -37,12 +37,18 @@ typedef enum PTYForwardFlags { PTY_FORWARD_IGNORE_INITIAL_VHANGUP = 4, } PTYForwardFlags; +typedef int (*PTYForwardHandler)(PTYForward *f, int rcode, void*userdata); + int pty_forward_new(sd_event *event, int master, PTYForwardFlags flags, PTYForward **f); PTYForward *pty_forward_free(PTYForward *f); int pty_forward_get_last_char(PTYForward *f, char *ch); int pty_forward_set_ignore_vhangup(PTYForward *f, bool ignore_vhangup); -int pty_forward_get_ignore_vhangup(PTYForward *f); +bool pty_forward_get_ignore_vhangup(PTYForward *f); + +bool pty_forward_is_done(PTYForward *f); + +void pty_forward_set_handler(PTYForward *f, PTYForwardHandler handler, void *userdata); DEFINE_TRIVIAL_CLEANUP_FUNC(PTYForward*, pty_forward_free); diff --git a/src/shared/seccomp-util.c b/src/shared/seccomp-util.c index 8656d112b8..c9b24f1065 100644 --- a/src/shared/seccomp-util.c +++ b/src/shared/seccomp-util.c @@ -20,25 +20,58 @@ #include <errno.h> #include <seccomp.h> #include <stddef.h> +#include <sys/prctl.h> +#include <linux/seccomp.h> #include "macro.h" #include "seccomp-util.h" #include "string-util.h" +#include "util.h" const char* seccomp_arch_to_string(uint32_t c) { + /* Maintain order used in <seccomp.h>. + * + * Names used here should be the same as those used for ConditionArchitecture=, + * except for "subarchitectures" like x32. */ - if (c == SCMP_ARCH_NATIVE) + switch(c) { + case SCMP_ARCH_NATIVE: return "native"; - if (c == SCMP_ARCH_X86) + case SCMP_ARCH_X86: return "x86"; - if (c == SCMP_ARCH_X86_64) + case SCMP_ARCH_X86_64: return "x86-64"; - if (c == SCMP_ARCH_X32) + case SCMP_ARCH_X32: return "x32"; - if (c == SCMP_ARCH_ARM) + case SCMP_ARCH_ARM: return "arm"; - - return NULL; + case SCMP_ARCH_AARCH64: + return "arm64"; + case SCMP_ARCH_MIPS: + return "mips"; + case SCMP_ARCH_MIPS64: + return "mips64"; + case SCMP_ARCH_MIPS64N32: + return "mips64-n32"; + case SCMP_ARCH_MIPSEL: + return "mips-le"; + case SCMP_ARCH_MIPSEL64: + return "mips64-le"; + case SCMP_ARCH_MIPSEL64N32: + return "mips64-le-n32"; + case SCMP_ARCH_PPC: + return "ppc"; + case SCMP_ARCH_PPC64: + return "ppc64"; + case SCMP_ARCH_PPC64LE: + return "ppc64-le"; + case SCMP_ARCH_S390: + return "s390"; + case SCMP_ARCH_S390X: + return "s390x"; + default: + return NULL; + } } int seccomp_arch_from_string(const char *n, uint32_t *ret) { @@ -57,60 +90,174 @@ int seccomp_arch_from_string(const char *n, uint32_t *ret) { *ret = SCMP_ARCH_X32; else if (streq(n, "arm")) *ret = SCMP_ARCH_ARM; + else if (streq(n, "arm64")) + *ret = SCMP_ARCH_AARCH64; + else if (streq(n, "mips")) + *ret = SCMP_ARCH_MIPS; + else if (streq(n, "mips64")) + *ret = SCMP_ARCH_MIPS64; + else if (streq(n, "mips64-n32")) + *ret = SCMP_ARCH_MIPS64N32; + else if (streq(n, "mips-le")) + *ret = SCMP_ARCH_MIPSEL; + else if (streq(n, "mips64-le")) + *ret = SCMP_ARCH_MIPSEL64; + else if (streq(n, "mips64-le-n32")) + *ret = SCMP_ARCH_MIPSEL64N32; + else if (streq(n, "ppc")) + *ret = SCMP_ARCH_PPC; + else if (streq(n, "ppc64")) + *ret = SCMP_ARCH_PPC64; + else if (streq(n, "ppc64-le")) + *ret = SCMP_ARCH_PPC64LE; + else if (streq(n, "s390")) + *ret = SCMP_ARCH_S390; + else if (streq(n, "s390x")) + *ret = SCMP_ARCH_S390X; else return -EINVAL; return 0; } -int seccomp_add_secondary_archs(scmp_filter_ctx *c) { - -#if defined(__i386__) || defined(__x86_64__) +int seccomp_init_conservative(scmp_filter_ctx *ret, uint32_t default_action) { + scmp_filter_ctx seccomp; int r; + /* Much like seccomp_init(), but tries to be a bit more conservative in its defaults: all secondary archs are + * added by default, and NNP is turned off. */ + + seccomp = seccomp_init(default_action); + if (!seccomp) + return -ENOMEM; + + r = seccomp_add_secondary_archs(seccomp); + if (r < 0) + goto finish; + + r = seccomp_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0); + if (r < 0) + goto finish; + + *ret = seccomp; + return 0; + +finish: + seccomp_release(seccomp); + return r; +} + +int seccomp_add_secondary_archs(scmp_filter_ctx ctx) { + /* Add in all possible secondary archs we are aware of that * this kernel might support. */ - r = seccomp_arch_add(c, SCMP_ARCH_X86); - if (r < 0 && r != -EEXIST) - return r; + static const int seccomp_arches[] = { +#if defined(__i386__) || defined(__x86_64__) + SCMP_ARCH_X86, + SCMP_ARCH_X86_64, + SCMP_ARCH_X32, - r = seccomp_arch_add(c, SCMP_ARCH_X86_64); - if (r < 0 && r != -EEXIST) - return r; +#elif defined(__arm__) || defined(__aarch64__) + SCMP_ARCH_ARM, + SCMP_ARCH_AARCH64, - r = seccomp_arch_add(c, SCMP_ARCH_X32); - if (r < 0 && r != -EEXIST) - return r; +#elif defined(__arm__) || defined(__aarch64__) + SCMP_ARCH_ARM, + SCMP_ARCH_AARCH64, + +#elif defined(__mips__) || defined(__mips64__) + SCMP_ARCH_MIPS, + SCMP_ARCH_MIPS64, + SCMP_ARCH_MIPS64N32, + SCMP_ARCH_MIPSEL, + SCMP_ARCH_MIPSEL64, + SCMP_ARCH_MIPSEL64N32, + +#elif defined(__powerpc__) || defined(__powerpc64__) + SCMP_ARCH_PPC, + SCMP_ARCH_PPC64, + SCMP_ARCH_PPC64LE, +#elif defined(__s390__) || defined(__s390x__) + SCMP_ARCH_S390, + SCMP_ARCH_S390X, #endif + }; + + unsigned i; + int r; + + for (i = 0; i < ELEMENTSOF(seccomp_arches); i++) { + r = seccomp_arch_add(ctx, seccomp_arches[i]); + if (r < 0 && r != -EEXIST) + return r; + } return 0; +} +static bool is_basic_seccomp_available(void) { + int r; + r = prctl(PR_GET_SECCOMP, 0, 0, 0, 0); + return r >= 0; } -const SystemCallFilterSet syscall_filter_sets[] = { - { +static bool is_seccomp_filter_available(void) { + int r; + r = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, NULL, 0, 0); + return r < 0 && errno == EFAULT; +} + +bool is_seccomp_available(void) { + static int cached_enabled = -1; + if (cached_enabled < 0) + cached_enabled = is_basic_seccomp_available() && is_seccomp_filter_available(); + return cached_enabled; +} + +const SyscallFilterSet syscall_filter_sets[_SYSCALL_FILTER_SET_MAX] = { + [SYSCALL_FILTER_SET_BASIC_IO] = { + /* Basic IO */ + .name = "@basic-io", + .value = + "close\0" + "dup2\0" + "dup3\0" + "dup\0" + "lseek\0" + "pread64\0" + "preadv\0" + "pwrite64\0" + "pwritev\0" + "read\0" + "readv\0" + "write\0" + "writev\0" + }, + [SYSCALL_FILTER_SET_CLOCK] = { /* Clock */ - .set_name = "@clock", + .name = "@clock", .value = "adjtimex\0" "clock_adjtime\0" "clock_settime\0" "settimeofday\0" "stime\0" - }, { + }, + [SYSCALL_FILTER_SET_CPU_EMULATION] = { /* CPU emulation calls */ - .set_name = "@cpu-emulation", + .name = "@cpu-emulation", .value = "modify_ldt\0" "subpage_prot\0" "switch_endian\0" "vm86\0" "vm86old\0" - }, { + }, + [SYSCALL_FILTER_SET_DEBUG] = { /* Debugging/Performance Monitoring/Tracing */ - .set_name = "@debug", + .name = "@debug", .value = "lookup_dcookie\0" "perf_event_open\0" @@ -118,20 +265,32 @@ const SystemCallFilterSet syscall_filter_sets[] = { "process_vm_writev\0" "ptrace\0" "rtas\0" +#ifdef __NR_s390_runtime_instr "s390_runtime_instr\0" +#endif "sys_debug_setcontext\0" - }, { - /* Default list */ - .set_name = "@default", + }, + [SYSCALL_FILTER_SET_DEFAULT] = { + /* Default list: the most basic of operations */ + .name = "@default", .value = + "clock_getres\0" + "clock_gettime\0" + "clock_nanosleep\0" "execve\0" "exit\0" "exit_group\0" + "getrlimit\0" /* make sure processes can query stack size and such */ + "gettimeofday\0" + "nanosleep\0" + "pause\0" "rt_sigreturn\0" "sigreturn\0" - }, { + "time\0" + }, + [SYSCALL_FILTER_SET_IO_EVENT] = { /* Event loop use */ - .set_name = "@io-event", + .name = "@io-event", .value = "_newselect\0" "epoll_create1\0" @@ -147,10 +306,12 @@ const SystemCallFilterSet syscall_filter_sets[] = { "ppoll\0" "pselect6\0" "select\0" - }, { - /* Message queues, SYSV IPC or other IPC: unusual */ - .set_name = "@ipc", + }, + [SYSCALL_FILTER_SET_IPC] = { + /* Message queues, SYSV IPC or other IPC */ + .name = "@ipc", .value = "ipc\0" + "memfd_create\0" "mq_getsetattr\0" "mq_notify\0" "mq_open\0" @@ -161,6 +322,8 @@ const SystemCallFilterSet syscall_filter_sets[] = { "msgget\0" "msgrcv\0" "msgsnd\0" + "pipe2\0" + "pipe\0" "process_vm_readv\0" "process_vm_writev\0" "semctl\0" @@ -171,33 +334,36 @@ const SystemCallFilterSet syscall_filter_sets[] = { "shmctl\0" "shmdt\0" "shmget\0" - }, { + }, + [SYSCALL_FILTER_SET_KEYRING] = { /* Keyring */ - .set_name = "@keyring", + .name = "@keyring", .value = "add_key\0" "keyctl\0" "request_key\0" - }, { + }, + [SYSCALL_FILTER_SET_MODULE] = { /* Kernel module control */ - .set_name = "@module", + .name = "@module", .value = "delete_module\0" "finit_module\0" "init_module\0" - }, { + }, + [SYSCALL_FILTER_SET_MOUNT] = { /* Mounting */ - .set_name = "@mount", + .name = "@mount", .value = "chroot\0" "mount\0" - "oldumount\0" "pivot_root\0" "umount2\0" "umount\0" - }, { + }, + [SYSCALL_FILTER_SET_NETWORK_IO] = { /* Network or Unix socket IO, should not be needed if not network facing */ - .set_name = "@network-io", + .name = "@network-io", .value = "accept4\0" "accept\0" @@ -220,9 +386,10 @@ const SystemCallFilterSet syscall_filter_sets[] = { "socket\0" "socketcall\0" "socketpair\0" - }, { + }, + [SYSCALL_FILTER_SET_OBSOLETE] = { /* Unusual, obsolete or unimplemented, some unknown even to libseccomp */ - .set_name = "@obsolete", + .name = "@obsolete", .value = "_sysctl\0" "afs_syscall\0" @@ -248,9 +415,10 @@ const SystemCallFilterSet syscall_filter_sets[] = { "uselib\0" "ustat\0" "vserver\0" - }, { + }, + [SYSCALL_FILTER_SET_PRIVILEGED] = { /* Nice grab-bag of all system calls which need superuser capabilities */ - .set_name = "@privileged", + .name = "@privileged", .value = "@clock\0" "@module\0" @@ -287,15 +455,15 @@ const SystemCallFilterSet syscall_filter_sets[] = { "setuid\0" "swapoff\0" "swapon\0" - "sysctl\0" + "_sysctl\0" "vhangup\0" - }, { + }, + [SYSCALL_FILTER_SET_PROCESS] = { /* Process control, execution, namespaces */ - .set_name = "@process", + .name = "@process", .value = "arch_prctl\0" "clone\0" - "execve\0" "execveat\0" "fork\0" "kill\0" @@ -305,19 +473,106 @@ const SystemCallFilterSet syscall_filter_sets[] = { "tkill\0" "unshare\0" "vfork\0" - }, { + }, + [SYSCALL_FILTER_SET_RAW_IO] = { /* Raw I/O ports */ - .set_name = "@raw-io", + .name = "@raw-io", .value = "ioperm\0" "iopl\0" "pciconfig_iobase\0" "pciconfig_read\0" "pciconfig_write\0" +#ifdef __NR_s390_pci_mmio_read "s390_pci_mmio_read\0" +#endif +#ifdef __NR_s390_pci_mmio_write "s390_pci_mmio_write\0" - }, { - .set_name = NULL, - .value = NULL - } +#endif + }, + [SYSCALL_FILTER_SET_RESOURCES] = { + /* Alter resource settings */ + .name = "@resources", + .value = + "sched_setparam\0" + "sched_setscheduler\0" + "sched_setaffinity\0" + "setpriority\0" + "setrlimit\0" + "set_mempolicy\0" + "migrate_pages\0" + "move_pages\0" + "mbind\0" + "sched_setattr\0" + "prlimit64\0" + }, }; + +const SyscallFilterSet *syscall_filter_set_find(const char *name) { + unsigned i; + + if (isempty(name) || name[0] != '@') + return NULL; + + for (i = 0; i < _SYSCALL_FILTER_SET_MAX; i++) + if (streq(syscall_filter_sets[i].name, name)) + return syscall_filter_sets + i; + + return NULL; +} + +int seccomp_add_syscall_filter_set(scmp_filter_ctx seccomp, const SyscallFilterSet *set, uint32_t action) { + const char *sys; + int r; + + assert(seccomp); + assert(set); + + NULSTR_FOREACH(sys, set->value) { + int id; + + if (sys[0] == '@') { + const SyscallFilterSet *other; + + other = syscall_filter_set_find(sys); + if (!other) + return -EINVAL; + + r = seccomp_add_syscall_filter_set(seccomp, other, action); + } else { + id = seccomp_syscall_resolve_name(sys); + if (id == __NR_SCMP_ERROR) + return -EINVAL; + + r = seccomp_rule_add(seccomp, action, id, 0); + } + if (r < 0) + return r; + } + + return 0; +} + +int seccomp_load_filter_set(uint32_t default_action, const SyscallFilterSet *set, uint32_t action) { + scmp_filter_ctx seccomp; + int r; + + assert(set); + + /* The one-stop solution: allocate a seccomp object, add a filter to it, and apply it */ + + r = seccomp_init_conservative(&seccomp, default_action); + if (r < 0) + return r; + + r = seccomp_add_syscall_filter_set(seccomp, set, action); + if (r < 0) + goto finish; + + r = seccomp_load(seccomp); + +finish: + seccomp_release(seccomp); + return r; + +} diff --git a/src/shared/seccomp-util.h b/src/shared/seccomp-util.h index be33eecb85..8e209efef2 100644 --- a/src/shared/seccomp-util.h +++ b/src/shared/seccomp-util.h @@ -20,16 +20,47 @@ ***/ #include <seccomp.h> +#include <stdbool.h> #include <stdint.h> const char* seccomp_arch_to_string(uint32_t c); int seccomp_arch_from_string(const char *n, uint32_t *ret); -int seccomp_add_secondary_archs(scmp_filter_ctx *c); +int seccomp_init_conservative(scmp_filter_ctx *ret, uint32_t default_action); -typedef struct SystemCallFilterSet { - const char *set_name; +int seccomp_add_secondary_archs(scmp_filter_ctx c); + +bool is_seccomp_available(void); + +typedef struct SyscallFilterSet { + const char *name; const char *value; -} SystemCallFilterSet; +} SyscallFilterSet; + +enum { + SYSCALL_FILTER_SET_BASIC_IO, + SYSCALL_FILTER_SET_CLOCK, + SYSCALL_FILTER_SET_CPU_EMULATION, + SYSCALL_FILTER_SET_DEBUG, + SYSCALL_FILTER_SET_DEFAULT, + SYSCALL_FILTER_SET_IO_EVENT, + SYSCALL_FILTER_SET_IPC, + SYSCALL_FILTER_SET_KEYRING, + SYSCALL_FILTER_SET_MODULE, + SYSCALL_FILTER_SET_MOUNT, + SYSCALL_FILTER_SET_NETWORK_IO, + SYSCALL_FILTER_SET_OBSOLETE, + SYSCALL_FILTER_SET_PRIVILEGED, + SYSCALL_FILTER_SET_PROCESS, + SYSCALL_FILTER_SET_RAW_IO, + SYSCALL_FILTER_SET_RESOURCES, + _SYSCALL_FILTER_SET_MAX +}; + +extern const SyscallFilterSet syscall_filter_sets[]; + +const SyscallFilterSet *syscall_filter_set_find(const char *name); + +int seccomp_add_syscall_filter_set(scmp_filter_ctx seccomp, const SyscallFilterSet *set, uint32_t action); -extern const SystemCallFilterSet syscall_filter_sets[]; +int seccomp_load_filter_set(uint32_t default_action, const SyscallFilterSet *set, uint32_t action); diff --git a/src/shared/sleep-config.c b/src/shared/sleep-config.c index f00624d0f2..ed31a80c8d 100644 --- a/src/shared/sleep-config.c +++ b/src/shared/sleep-config.c @@ -58,7 +58,7 @@ int parse_sleep_config(const char *verb, char ***_modes, char ***_states) { {} }; - config_parse_many(PKGSYSCONFDIR "/sleep.conf", + config_parse_many_nulstr(PKGSYSCONFDIR "/sleep.conf", CONF_PATHS_NULSTR("systemd/sleep.conf.d"), "Sleep\0", config_item_table_lookup, items, false, NULL); diff --git a/src/shared/switch-root.c b/src/shared/switch-root.c index 47d3a5a1fa..4eff4f692e 100644 --- a/src/shared/switch-root.c +++ b/src/shared/switch-root.c @@ -75,17 +75,29 @@ int switch_root(const char *new_root, const char *oldroot, bool detach_oldroot, NULSTR_FOREACH(i, move_mounts) { char new_mount[PATH_MAX]; struct stat sb; + size_t n; - xsprintf(new_mount, "%s%s", new_root, i); + n = snprintf(new_mount, sizeof new_mount, "%s%s", new_root, i); + if (n >= sizeof new_mount) { + bool move = mountflags & MS_MOVE; + + log_warning("New path is too long, %s: %s%s", + move ? "forcing unmount instead" : "ignoring", + new_root, i); + + if (move) + if (umount2(i, MNT_FORCE) < 0) + log_warning_errno(errno, "Failed to unmount %s: %m", i); + continue; + } mkdir_p_label(new_mount, 0755); - if ((stat(new_mount, &sb) < 0) || + if (stat(new_mount, &sb) < 0 || sb.st_dev != new_root_stat.st_dev) { /* Mount point seems to be mounted already or - * stat failed. Unmount the old mount - * point. */ + * stat failed. Unmount the old mount point. */ if (umount2(i, MNT_DETACH) < 0) log_warning_errno(errno, "Failed to unmount %s: %m", i); continue; @@ -97,10 +109,9 @@ int switch_root(const char *new_root, const char *oldroot, bool detach_oldroot, if (umount2(i, MNT_FORCE) < 0) log_warning_errno(errno, "Failed to unmount %s: %m", i); - } - if (mountflags & MS_BIND) - log_error_errno(errno, "Failed to bind mount %s to %s: %m", i, new_mount); + } else if (mountflags & MS_BIND) + log_error_errno(errno, "Failed to bind mount %s to %s: %m", i, new_mount); } } diff --git a/src/sysctl/sysctl.c b/src/sysctl/sysctl.c index ce7c26e7d3..b3587e249d 100644 --- a/src/sysctl/sysctl.c +++ b/src/sysctl/sysctl.c @@ -41,29 +41,56 @@ static char **arg_prefixes = NULL; static const char conf_file_dirs[] = CONF_PATHS_NULSTR("sysctl.d"); -static int apply_all(Hashmap *sysctl_options) { +static int apply_all(OrderedHashmap *sysctl_options) { char *property, *value; Iterator i; int r = 0; - HASHMAP_FOREACH_KEY(value, property, sysctl_options, i) { + ORDERED_HASHMAP_FOREACH_KEY(value, property, sysctl_options, i) { int k; k = sysctl_write(property, value); if (k < 0) { - log_full_errno(k == -ENOENT ? LOG_INFO : LOG_WARNING, k, - "Couldn't write '%s' to '%s', ignoring: %m", value, property); - - if (r == 0 && k != -ENOENT) - r = k; + /* If the sysctl is not available in the kernel or we are running with reduced privileges and + * cannot write it, then log about the issue at LOG_NOTICE level, and proceed without + * failing. (EROFS is treated as a permission problem here, since that's how container managers + * usually protected their sysctls.) In all other cases log an error and make the tool fail. */ + + if (IN_SET(k, -EPERM, -EACCES, -EROFS, -ENOENT)) + log_notice_errno(k, "Couldn't write '%s' to '%s', ignoring: %m", value, property); + else { + log_error_errno(k, "Couldn't write '%s' to '%s': %m", value, property); + if (r == 0) + r = k; + } } } return r; } -static int parse_file(Hashmap *sysctl_options, const char *path, bool ignore_enoent) { +static bool test_prefix(const char *p) { + char **i; + + if (strv_isempty(arg_prefixes)) + return true; + + STRV_FOREACH(i, arg_prefixes) { + const char *t; + + t = path_startswith(*i, "/proc/sys/"); + if (!t) + t = *i; + if (path_startswith(p, t)) + return true; + } + + return false; +} + +static int parse_file(OrderedHashmap *sysctl_options, const char *path, bool ignore_enoent) { _cleanup_fclose_ FILE *f = NULL; + unsigned c = 0; int r; assert(path); @@ -77,7 +104,7 @@ static int parse_file(Hashmap *sysctl_options, const char *path, bool ignore_eno } log_debug("Parsing %s", path); - while (!feof(f)) { + for (;;) { char l[LINE_MAX], *p, *value, *new_value, *property, *existing; void *v; int k; @@ -89,6 +116,8 @@ static int parse_file(Hashmap *sysctl_options, const char *path, bool ignore_eno return log_error_errno(errno, "Failed to read file '%s', ignoring: %m", path); } + c++; + p = strstrip(l); if (!*p) continue; @@ -98,7 +127,7 @@ static int parse_file(Hashmap *sysctl_options, const char *path, bool ignore_eno value = strchr(p, '='); if (!value) { - log_error("Line is not an assignment in file '%s': %s", path, value); + log_error("Line is not an assignment at '%s:%u': %s", path, c, value); if (r == 0) r = -EINVAL; @@ -111,27 +140,16 @@ static int parse_file(Hashmap *sysctl_options, const char *path, bool ignore_eno p = sysctl_normalize(strstrip(p)); value = strstrip(value); - if (!strv_isempty(arg_prefixes)) { - char **i, *t; - STRV_FOREACH(i, arg_prefixes) { - t = path_startswith(*i, "/proc/sys/"); - if (t == NULL) - t = *i; - if (path_startswith(p, t)) - goto found; - } - /* not found */ + if (!test_prefix(p)) continue; - } -found: - existing = hashmap_get2(sysctl_options, p, &v); + existing = ordered_hashmap_get2(sysctl_options, p, &v); if (existing) { if (streq(value, existing)) continue; - log_debug("Overwriting earlier assignment of %s in file '%s'.", p, path); - free(hashmap_remove(sysctl_options, p)); + log_debug("Overwriting earlier assignment of %s at '%s:%u'.", p, path, c); + free(ordered_hashmap_remove(sysctl_options, p)); free(v); } @@ -145,7 +163,7 @@ found: return log_oom(); } - k = hashmap_put(sysctl_options, property, new_value); + k = ordered_hashmap_put(sysctl_options, property, new_value); if (k < 0) { log_error_errno(k, "Failed to add sysctl variable %s to hashmap: %m", property); free(property); @@ -229,12 +247,12 @@ static int parse_argv(int argc, char *argv[]) { } int main(int argc, char *argv[]) { + OrderedHashmap *sysctl_options = NULL; int r = 0, k; - Hashmap *sysctl_options; r = parse_argv(argc, argv); if (r <= 0) - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + goto finish; log_set_target(LOG_TARGET_AUTO); log_parse_environment(); @@ -242,7 +260,7 @@ int main(int argc, char *argv[]) { umask(0022); - sysctl_options = hashmap_new(&string_hash_ops); + sysctl_options = ordered_hashmap_new(&string_hash_ops); if (!sysctl_options) { r = log_oom(); goto finish; @@ -280,7 +298,7 @@ int main(int argc, char *argv[]) { r = k; finish: - hashmap_free_free_free(sysctl_options); + ordered_hashmap_free_free_free(sysctl_options); strv_free(arg_prefixes); return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 114c4f1703..dd3b931cd6 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -118,6 +118,7 @@ static enum dependency { } arg_dependency = DEPENDENCY_FORWARD; static const char *arg_job_mode = "replace"; static UnitFileScope arg_scope = UNIT_FILE_SYSTEM; +static bool arg_wait = false; static bool arg_no_block = false; static bool arg_no_legend = false; static bool arg_no_pager = false; @@ -188,6 +189,11 @@ typedef enum BusFocus { static sd_bus *busses[_BUS_FOCUS_MAX] = {}; +static UnitFileFlags args_to_flags(void) { + return (arg_runtime ? UNIT_FILE_RUNTIME : 0) | + (arg_force ? UNIT_FILE_FORCE : 0); +} + static int acquire_bus(BusFocus focus, sd_bus **ret) { int r; @@ -361,22 +367,24 @@ static int compare_unit_info(const void *a, const void *b) { return strcasecmp(u->id, v->id); } +static const char* unit_type_suffix(const char *name) { + const char *dot; + + dot = strrchr(name, '.'); + if (!dot) + return ""; + + return dot + 1; +} + static bool output_show_unit(const UnitInfo *u, char **patterns) { assert(u); if (!strv_fnmatch_or_empty(patterns, u->id, FNM_NOESCAPE)) return false; - if (arg_types) { - const char *dot; - - dot = strrchr(u->id, '.'); - if (!dot) - return false; - - if (!strv_find(arg_types, dot+1)) - return false; - } + if (arg_types && !strv_find(arg_types, unit_type_suffix(u->id))) + return false; if (arg_all) return true; @@ -402,7 +410,7 @@ static bool output_show_unit(const UnitInfo *u, char **patterns) { } static int output_units_list(const UnitInfo *unit_infos, unsigned c) { - unsigned circle_len = 0, id_len, max_id_len, load_len, active_len, sub_len, job_len, desc_len; + unsigned circle_len = 0, id_len, max_id_len, load_len, active_len, sub_len, job_len, desc_len, max_desc_len; const UnitInfo *u; unsigned n_shown = 0; int job_count = 0; @@ -412,13 +420,14 @@ static int output_units_list(const UnitInfo *unit_infos, unsigned c) { active_len = strlen("ACTIVE"); sub_len = strlen("SUB"); job_len = strlen("JOB"); - desc_len = 0; + max_desc_len = strlen("DESCRIPTION"); for (u = unit_infos; u < unit_infos + c; u++) { max_id_len = MAX(max_id_len, strlen(u->id) + (u->machine ? strlen(u->machine)+1 : 0)); load_len = MAX(load_len, strlen(u->load_state)); active_len = MAX(active_len, strlen(u->active_state)); sub_len = MAX(sub_len, strlen(u->sub_state)); + max_desc_len = MAX(max_desc_len, strlen(u->description)); if (u->job_id != 0) { job_len = MAX(job_len, strlen(u->job_type)); @@ -434,7 +443,7 @@ static int output_units_list(const UnitInfo *unit_infos, unsigned c) { if (!arg_full && original_stdout_is_tty) { unsigned basic_len; - id_len = MIN(max_id_len, 25u); + id_len = MIN(max_id_len, 25u); /* as much as it needs, but at most 25 for now */ basic_len = circle_len + 5 + id_len + 5 + active_len + sub_len; if (job_count) @@ -447,34 +456,38 @@ static int output_units_list(const UnitInfo *unit_infos, unsigned c) { /* Either UNIT already got 25, or is fully satisfied. * Grant up to 25 to DESC now. */ incr = MIN(extra_len, 25u); - desc_len += incr; + desc_len = incr; extra_len -= incr; - /* split the remaining space between UNIT and DESC, - * but do not give UNIT more than it needs. */ + /* Of the remainder give as much as the ID needs to the ID, and give the rest to the + * description but not more than it needs. */ if (extra_len > 0) { - incr = MIN(extra_len / 2, max_id_len - id_len); + incr = MIN(max_id_len - id_len, extra_len); id_len += incr; - desc_len += extra_len - incr; + desc_len += MIN(extra_len - incr, max_desc_len - desc_len); } } - } else + } else { id_len = max_id_len; + desc_len = max_desc_len; + } for (u = unit_infos; u < unit_infos + c; u++) { _cleanup_free_ char *e = NULL, *j = NULL; + const char *on_underline = "", *off_underline = ""; const char *on_loaded = "", *off_loaded = ""; const char *on_active = "", *off_active = ""; const char *on_circle = "", *off_circle = ""; const char *id; - bool circle = false; + bool circle = false, underline = false; if (!n_shown && !arg_no_legend) { if (circle_len > 0) fputs(" ", stdout); - printf("%-*s %-*s %-*s %-*s ", + printf("%s%-*s %-*s %-*s %-*s ", + ansi_underline(), id_len, "UNIT", load_len, "LOAD", active_len, "ACTIVE", @@ -483,23 +496,34 @@ static int output_units_list(const UnitInfo *unit_infos, unsigned c) { if (job_count) printf("%-*s ", job_len, "JOB"); - if (!arg_full && arg_no_pager) - printf("%.*s\n", desc_len, "DESCRIPTION"); - else - printf("%s\n", "DESCRIPTION"); + printf("%-*.*s%s\n", + desc_len, + !arg_full && arg_no_pager ? (int) desc_len : -1, + "DESCRIPTION", + ansi_normal()); } n_shown++; + if (u + 1 < unit_infos + c && + !streq(unit_type_suffix(u->id), unit_type_suffix((u + 1)->id))) { + on_underline = ansi_underline(); + off_underline = ansi_normal(); + underline = true; + } + if (STR_IN_SET(u->load_state, "error", "not-found", "masked") && !arg_plain) { - on_loaded = ansi_highlight_red(); on_circle = ansi_highlight_yellow(); - off_loaded = off_circle = ansi_normal(); + off_circle = ansi_normal(); circle = true; + on_loaded = underline ? ansi_highlight_red_underline() : ansi_highlight_red(); + off_loaded = underline ? on_underline : ansi_normal(); } else if (streq(u->active_state, "failed") && !arg_plain) { - on_circle = on_active = ansi_highlight_red(); - off_circle = off_active = ansi_normal(); + on_circle = ansi_highlight_red(); + off_circle = ansi_normal(); circle = true; + on_active = underline ? ansi_highlight_red_underline() : ansi_highlight_red(); + off_active = underline ? on_underline : ansi_normal(); } if (u->machine) { @@ -522,17 +546,19 @@ static int output_units_list(const UnitInfo *unit_infos, unsigned c) { if (circle_len > 0) printf("%s%s%s ", on_circle, circle ? special_glyph(BLACK_CIRCLE) : " ", off_circle); - printf("%s%-*s%s %s%-*s%s %s%-*s %-*s%s %-*s", + printf("%s%s%-*s%s %s%-*s%s %s%-*s %-*s%s %-*s", + on_underline, on_active, id_len, id, off_active, on_loaded, load_len, u->load_state, off_loaded, on_active, active_len, u->active_state, sub_len, u->sub_state, off_active, job_count ? job_len + 1 : 0, u->job_id ? u->job_type : ""); - if (desc_len > 0) - printf("%.*s\n", desc_len, u->description); - else - printf("%s\n", u->description); + printf("%-*.*s%s\n", + desc_len, + !arg_full && arg_no_pager ? (int) desc_len : -1, + u->description, + off_underline); } if (!arg_no_legend) { @@ -1394,35 +1420,46 @@ static void output_unit_file_list(const UnitFileList *units, unsigned c) { id_cols = max_id_len; if (!arg_no_legend && c > 0) - printf("%-*s %-*s\n", + printf("%s%-*s %-*s%s\n", + ansi_underline(), id_cols, "UNIT FILE", - state_cols, "STATE"); + state_cols, "STATE", + ansi_normal()); for (u = units; u < units + c; u++) { _cleanup_free_ char *e = NULL; - const char *on, *off; + const char *on, *off, *on_underline = "", *off_underline = ""; const char *id; + bool underline = false; + + if (u + 1 < units + c && + !streq(unit_type_suffix(u->path), unit_type_suffix((u + 1)->path))) { + on_underline = ansi_underline(); + off_underline = ansi_normal(); + underline = true; + } if (IN_SET(u->state, UNIT_FILE_MASKED, UNIT_FILE_MASKED_RUNTIME, UNIT_FILE_DISABLED, - UNIT_FILE_BAD)) { - on = ansi_highlight_red(); - off = ansi_normal(); - } else if (u->state == UNIT_FILE_ENABLED) { - on = ansi_highlight_green(); - off = ansi_normal(); - } else - on = off = ""; + UNIT_FILE_BAD)) + on = underline ? ansi_highlight_red_underline() : ansi_highlight_red(); + else if (u->state == UNIT_FILE_ENABLED) + on = underline ? ansi_highlight_green_underline() : ansi_highlight_green(); + else + on = on_underline; + off = off_underline; id = basename(u->path); e = arg_full ? NULL : ellipsize(id, id_cols, 33); - printf("%-*s %s%-*s%s\n", + printf("%s%-*s %s%-*s%s%s\n", + on_underline, id_cols, e ? e : id, - on, state_cols, unit_file_state_to_string(u->state), off); + on, state_cols, unit_file_state_to_string(u->state), off, + off_underline); } if (!arg_no_legend) @@ -2110,7 +2147,7 @@ static int set_default(int argc, char *argv[], void *userdata) { return log_error_errno(r, "Failed to mangle unit name: %m"); if (install_client_side()) { - r = unit_file_set_default(arg_scope, arg_root, unit, true, &changes, &n_changes); + r = unit_file_set_default(arg_scope, UNIT_FILE_FORCE, arg_root, unit, &changes, &n_changes); unit_file_dump_changes(r, "set default", changes, n_changes, arg_quiet); if (r > 0) @@ -2679,13 +2716,92 @@ static const char *method_to_verb(const char *method) { return "n/a"; } +typedef struct { + sd_bus_slot *match; + sd_event *event; + Set *unit_paths; + bool any_failed; +} WaitContext; + +static void wait_context_free(WaitContext *c) { + c->match = sd_bus_slot_unref(c->match); + c->event = sd_event_unref(c->event); + c->unit_paths = set_free_free(c->unit_paths); +} + +static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error *error) { + WaitContext *c = userdata; + const char *path; + int r; + + path = sd_bus_message_get_path(m); + if (!set_contains(c->unit_paths, path)) + return 0; + + /* Check if ActiveState changed to inactive/failed */ + /* (s interface, a{sv} changed_properties, as invalidated_properties) */ + r = sd_bus_message_skip(m, "s"); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "{sv}"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) { + const char *s; + + r = sd_bus_message_read(m, "s", &s); + if (r < 0) + return bus_log_parse_error(r); + + if (streq(s, "ActiveState")) { + bool is_failed; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, "s"); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(m, "s", &s); + if (r < 0) + return bus_log_parse_error(r); + + is_failed = streq(s, "failed"); + if (streq(s, "inactive") || is_failed) { + log_debug("%s became %s, dropping from --wait tracking", path, s); + free(set_remove(c->unit_paths, path)); + c->any_failed = c->any_failed || is_failed; + } else + log_debug("ActiveState on %s changed to %s", path, s); + + break; /* no need to dissect the rest of the message */ + } else { + /* other property */ + r = sd_bus_message_skip(m, "v"); + if (r < 0) + return bus_log_parse_error(r); + } + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + } + if (r < 0) + return bus_log_parse_error(r); + + if (set_isempty(c->unit_paths)) + sd_event_exit(c->event, EXIT_SUCCESS); + + return 0; +} + static int start_unit_one( sd_bus *bus, const char *method, const char *name, const char *mode, sd_bus_error *error, - BusWaitForJobs *w) { + BusWaitForJobs *w, + WaitContext *wait_context) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; const char *path; @@ -2696,6 +2812,40 @@ static int start_unit_one( assert(mode); assert(error); + if (wait_context) { + _cleanup_free_ char *unit_path = NULL; + const char* mt; + + log_debug("Watching for property changes of %s", name); + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "RefUnit", + error, + NULL, + "s", name); + if (r < 0) + return log_error_errno(r, "Failed to RefUnit %s: %s", name, bus_error_message(error, r)); + + unit_path = unit_dbus_path_from_name(name); + if (!unit_path) + return log_oom(); + + r = set_put_strdup(wait_context->unit_paths, unit_path); + if (r < 0) + return log_error_errno(r, "Failed to add unit path %s to set: %m", unit_path); + + mt = strjoina("type='signal'," + "interface='org.freedesktop.DBus.Properties'," + "path='", unit_path, "'," + "member='PropertiesChanged'"); + r = sd_bus_add_match(bus, &wait_context->match, mt, on_properties_changed, wait_context); + if (r < 0) + return log_error_errno(r, "Failed to add match for PropertiesChanged signal: %m"); + } + log_debug("Calling manager for %s on %s, %s", method, name, mode); r = sd_bus_call_method( @@ -2720,9 +2870,10 @@ static int start_unit_one( if (!sd_bus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT) && !sd_bus_error_has_name(error, BUS_ERROR_UNIT_MASKED)) - log_error("See %s logs and 'systemctl%s status %s' for details.", + log_error("See %s logs and 'systemctl%s status%s %s' for details.", arg_scope == UNIT_FILE_SYSTEM ? "system" : "user", arg_scope == UNIT_FILE_SYSTEM ? "" : " --user", + name[0] == '-' ? " --" : "", name); return r; @@ -2840,10 +2991,18 @@ static int start_unit(int argc, char *argv[], void *userdata) { const char *method, *mode, *one_name, *suffix = NULL; _cleanup_strv_free_ char **names = NULL; sd_bus *bus; + _cleanup_(wait_context_free) WaitContext wait_context = {}; char **name; int r = 0; - r = acquire_bus(BUS_MANAGER, &bus); + if (arg_wait && !strstr(argv[0], "start")) { + log_error("--wait may only be used with a command that starts units."); + return -EINVAL; + } + + /* we cannot do sender tracking on the private bus, so we need the full + * one for RefUnit to implement --wait */ + r = acquire_bus(arg_wait ? BUS_FULL : BUS_MANAGER, &bus); if (r < 0) return r; @@ -2887,11 +3046,36 @@ static int start_unit(int argc, char *argv[], void *userdata) { return log_error_errno(r, "Could not watch jobs: %m"); } + if (arg_wait) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + + wait_context.unit_paths = set_new(&string_hash_ops); + if (!wait_context.unit_paths) + return log_oom(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "Subscribe", + &error, + NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to enable subscription: %s", bus_error_message(&error, r)); + r = sd_event_default(&wait_context.event); + if (r < 0) + return log_error_errno(r, "Failed to allocate event loop: %m"); + r = sd_bus_attach_event(bus, wait_context.event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + } + STRV_FOREACH(name, names) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int q; - q = start_unit_one(bus, method, *name, mode, &error, w); + q = start_unit_one(bus, method, *name, mode, &error, w, arg_wait ? &wait_context : NULL); if (r >= 0 && q < 0) r = translate_bus_error_to_exit_status(q, &error); } @@ -2923,6 +3107,15 @@ static int start_unit(int argc, char *argv[], void *userdata) { check_triggering_units(bus, *name); } + if (r >= 0 && arg_wait) { + int q; + q = sd_event_loop(wait_context.event); + if (q < 0) + return log_error_errno(q, "Failed to run event loop: %m"); + if (wait_context.any_failed) + r = EXIT_FAILURE; + } + return r; } @@ -3120,7 +3313,7 @@ static int logind_check_inhibitors(enum action a) { if (sd_session_get_class(*s, &class) < 0 || !streq(class, "user")) continue; - if (sd_session_get_type(*s, &type) < 0 || (!streq(type, "x11") && !streq(type, "tty"))) + if (sd_session_get_type(*s, &type) < 0 || !STR_IN_SET(type, "x11", "tty")) continue; sd_session_get_tty(*s, &tty); @@ -3384,9 +3577,9 @@ static int kill_unit(int argc, char *argv[], void *userdata) { "KillUnit", &error, NULL, - "ssi", *names, kill_who ? kill_who : arg_kill_who, arg_signal); + "ssi", *name, kill_who ? kill_who : arg_kill_who, arg_signal); if (q < 0) { - log_error_errno(q, "Failed to kill unit %s: %s", *names, bus_error_message(&error, q)); + log_error_errno(q, "Failed to kill unit %s: %s", *name, bus_error_message(&error, q)); if (r == 0) r = q; } @@ -3571,6 +3764,7 @@ typedef struct UnitStatusInfo { uint64_t memory_low; uint64_t memory_high; uint64_t memory_max; + uint64_t memory_swap_max; uint64_t memory_limit; uint64_t cpu_usage_nsec; uint64_t tasks_current; @@ -3620,7 +3814,7 @@ static void print_status_info( if (streq_ptr(i->active_state, "failed")) { active_on = ansi_highlight_red(); active_off = ansi_normal(); - } else if (streq_ptr(i->active_state, "active") || streq_ptr(i->active_state, "reloading")) { + } else if (STRPTR_IN_SET(i->active_state, "active", "reloading")) { active_on = ansi_highlight_green(); active_off = ansi_normal(); } else @@ -3701,12 +3895,10 @@ static void print_status_info( if (!isempty(i->result) && !streq(i->result, "success")) printf(" (Result: %s)", i->result); - timestamp = (streq_ptr(i->active_state, "active") || - streq_ptr(i->active_state, "reloading")) ? i->active_enter_timestamp : - (streq_ptr(i->active_state, "inactive") || - streq_ptr(i->active_state, "failed")) ? i->inactive_enter_timestamp : - streq_ptr(i->active_state, "activating") ? i->inactive_exit_timestamp : - i->active_exit_timestamp; + timestamp = STRPTR_IN_SET(i->active_state, "active", "reloading") ? i->active_enter_timestamp : + STRPTR_IN_SET(i->active_state, "inactive", "failed") ? i->inactive_enter_timestamp : + STRPTR_IN_SET(i->active_state, "activating") ? i->inactive_exit_timestamp : + i->active_exit_timestamp; s1 = format_timestamp_relative(since1, sizeof(since1), timestamp); s2 = format_timestamp(since2, sizeof(since2), timestamp); @@ -3786,7 +3978,7 @@ static void print_status_info( argv = strv_join(p->argv, " "); printf(" Process: "PID_FMT" %s=%s ", p->pid, p->name, strna(argv)); - good = is_clean_exit_lsb(p->code, p->status, NULL); + good = is_clean_exit(p->code, p->status, EXIT_CLEAN_DAEMON, NULL); if (!good) { on = ansi_highlight_red(); off = ansi_normal(); @@ -3883,7 +4075,8 @@ static void print_status_info( printf(" Memory: %s", format_bytes(buf, sizeof(buf), i->memory_current)); - if (i->memory_low > 0 || i->memory_high != CGROUP_LIMIT_MAX || i->memory_max != CGROUP_LIMIT_MAX || + if (i->memory_low > 0 || i->memory_high != CGROUP_LIMIT_MAX || + i->memory_max != CGROUP_LIMIT_MAX || i->memory_swap_max != CGROUP_LIMIT_MAX || i->memory_limit != CGROUP_LIMIT_MAX) { const char *prefix = ""; @@ -3900,6 +4093,10 @@ static void print_status_info( printf("%smax: %s", prefix, format_bytes(buf, sizeof(buf), i->memory_max)); prefix = " "; } + if (i->memory_swap_max != CGROUP_LIMIT_MAX) { + printf("%sswap max: %s", prefix, format_bytes(buf, sizeof(buf), i->memory_swap_max)); + prefix = " "; + } if (i->memory_limit != CGROUP_LIMIT_MAX) { printf("%slimit: %s", prefix, format_bytes(buf, sizeof(buf), i->memory_limit)); prefix = " "; @@ -4140,6 +4337,8 @@ static int status_property(const char *name, sd_bus_message *m, UnitStatusInfo * i->memory_high = u; else if (streq(name, "MemoryMax")) i->memory_max = u; + else if (streq(name, "MemorySwapMax")) + i->memory_swap_max = u; else if (streq(name, "MemoryLimit")) i->memory_limit = u; else if (streq(name, "TasksCurrent")) @@ -4574,7 +4773,8 @@ static int print_property(const char *name, sd_bus_message *m, const char *conte return 0; - } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && (streq(name, "IODeviceWeight") || streq(name, "BlockIODeviceWeight"))) { + } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && + STR_IN_SET(name, "IODeviceWeight", "BlockIODeviceWeight")) { const char *path; uint64_t weight; @@ -4593,8 +4793,9 @@ static int print_property(const char *name, sd_bus_message *m, const char *conte return 0; - } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && (cgroup_io_limit_type_from_string(name) >= 0 || - streq(name, "BlockIOReadBandwidth") || streq(name, "BlockIOWriteBandwidth"))) { + } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && + (cgroup_io_limit_type_from_string(name) >= 0 || + STR_IN_SET(name, "BlockIOReadBandwidth", "BlockIOWriteBandwidth"))) { const char *path; uint64_t bandwidth; @@ -4655,6 +4856,7 @@ static int show_one( .memory_current = (uint64_t) -1, .memory_high = CGROUP_LIMIT_MAX, .memory_max = CGROUP_LIMIT_MAX, + .memory_swap_max = CGROUP_LIMIT_MAX, .memory_limit = (uint64_t) -1, .cpu_usage_nsec = (uint64_t) -1, .tasks_current = (uint64_t) -1, @@ -4770,7 +4972,7 @@ static int show_one( else if (streq(verb, "status")) { print_status_info(bus, &info, ellipsized); - if (info.active_state && STR_IN_SET(info.active_state, "inactive", "failed")) + if (info.active_state && !STR_IN_SET(info.active_state, "active", "reloading")) r = EXIT_PROGRAM_NOT_RUNNING; else r = EXIT_PROGRAM_RUNNING_OR_SERVICE_OK; @@ -5075,6 +5277,20 @@ static int cat(int argc, char *argv[], void *userdata) { else puts(""); + if (need_daemon_reload(bus, *name) > 0) /* ignore errors (<0), this is informational output */ + fprintf(stderr, + "%s# Warning: %s changed on disk, the version systemd has loaded is outdated.\n" + "%s# This output shows the current version of the unit's original fragment and drop-in files.\n" + "%s# If fragments or drop-ins were added or removed, they are not properly reflected in this output.\n" + "%s# Run 'systemctl%s daemon-reload' to reload units.%s\n", + ansi_highlight_red(), + *name, + ansi_highlight_red(), + ansi_highlight_red(), + ansi_highlight_red(), + arg_scope == UNIT_FILE_SYSTEM ? "" : " --user", + ansi_normal()); + if (fragment_path) { r = cat_file(fragment_path, false); if (r < 0) @@ -5096,7 +5312,6 @@ static int set_property(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_free_ char *n = NULL; sd_bus *bus; - char **i; int r; r = acquire_bus(BUS_MANAGER, &bus); @@ -5127,11 +5342,9 @@ static int set_property(int argc, char *argv[], void *userdata) { if (r < 0) return bus_log_create_error(r); - STRV_FOREACH(i, strv_skip(argv, 2)) { - r = bus_append_unit_property_assignment(m, *i); - if (r < 0) - return r; - } + r = bus_append_unit_property_assignment_many(m, strv_skip(argv, 2)); + if (r < 0) + return r; r = sd_bus_message_close_container(m); if (r < 0) @@ -5569,10 +5782,12 @@ static int enable_sysv_units(const char *verb, char **args) { if (!found_sysv) continue; - if (found_native) - log_info("Synchronizing state of %s with SysV service script with %s.", name, argv[0]); - else - log_info("%s is not a native service, redirecting to systemd-sysv-install.", name); + if (!arg_quiet) { + if (found_native) + log_info("Synchronizing state of %s with SysV service script with %s.", name, argv[0]); + else + log_info("%s is not a native service, redirecting to systemd-sysv-install.", name); + } if (!isempty(arg_root)) argv[c++] = q = strappend("--root=", arg_root); @@ -5673,6 +5888,29 @@ static int mangle_names(char **original_names, char ***mangled_names) { return 0; } +static int normalize_names(char **names, bool warn_if_path) { + char **u; + bool was_path = false; + + STRV_FOREACH(u, names) { + int r; + + if (!is_path(*u)) + continue; + + r = free_and_strdup(u, basename(*u)); + if (r < 0) + return log_error_errno(r, "Failed to normalize unit file path: %m"); + + was_path = true; + } + + if (warn_if_path && was_path) + log_warning("Warning: Can't execute disable on the unit file path. Proceeding with the unit name."); + + return 0; +} + static int unit_exists(const char *unit) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -5740,23 +5978,32 @@ static int enable_unit(int argc, char *argv[], void *userdata) { return daemon_reload(argc, argv, userdata); } + if (streq(verb, "disable")) { + r = normalize_names(names, true); + if (r < 0) + return r; + } + if (install_client_side()) { + UnitFileFlags flags; + + flags = args_to_flags(); if (streq(verb, "enable")) { - r = unit_file_enable(arg_scope, arg_runtime, arg_root, names, arg_force, &changes, &n_changes); + r = unit_file_enable(arg_scope, flags, arg_root, names, &changes, &n_changes); carries_install_info = r; } else if (streq(verb, "disable")) - r = unit_file_disable(arg_scope, arg_runtime, arg_root, names, &changes, &n_changes); + r = unit_file_disable(arg_scope, flags, arg_root, names, &changes, &n_changes); else if (streq(verb, "reenable")) { - r = unit_file_reenable(arg_scope, arg_runtime, arg_root, names, arg_force, &changes, &n_changes); + r = unit_file_reenable(arg_scope, flags, arg_root, names, &changes, &n_changes); carries_install_info = r; } else if (streq(verb, "link")) - r = unit_file_link(arg_scope, arg_runtime, arg_root, names, arg_force, &changes, &n_changes); + r = unit_file_link(arg_scope, flags, arg_root, names, &changes, &n_changes); else if (streq(verb, "preset")) { - r = unit_file_preset(arg_scope, arg_runtime, arg_root, names, arg_preset_mode, arg_force, &changes, &n_changes); + r = unit_file_preset(arg_scope, flags, arg_root, names, arg_preset_mode, &changes, &n_changes); } else if (streq(verb, "mask")) - r = unit_file_mask(arg_scope, arg_runtime, arg_root, names, arg_force, &changes, &n_changes); + r = unit_file_mask(arg_scope, flags, arg_root, names, &changes, &n_changes); else if (streq(verb, "unmask")) - r = unit_file_unmask(arg_scope, arg_runtime, arg_root, names, &changes, &n_changes); + r = unit_file_unmask(arg_scope, flags, arg_root, names, &changes, &n_changes); else if (streq(verb, "revert")) r = unit_file_revert(arg_scope, arg_root, names, &changes, &n_changes); else @@ -5938,7 +6185,7 @@ static int add_dependency(int argc, char *argv[], void *userdata) { assert_not_reached("Unknown verb"); if (install_client_side()) { - r = unit_file_add_dependency(arg_scope, arg_runtime, arg_root, names, target, dep, arg_force, &changes, &n_changes); + r = unit_file_add_dependency(arg_scope, args_to_flags(), arg_root, names, target, dep, &changes, &n_changes); unit_file_dump_changes(r, "add dependency on", changes, n_changes, arg_quiet); if (r > 0) @@ -6000,7 +6247,7 @@ static int preset_all(int argc, char *argv[], void *userdata) { int r; if (install_client_side()) { - r = unit_file_preset_all(arg_scope, arg_runtime, arg_root, arg_preset_mode, arg_force, &changes, &n_changes); + r = unit_file_preset_all(arg_scope, args_to_flags(), arg_root, arg_preset_mode, &changes, &n_changes); unit_file_dump_changes(r, "preset", changes, n_changes, arg_quiet); if (r > 0) @@ -6049,6 +6296,63 @@ finish: return r; } +static int show_installation_targets_client_side(const char *name) { + UnitFileChange *changes = NULL; + unsigned n_changes = 0, i; + UnitFileFlags flags; + char **p; + int r; + + p = STRV_MAKE(name); + flags = UNIT_FILE_DRY_RUN | + (arg_runtime ? UNIT_FILE_RUNTIME : 0); + + r = unit_file_disable(UNIT_FILE_SYSTEM, flags, NULL, p, &changes, &n_changes); + if (r < 0) + return log_error_errno(r, "Failed to get file links for %s: %m", name); + + for (i = 0; i < n_changes; i++) + if (changes[i].type == UNIT_FILE_UNLINK) + printf(" %s\n", changes[i].path); + + return 0; +} + +static int show_installation_targets(sd_bus *bus, const char *name) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *link; + int r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetUnitFileLinks", + &error, + &reply, + "sb", name, arg_runtime); + if (r < 0) + return log_error_errno(r, "Failed to get unit file links for %s: %s", name, bus_error_message(&error, r)); + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "s"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "s", &link)) > 0) + printf(" %s\n", link); + + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + return 0; +} + static int unit_is_enabled(int argc, char *argv[], void *userdata) { _cleanup_strv_free_ char **names = NULL; @@ -6067,7 +6371,6 @@ static int unit_is_enabled(int argc, char *argv[], void *userdata) { enabled = r > 0; if (install_client_side()) { - STRV_FOREACH(name, names) { UnitFileState state; @@ -6083,8 +6386,14 @@ static int unit_is_enabled(int argc, char *argv[], void *userdata) { UNIT_FILE_GENERATED)) enabled = true; - if (!arg_quiet) + if (!arg_quiet) { puts(unit_file_state_to_string(state)); + if (arg_full) { + r = show_installation_targets_client_side(*name); + if (r < 0) + return r; + } + } } r = 0; @@ -6119,8 +6428,14 @@ static int unit_is_enabled(int argc, char *argv[], void *userdata) { if (STR_IN_SET(s, "enabled", "enabled-runtime", "static", "indirect", "generated")) enabled = true; - if (!arg_quiet) + if (!arg_quiet) { puts(s); + if (arg_full) { + r = show_installation_targets(bus, *name); + if (r < 0) + return r; + } + } } } @@ -6533,9 +6848,9 @@ static void systemctl_help(void) { " -t --type=TYPE List units of a particular type\n" " --state=STATE List units with particular LOAD or SUB or ACTIVE state\n" " -p --property=NAME Show only properties by this name\n" - " -a --all Show all loaded units/properties, including dead/empty\n" - " ones. To list all units installed on the system, use\n" - " the 'list-unit-files' command instead.\n" + " -a --all Show all properties/all units currently in memory,\n" + " including dead/empty ones. To list all units installed on\n" + " the system, use the 'list-unit-files' command instead.\n" " -l --full Don't ellipsize unit names on output\n" " -r --recursive Show unit list of host and local containers\n" " --reverse Show reverse dependencies with 'list-dependencies'\n" @@ -6549,6 +6864,7 @@ static void systemctl_help(void) { " -s --signal=SIGNAL Which signal to send\n" " --now Start or stop unit in addition to enabling or disabling it\n" " -q --quiet Suppress output\n" + " --wait For (re)start, wait until service stopped again\n" " --no-block Do not wait until operation finished\n" " --no-wall Don't send wall message before halt/power-off/reboot\n" " --no-reload Don't reload daemon after en-/dis-abling unit files\n" @@ -6563,15 +6879,17 @@ static void systemctl_help(void) { " --preset-mode= Apply only enable, only disable, or all presets\n" " --root=PATH Enable unit files in the specified root directory\n" " -n --lines=INTEGER Number of journal entries to show\n" - " -o --output=STRING Change journal output mode (short, short-iso,\n" - " short-precise, short-monotonic, verbose,\n" - " export, json, json-pretty, json-sse, cat)\n" + " -o --output=STRING Change journal output mode (short, short-precise,\n" + " short-iso, short-full, short-monotonic, short-unix,\n" + " verbose, export, json, json-pretty, json-sse, cat)\n" " --firmware-setup Tell the firmware to show the setup menu on next boot\n" " --plain Print unit dependencies as a list instead of a tree\n\n" "Unit Commands:\n" - " list-units [PATTERN...] List loaded units\n" - " list-sockets [PATTERN...] List loaded sockets ordered by address\n" - " list-timers [PATTERN...] List loaded timers ordered by next elapse\n" + " list-units [PATTERN...] List units currently in memory\n" + " list-sockets [PATTERN...] List socket units currently in memory, ordered\n" + " by address\n" + " list-timers [PATTERN...] List timer units currently in memory, ordered\n" + " by next elapse\n" " start NAME... Start (activate) one or more units\n" " stop NAME... Stop (deactivate) one or more units\n" " reload NAME... Reload one or more units\n" @@ -6819,6 +7137,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { ARG_FIRMWARE_SETUP, ARG_NOW, ARG_MESSAGE, + ARG_WAIT, }; static const struct option options[] = { @@ -6842,6 +7161,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { { "user", no_argument, NULL, ARG_USER }, { "system", no_argument, NULL, ARG_SYSTEM }, { "global", no_argument, NULL, ARG_GLOBAL }, + { "wait", no_argument, NULL, ARG_WAIT }, { "no-block", no_argument, NULL, ARG_NO_BLOCK }, { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, { "no-pager", no_argument, NULL, ARG_NO_PAGER }, @@ -7022,6 +7342,10 @@ static int systemctl_parse_argv(int argc, char *argv[]) { arg_scope = UNIT_FILE_GLOBAL; break; + case ARG_WAIT: + arg_wait = true; + break; + case ARG_NO_BLOCK: arg_no_block = true; break; @@ -7197,6 +7521,11 @@ static int systemctl_parse_argv(int argc, char *argv[]) { return -EINVAL; } + if (arg_wait && arg_no_block) { + log_error("--wait may not be combined with --no-block."); + return -EINVAL; + } + return 1; } diff --git a/src/systemd/sd-bus.h b/src/systemd/sd-bus.h index 295989cd69..c47459c9ad 100644 --- a/src/systemd/sd-bus.h +++ b/src/systemd/sd-bus.h @@ -147,6 +147,8 @@ int sd_bus_can_send(sd_bus *bus, char type); int sd_bus_get_creds_mask(sd_bus *bus, uint64_t *creds_mask); int sd_bus_set_allow_interactive_authorization(sd_bus *bus, int b); int sd_bus_get_allow_interactive_authorization(sd_bus *bus); +int sd_bus_set_exit_on_disconnect(sd_bus *bus, int b); +int sd_bus_get_exit_on_disconnect(sd_bus *bus); int sd_bus_start(sd_bus *ret); @@ -438,8 +440,14 @@ int sd_bus_track_remove_sender(sd_bus_track *track, sd_bus_message *m); int sd_bus_track_add_name(sd_bus_track *track, const char *name); int sd_bus_track_remove_name(sd_bus_track *track, const char *name); +int sd_bus_track_set_recursive(sd_bus_track *track, int b); +int sd_bus_track_get_recursive(sd_bus_track *track); + unsigned sd_bus_track_count(sd_bus_track *track); -const char* sd_bus_track_contains(sd_bus_track *track, const char *names); +int sd_bus_track_count_sender(sd_bus_track *track, sd_bus_message *m); +int sd_bus_track_count_name(sd_bus_track *track, const char *name); + +const char* sd_bus_track_contains(sd_bus_track *track, const char *name); const char* sd_bus_track_first(sd_bus_track *track); const char* sd_bus_track_next(sd_bus_track *track); diff --git a/src/systemd/sd-id128.h b/src/systemd/sd-id128.h index 4dff0b9b81..ee011b1861 100644 --- a/src/systemd/sd-id128.h +++ b/src/systemd/sd-id128.h @@ -45,8 +45,8 @@ int sd_id128_from_string(const char *s, sd_id128_t *ret); int sd_id128_randomize(sd_id128_t *ret); int sd_id128_get_machine(sd_id128_t *ret); - int sd_id128_get_boot(sd_id128_t *ret); +int sd_id128_get_invocation(sd_id128_t *ret); #define SD_ID128_MAKE(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) \ ((const sd_id128_t) { .bytes = { 0x##v0, 0x##v1, 0x##v2, 0x##v3, 0x##v4, 0x##v5, 0x##v6, 0x##v7, \ diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h index 3c44d63021..79246ae060 100644 --- a/src/systemd/sd-messages.h +++ b/src/systemd/sd-messages.h @@ -40,6 +40,7 @@ _SD_BEGIN_DECLARATIONS; #define SD_MESSAGE_JOURNAL_USAGE SD_ID128_MAKE(ec,38,7f,57,7b,84,4b,8f,a9,48,f3,3c,ad,9a,75,e6) #define SD_MESSAGE_COREDUMP SD_ID128_MAKE(fc,2e,22,bc,6e,e6,47,b6,b9,07,29,ab,34,a2,50,b1) +#define SD_MESSAGE_TRUNCATED_CORE SD_ID128_MAKE(5a,ad,d8,e9,54,dc,4b,1a,8c,95,4d,63,fd,9e,11,37) #define SD_MESSAGE_SESSION_START SD_ID128_MAKE(8d,45,62,0c,1a,43,48,db,b1,74,10,da,57,c6,0c,66) #define SD_MESSAGE_SESSION_STOP SD_ID128_MAKE(33,54,93,94,24,b4,45,6d,98,02,ca,83,33,ed,42,4a) diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index 787d68a009..0684f58fcd 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -190,7 +190,8 @@ static int load_group_database(void) { static int make_backup(const char *target, const char *x) { _cleanup_close_ int src = -1; _cleanup_fclose_ FILE *dst = NULL; - char *backup, *temp; + _cleanup_free_ char *temp = NULL; + char *backup; struct timespec ts[2]; struct stat st; int r; @@ -1189,6 +1190,7 @@ static void item_free(Item *i) { free(i->uid_path); free(i->gid_path); free(i->description); + free(i->home); free(i); } @@ -1299,81 +1301,6 @@ static bool item_equal(Item *a, Item *b) { return true; } -static bool valid_user_group_name(const char *u) { - const char *i; - long sz; - - if (isempty(u)) - return false; - - if (!(u[0] >= 'a' && u[0] <= 'z') && - !(u[0] >= 'A' && u[0] <= 'Z') && - u[0] != '_') - return false; - - for (i = u+1; *i; i++) { - if (!(*i >= 'a' && *i <= 'z') && - !(*i >= 'A' && *i <= 'Z') && - !(*i >= '0' && *i <= '9') && - *i != '_' && - *i != '-') - return false; - } - - sz = sysconf(_SC_LOGIN_NAME_MAX); - assert_se(sz > 0); - - if ((size_t) (i-u) > (size_t) sz) - return false; - - if ((size_t) (i-u) > UT_NAMESIZE - 1) - return false; - - return true; -} - -static bool valid_gecos(const char *d) { - - if (!d) - return false; - - if (!utf8_is_valid(d)) - return false; - - if (string_has_cc(d, NULL)) - return false; - - /* Colons are used as field separators, and hence not OK */ - if (strchr(d, ':')) - return false; - - return true; -} - -static bool valid_home(const char *p) { - - if (isempty(p)) - return false; - - if (!utf8_is_valid(p)) - return false; - - if (string_has_cc(p, NULL)) - return false; - - if (!path_is_absolute(p)) - return false; - - if (!path_is_safe(p)) - return false; - - /* Colons are used as field separators, and hence not OK */ - if (strchr(p, ':')) - return false; - - return true; -} - static int parse_line(const char *fname, unsigned line, const char *buffer) { static const Specifier specifier_table[] = { diff --git a/src/sysv-generator/sysv-generator.c b/src/sysv-generator/sysv-generator.c index 3ed8f23ff9..c2c80175a2 100644 --- a/src/sysv-generator/sysv-generator.c +++ b/src/sysv-generator/sysv-generator.c @@ -25,6 +25,7 @@ #include "alloc-util.h" #include "dirent-util.h" +#include "exit-status.h" #include "fd-util.h" #include "fileio.h" #include "hashmap.h" @@ -199,6 +200,13 @@ static int generate_unit_file(SysvStub *s) { if (s->pid_file) fprintf(f, "PIDFile=%s\n", s->pid_file); + /* Consider two special LSB exit codes a clean exit */ + if (s->has_lsb) + fprintf(f, + "SuccessExitStatus=%i %i\n", + EXIT_NOTINSTALLED, + EXIT_NOTCONFIGURED); + fprintf(f, "ExecStart=%s start\n" "ExecStop=%s stop\n", @@ -247,7 +255,7 @@ static char *sysv_translate_name(const char *name) { return res; } -static int sysv_translate_facility(const char *name, const char *filename, char **ret) { +static int sysv_translate_facility(SysvStub *s, unsigned line, const char *name, char **ret) { /* We silently ignore the $ prefix here. According to the LSB * spec it simply indicates whether something is a @@ -266,15 +274,18 @@ static int sysv_translate_facility(const char *name, const char *filename, char "time", SPECIAL_TIME_SYNC_TARGET, }; + const char *filename; char *filename_no_sh, *e, *m; const char *n; unsigned i; int r; assert(name); - assert(filename); + assert(s); assert(ret); + filename = basename(s->path); + n = *name == '$' ? name + 1 : name; for (i = 0; i < ELEMENTSOF(table); i += 2) { @@ -299,7 +310,7 @@ static int sysv_translate_facility(const char *name, const char *filename, char if (*name == '$') { r = unit_name_build(n, NULL, ".target", ret); if (r < 0) - return log_error_errno(r, "Failed to build name: %m"); + return log_error_errno(r, "[%s:%u] Could not build name for facility %s: %m", s->path, line, name); return r; } @@ -337,11 +348,11 @@ static int handle_provides(SysvStub *s, unsigned line, const char *full_text, co r = extract_first_word(&text, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); if (r < 0) - return log_error_errno(r, "Failed to parse word from provides string: %m"); + return log_error_errno(r, "[%s:%u] Failed to parse word from provides string: %m", s->path, line); if (r == 0) break; - r = sysv_translate_facility(word, basename(s->path), &m); + r = sysv_translate_facility(s, line, word, &m); if (r <= 0) /* continue on error */ continue; @@ -403,11 +414,11 @@ static int handle_dependencies(SysvStub *s, unsigned line, const char *full_text r = extract_first_word(&text, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); if (r < 0) - return log_error_errno(r, "Failed to parse word from provides string: %m"); + return log_error_errno(r, "[%s:%u] Failed to parse word from provides string: %m", s->path, line); if (r == 0) break; - r = sysv_translate_facility(word, basename(s->path), &m); + r = sysv_translate_facility(s, line, word, &m); if (r <= 0) /* continue on error */ continue; diff --git a/src/test/test-acl-util.c b/src/test/test-acl-util.c index 430dda8e78..5b572bb0bf 100644 --- a/src/test/test-acl-util.c +++ b/src/test/test-acl-util.c @@ -35,7 +35,7 @@ static void test_add_acls_for_user(void) { uid_t uid; int r; - fd = mkostemp_safe(fn, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(fn); assert_se(fd >= 0); /* Use the mode that user journal files use */ diff --git a/src/test/test-async.c b/src/test/test-async.c index ada6d67c42..4ebc27f0bd 100644 --- a/src/test/test-async.c +++ b/src/test/test-async.c @@ -36,7 +36,7 @@ int main(int argc, char *argv[]) { int fd; char name[] = "/tmp/test-asynchronous_close.XXXXXX"; - fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(name); assert_se(fd >= 0); asynchronous_close(fd); diff --git a/src/test/test-calendarspec.c b/src/test/test-calendarspec.c index 4a2b93de59..59217b131c 100644 --- a/src/test/test-calendarspec.c +++ b/src/test/test-calendarspec.c @@ -73,7 +73,7 @@ static void test_next(const char *input, const char *new_tz, usec_t after, usec_ u = after; r = calendar_spec_next_usec(c, after, &u); - printf("At: %s\n", r < 0 ? strerror(-r) : format_timestamp_us(buf, sizeof(buf), u)); + printf("At: %s\n", r < 0 ? strerror(-r) : format_timestamp_us(buf, sizeof buf, u)); if (expect != (usec_t)-1) assert_se(r >= 0 && u == expect); else @@ -88,6 +88,51 @@ static void test_next(const char *input, const char *new_tz, usec_t after, usec_ tzset(); } +static void test_timestamp(void) { + char buf[FORMAT_TIMESTAMP_MAX]; + _cleanup_free_ char *t = NULL; + CalendarSpec *c; + usec_t x, y; + + /* Ensure that a timestamp is also a valid calendar specification. Convert forth and back */ + + x = now(CLOCK_REALTIME); + + assert_se(format_timestamp_us(buf, sizeof(buf), x)); + printf("%s\n", buf); + assert_se(calendar_spec_from_string(buf, &c) >= 0); + assert_se(calendar_spec_to_string(c, &t) >= 0); + calendar_spec_free(c); + printf("%s\n", t); + + assert_se(parse_timestamp(t, &y) >= 0); + assert_se(y == x); +} + +static void test_hourly_bug_4031(void) { + CalendarSpec *c; + usec_t n, u, w; + char buf[FORMAT_TIMESTAMP_MAX], zaf[FORMAT_TIMESTAMP_MAX]; + int r; + + assert_se(calendar_spec_from_string("hourly", &c) >= 0); + n = now(CLOCK_REALTIME); + assert_se((r = calendar_spec_next_usec(c, n, &u)) >= 0); + + printf("Now: %s (%"PRIu64")\n", format_timestamp_us(buf, sizeof buf, n), n); + printf("Next hourly: %s (%"PRIu64")\n", r < 0 ? strerror(-r) : format_timestamp_us(buf, sizeof buf, u), u); + + assert_se((r = calendar_spec_next_usec(c, u, &w)) >= 0); + printf("Next hourly: %s (%"PRIu64")\n", r < 0 ? strerror(-r) : format_timestamp_us(zaf, sizeof zaf, w), w); + + assert_se(n < u); + assert_se(u <= n + USEC_PER_HOUR); + assert_se(u < w); + assert_se(w <= u + USEC_PER_HOUR); + + calendar_spec_free(c); +} + int main(int argc, char* argv[]) { CalendarSpec *c; @@ -155,5 +200,8 @@ int main(int argc, char* argv[]) { assert_se(calendar_spec_from_string("00:00:00/0.00000001", &c) < 0); assert_se(calendar_spec_from_string("00:00:00.0..00.9", &c) < 0); + test_timestamp(); + test_hourly_bug_4031(); + return 0; } diff --git a/src/test/test-cgroup-util.c b/src/test/test-cgroup-util.c index 43f8906172..c24c784e9b 100644 --- a/src/test/test-cgroup-util.c +++ b/src/test/test-cgroup-util.c @@ -24,6 +24,7 @@ #include "formats-util.h" #include "parse-util.h" #include "process-util.h" +#include "stat-util.h" #include "string-util.h" #include "test-helper.h" #include "user-util.h" @@ -309,6 +310,28 @@ static void test_mask_supported(void) { printf("'%s' is supported: %s\n", cgroup_controller_to_string(c), yes_no(m & CGROUP_CONTROLLER_TO_MASK(c))); } +static void test_is_cgroup_fs(void) { + struct statfs sfs; + assert_se(statfs("/sys/fs/cgroup", &sfs) == 0); + if (is_temporary_fs(&sfs)) + assert_se(statfs("/sys/fs/cgroup/systemd", &sfs) == 0); + assert_se(is_cgroup_fs(&sfs)); +} + +static void test_fd_is_cgroup_fs(void) { + int fd; + + fd = open("/sys/fs/cgroup", O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + assert_se(fd >= 0); + if (fd_is_temporary_fs(fd)) { + fd = safe_close(fd); + fd = open("/sys/fs/cgroup/systemd", O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + assert_se(fd >= 0); + } + assert_se(fd_is_cgroup_fs(fd)); + fd = safe_close(fd); +} + int main(void) { test_path_decode_unit(); test_path_get_unit(); @@ -324,6 +347,8 @@ int main(void) { test_slice_to_path(); test_shift_path(); TEST_REQ_RUNNING_SYSTEMD(test_mask_supported()); + TEST_REQ_RUNNING_SYSTEMD(test_is_cgroup_fs()); + TEST_REQ_RUNNING_SYSTEMD(test_fd_is_cgroup_fs()); return 0; } diff --git a/src/test/test-clock.c b/src/test/test-clock.c index 84f775e5bc..7d97328416 100644 --- a/src/test/test-clock.c +++ b/src/test/test-clock.c @@ -55,7 +55,7 @@ static void test_clock_is_localtime(void) { /* without an adjtime file we default to UTC */ assert_se(clock_is_localtime("/nonexisting/adjtime") == 0); - fd = mkostemp_safe(adjtime, O_WRONLY|O_CLOEXEC); + fd = mkostemp_safe(adjtime); assert_se(fd >= 0); log_info("adjtime test file: %s", adjtime); f = fdopen(fd, "w"); diff --git a/src/test/test-condition.c b/src/test/test-condition.c index 987862f1c6..dd985f5863 100644 --- a/src/test/test-condition.c +++ b/src/test/test-condition.c @@ -25,77 +25,96 @@ #include "audit-util.h" #include "condition.h" #include "hostname-util.h" +#include "id128-util.h" #include "ima-util.h" #include "log.h" #include "macro.h" #include "selinux-util.h" #include "smack-util.h" +#include "strv.h" +#include "virt.h" #include "util.h" static void test_condition_test_path(void) { Condition *condition; condition = condition_new(CONDITION_PATH_EXISTS, "/bin/sh", false, false); + assert_se(condition); assert_se(condition_test(condition)); condition_free(condition); condition = condition_new(CONDITION_PATH_EXISTS, "/bin/s?", false, false); + assert_se(condition); assert_se(!condition_test(condition)); condition_free(condition); condition = condition_new(CONDITION_PATH_EXISTS_GLOB, "/bin/s?", false, false); + assert_se(condition); assert_se(condition_test(condition)); condition_free(condition); condition = condition_new(CONDITION_PATH_EXISTS_GLOB, "/bin/s?", false, true); + assert_se(condition); assert_se(!condition_test(condition)); condition_free(condition); condition = condition_new(CONDITION_PATH_EXISTS, "/thiscertainlywontexist", false, false); + assert_se(condition); assert_se(!condition_test(condition)); condition_free(condition); condition = condition_new(CONDITION_PATH_EXISTS, "/thiscertainlywontexist", false, true); + assert_se(condition); assert_se(condition_test(condition)); condition_free(condition); condition = condition_new(CONDITION_PATH_IS_DIRECTORY, "/bin", false, false); + assert_se(condition); assert_se(condition_test(condition)); condition_free(condition); condition = condition_new(CONDITION_DIRECTORY_NOT_EMPTY, "/bin", false, false); + assert_se(condition); assert_se(condition_test(condition)); condition_free(condition); condition = condition_new(CONDITION_FILE_NOT_EMPTY, "/bin/sh", false, false); + assert_se(condition); assert_se(condition_test(condition)); condition_free(condition); condition = condition_new(CONDITION_FILE_IS_EXECUTABLE, "/bin/sh", false, false); + assert_se(condition); assert_se(condition_test(condition)); condition_free(condition); condition = condition_new(CONDITION_FILE_IS_EXECUTABLE, "/etc/passwd", false, false); + assert_se(condition); assert_se(!condition_test(condition)); condition_free(condition); condition = condition_new(CONDITION_PATH_IS_MOUNT_POINT, "/proc", false, false); + assert_se(condition); assert_se(condition_test(condition)); condition_free(condition); condition = condition_new(CONDITION_PATH_IS_MOUNT_POINT, "/", false, false); + assert_se(condition); assert_se(condition_test(condition)); condition_free(condition); condition = condition_new(CONDITION_PATH_IS_MOUNT_POINT, "/bin", false, false); + assert_se(condition); assert_se(!condition_test(condition)); condition_free(condition); condition = condition_new(CONDITION_PATH_IS_READ_WRITE, "/tmp", false, false); + assert_se(condition); assert_se(condition_test(condition)); condition_free(condition); condition = condition_new(CONDITION_PATH_IS_SYMBOLIC_LINK, "/dev/stdout", false, false); + assert_se(condition); assert_se(condition_test(condition)); condition_free(condition); } @@ -104,47 +123,59 @@ static void test_condition_test_ac_power(void) { Condition *condition; condition = condition_new(CONDITION_AC_POWER, "true", false, false); + assert_se(condition); assert_se(condition_test(condition) == on_ac_power()); condition_free(condition); condition = condition_new(CONDITION_AC_POWER, "false", false, false); + assert_se(condition); assert_se(condition_test(condition) != on_ac_power()); condition_free(condition); condition = condition_new(CONDITION_AC_POWER, "false", false, true); + assert_se(condition); assert_se(condition_test(condition) == on_ac_power()); condition_free(condition); } static void test_condition_test_host(void) { + _cleanup_free_ char *hostname = NULL; + char sid[SD_ID128_STRING_MAX]; Condition *condition; sd_id128_t id; int r; - char sid[SD_ID128_STRING_MAX]; - _cleanup_free_ char *hostname = NULL; r = sd_id128_get_machine(&id); assert_se(r >= 0); assert_se(sd_id128_to_string(id, sid)); condition = condition_new(CONDITION_HOST, sid, false, false); + assert_se(condition); assert_se(condition_test(condition)); condition_free(condition); condition = condition_new(CONDITION_HOST, "garbage value jjjjjjjjjjjjjj", false, false); + assert_se(condition); assert_se(!condition_test(condition)); condition_free(condition); condition = condition_new(CONDITION_HOST, sid, false, true); + assert_se(condition); assert_se(!condition_test(condition)); condition_free(condition); hostname = gethostname_malloc(); assert_se(hostname); - condition = condition_new(CONDITION_HOST, hostname, false, false); - assert_se(condition_test(condition)); - condition_free(condition); + /* if hostname looks like an id128 then skip testing it */ + if (id128_is_valid(hostname)) + log_notice("hostname is an id128, skipping test"); + else { + condition = condition_new(CONDITION_HOST, hostname, false, false); + assert_se(condition); + assert_se(condition_test(condition)); + condition_free(condition); + } } static void test_condition_test_architecture(void) { @@ -159,14 +190,17 @@ static void test_condition_test_architecture(void) { assert_se(sa); condition = condition_new(CONDITION_ARCHITECTURE, sa, false, false); + assert_se(condition); assert_se(condition_test(condition) > 0); condition_free(condition); condition = condition_new(CONDITION_ARCHITECTURE, "garbage value", false, false); + assert_se(condition); assert_se(condition_test(condition) == 0); condition_free(condition); condition = condition_new(CONDITION_ARCHITECTURE, sa, false, true); + assert_se(condition); assert_se(condition_test(condition) == 0); condition_free(condition); } @@ -175,10 +209,12 @@ static void test_condition_test_kernel_command_line(void) { Condition *condition; condition = condition_new(CONDITION_KERNEL_COMMAND_LINE, "thisreallyshouldntbeonthekernelcommandline", false, false); + assert_se(condition); assert_se(!condition_test(condition)); condition_free(condition); condition = condition_new(CONDITION_KERNEL_COMMAND_LINE, "andthis=neither", false, false); + assert_se(condition); assert_se(!condition_test(condition)); condition_free(condition); } @@ -187,10 +223,12 @@ static void test_condition_test_null(void) { Condition *condition; condition = condition_new(CONDITION_NULL, NULL, false, false); + assert_se(condition); assert_se(condition_test(condition)); condition_free(condition); condition = condition_new(CONDITION_NULL, NULL, false, true); + assert_se(condition); assert_se(!condition_test(condition)); condition_free(condition); } @@ -199,32 +237,94 @@ static void test_condition_test_security(void) { Condition *condition; condition = condition_new(CONDITION_SECURITY, "garbage oifdsjfoidsjoj", false, false); + assert_se(condition); assert_se(!condition_test(condition)); condition_free(condition); condition = condition_new(CONDITION_SECURITY, "selinux", false, true); + assert_se(condition); assert_se(condition_test(condition) != mac_selinux_have()); condition_free(condition); condition = condition_new(CONDITION_SECURITY, "ima", false, false); + assert_se(condition); assert_se(condition_test(condition) == use_ima()); condition_free(condition); condition = condition_new(CONDITION_SECURITY, "apparmor", false, false); + assert_se(condition); assert_se(condition_test(condition) == mac_apparmor_use()); condition_free(condition); condition = condition_new(CONDITION_SECURITY, "smack", false, false); + assert_se(condition); assert_se(condition_test(condition) == mac_smack_use()); condition_free(condition); condition = condition_new(CONDITION_SECURITY, "audit", false, false); + assert_se(condition); assert_se(condition_test(condition) == use_audit()); condition_free(condition); } +static void test_condition_test_virtualization(void) { + Condition *condition; + const char *virt; + int r; + + condition = condition_new(CONDITION_VIRTUALIZATION, "garbage oifdsjfoidsjoj", false, false); + assert_se(condition); + r = condition_test(condition); + log_info("ConditionVirtualization=garbage → %i", r); + assert_se(r == 0); + condition_free(condition); + + condition = condition_new(CONDITION_VIRTUALIZATION, "container", false, false); + assert_se(condition); + r = condition_test(condition); + log_info("ConditionVirtualization=container → %i", r); + assert_se(r == !!detect_container()); + condition_free(condition); + + condition = condition_new(CONDITION_VIRTUALIZATION, "vm", false, false); + assert_se(condition); + r = condition_test(condition); + log_info("ConditionVirtualization=vm → %i", r); + assert_se(r == (detect_vm() && !detect_container())); + condition_free(condition); + + condition = condition_new(CONDITION_VIRTUALIZATION, "private-users", false, false); + assert_se(condition); + r = condition_test(condition); + log_info("ConditionVirtualization=private-users → %i", r); + assert_se(r == !!running_in_userns()); + condition_free(condition); + + NULSTR_FOREACH(virt, + "kvm\0" + "qemu\0" + "bochs\0" + "xen\0" + "uml\0" + "vmware\0" + "oracle\0" + "microsoft\0" + "zvm\0" + "parallels\0" + "bhyve\0" + "vm_other\0") { + + condition = condition_new(CONDITION_VIRTUALIZATION, virt, false, false); + assert_se(condition); + r = condition_test(condition); + log_info("ConditionVirtualization=%s → %i", virt, r); + assert_se(r >= 0); + condition_free(condition); + } +} int main(int argc, char *argv[]) { + log_set_max_level(LOG_DEBUG); log_parse_environment(); log_open(); @@ -235,6 +335,7 @@ int main(int argc, char *argv[]) { test_condition_test_kernel_command_line(); test_condition_test_null(); test_condition_test_security(); + test_condition_test_virtualization(); return 0; } diff --git a/src/test/test-copy.c b/src/test/test-copy.c index 68154fc4e8..ed1ea51dbd 100644 --- a/src/test/test-copy.c +++ b/src/test/test-copy.c @@ -42,11 +42,11 @@ static void test_copy_file(void) { log_info("%s", __func__); - fd = mkostemp_safe(fn, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(fn); assert_se(fd >= 0); close(fd); - fd = mkostemp_safe(fn_copy, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(fn_copy); assert_se(fd >= 0); close(fd); @@ -71,9 +71,9 @@ static void test_copy_file_fd(void) { log_info("%s", __func__); - in_fd = mkostemp_safe(in_fn, O_RDWR); + in_fd = mkostemp_safe(in_fn); assert_se(in_fd >= 0); - out_fd = mkostemp_safe(out_fn, O_RDWR); + out_fd = mkostemp_safe(out_fn); assert_se(out_fd >= 0); assert_se(write_string_file(in_fn, text, WRITE_STRING_FILE_CREATE) == 0); @@ -207,10 +207,10 @@ static void test_copy_bytes_regular_file(const char *src, bool try_reflink, uint fd = open(src, O_RDONLY | O_CLOEXEC | O_NOCTTY); assert_se(fd >= 0); - fd2 = mkostemp_safe(fn2, O_RDWR); + fd2 = mkostemp_safe(fn2); assert_se(fd2 >= 0); - fd3 = mkostemp_safe(fn3, O_WRONLY); + fd3 = mkostemp_safe(fn3); assert_se(fd3 >= 0); r = copy_bytes(fd, fd2, max_bytes, try_reflink); diff --git a/src/test/test-dns-domain.c b/src/test/test-dns-domain.c index a9d09f59bc..e2f097c95e 100644 --- a/src/test/test-dns-domain.c +++ b/src/test/test-dns-domain.c @@ -48,6 +48,7 @@ static void test_dns_label_unescape(void) { test_dns_label_unescape_one("..", "", 20, -EINVAL); test_dns_label_unescape_one(".foobar", "", 20, -EINVAL); test_dns_label_unescape_one("foobar.", "foobar", 20, 6); + test_dns_label_unescape_one("foobar..", "foobar", 20, -EINVAL); } static void test_dns_name_to_wire_format_one(const char *what, const char *expect, size_t buffer_sz, int ret) { @@ -359,6 +360,7 @@ static void test_dns_name_is_valid_one(const char *s, int ret) { static void test_dns_name_is_valid(void) { test_dns_name_is_valid_one("foo", 1); test_dns_name_is_valid_one("foo.", 1); + test_dns_name_is_valid_one("foo..", 0); test_dns_name_is_valid_one("Foo", 1); test_dns_name_is_valid_one("foo.bar", 1); test_dns_name_is_valid_one("foo.bar.baz", 1); @@ -366,6 +368,7 @@ static void test_dns_name_is_valid(void) { test_dns_name_is_valid_one("foo..bar", 0); test_dns_name_is_valid_one(".foo.bar", 0); test_dns_name_is_valid_one("foo.bar.", 1); + test_dns_name_is_valid_one("foo.bar..", 0); test_dns_name_is_valid_one("\\zbar", 0); test_dns_name_is_valid_one("ä", 1); test_dns_name_is_valid_one("\n", 0); diff --git a/src/test/test-engine.c b/src/test/test-engine.c index 23da10fa1a..a651f6b683 100644 --- a/src/test/test-engine.c +++ b/src/test/test-engine.c @@ -43,7 +43,7 @@ int main(int argc, char *argv[]) { assert_se(set_unit_path(TEST_DIR) >= 0); r = manager_new(UNIT_FILE_USER, true, &m); if (MANAGER_SKIP_TEST(r)) { - printf("Skipping test: manager_new: %s\n", strerror(-r)); + log_notice_errno(r, "Skipping test: manager_new: %m"); return EXIT_TEST_SKIP; } assert_se(r >= 0); diff --git a/src/test/test-execute.c b/src/test/test-execute.c index 77ef4e8b2a..6029853e3e 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -30,9 +30,13 @@ #include "mkdir.h" #include "path-util.h" #include "rm-rf.h" +#ifdef HAVE_SECCOMP +#include "seccomp-util.h" +#endif #include "test-helper.h" #include "unit.h" #include "util.h" +#include "virt.h" typedef void (*test_function_t)(Manager *m); @@ -66,6 +70,24 @@ static void check(Manager *m, Unit *unit, int status_expected, int code_expected assert_se(service->main_exec_status.code == code_expected); } +static bool is_inaccessible_available(void) { + char *p; + + FOREACH_STRING(p, + "/run/systemd/inaccessible/reg", + "/run/systemd/inaccessible/dir", + "/run/systemd/inaccessible/chr", + "/run/systemd/inaccessible/blk", + "/run/systemd/inaccessible/fifo", + "/run/systemd/inaccessible/sock" + ) { + if (access(p, F_OK) < 0) + return false; + } + + return true; +} + static void test(Manager *m, const char *unit_name, int status_expected, int code_expected) { Unit *unit; @@ -91,6 +113,16 @@ static void test_exec_personality(Manager *m) { #elif defined(__s390__) test(m, "exec-personality-s390.service", 0, CLD_EXITED); +#elif defined(__powerpc64__) +# if __BYTE_ORDER == __BIG_ENDIAN + test(m, "exec-personality-ppc64.service", 0, CLD_EXITED); +# else + test(m, "exec-personality-ppc64le.service", 0, CLD_EXITED); +# endif + +#elif defined(__aarch64__) + test(m, "exec-personality-aarch64.service", 0, CLD_EXITED); + #elif defined(__i386__) test(m, "exec-personality-x86.service", 0, CLD_EXITED); #endif @@ -111,27 +143,86 @@ static void test_exec_privatetmp(Manager *m) { } static void test_exec_privatedevices(Manager *m) { + if (detect_container() > 0) { + log_notice("testing in container, skipping private device tests"); + return; + } + if (!is_inaccessible_available()) { + log_notice("testing without inaccessible, skipping private device tests"); + return; + } + test(m, "exec-privatedevices-yes.service", 0, CLD_EXITED); test(m, "exec-privatedevices-no.service", 0, CLD_EXITED); } +static void test_exec_privatedevices_capabilities(Manager *m) { + if (detect_container() > 0) { + log_notice("testing in container, skipping private device tests"); + return; + } + if (!is_inaccessible_available()) { + log_notice("testing without inaccessible, skipping private device tests"); + return; + } + + test(m, "exec-privatedevices-yes-capability-mknod.service", 0, CLD_EXITED); + test(m, "exec-privatedevices-no-capability-mknod.service", 0, CLD_EXITED); + test(m, "exec-privatedevices-yes-capability-sys-rawio.service", 0, CLD_EXITED); + test(m, "exec-privatedevices-no-capability-sys-rawio.service", 0, CLD_EXITED); +} + +static void test_exec_protectkernelmodules(Manager *m) { + if (detect_container() > 0) { + log_notice("testing in container, skipping protectkernelmodules tests"); + return; + } + if (!is_inaccessible_available()) { + log_notice("testing without inaccessible, skipping protectkernelmodules tests"); + return; + } + + test(m, "exec-protectkernelmodules-no-capabilities.service", 0, CLD_EXITED); + test(m, "exec-protectkernelmodules-yes-capabilities.service", 0, CLD_EXITED); + test(m, "exec-protectkernelmodules-yes-mount-propagation.service", 0, CLD_EXITED); +} + +static void test_exec_readonlypaths(Manager *m) { + test(m, "exec-readonlypaths.service", 0, CLD_EXITED); + test(m, "exec-readonlypaths-mount-propagation.service", 0, CLD_EXITED); +} + +static void test_exec_readwritepaths(Manager *m) { + test(m, "exec-readwritepaths-mount-propagation.service", 0, CLD_EXITED); +} + +static void test_exec_inaccessiblepaths(Manager *m) { + test(m, "exec-inaccessiblepaths-mount-propagation.service", 0, CLD_EXITED); +} + static void test_exec_systemcallfilter(Manager *m) { #ifdef HAVE_SECCOMP + if (!is_seccomp_available()) + return; test(m, "exec-systemcallfilter-not-failing.service", 0, CLD_EXITED); test(m, "exec-systemcallfilter-not-failing2.service", 0, CLD_EXITED); test(m, "exec-systemcallfilter-failing.service", SIGSYS, CLD_KILLED); test(m, "exec-systemcallfilter-failing2.service", SIGSYS, CLD_KILLED); + #endif } static void test_exec_systemcallerrornumber(Manager *m) { #ifdef HAVE_SECCOMP - test(m, "exec-systemcallerrornumber.service", 1, CLD_EXITED); + if (is_seccomp_available()) + test(m, "exec-systemcallerrornumber.service", 1, CLD_EXITED); #endif } static void test_exec_systemcall_system_mode_with_user(Manager *m) { #ifdef HAVE_SECCOMP + if (!is_seccomp_available()) + return; if (getpwnam("nobody")) test(m, "exec-systemcallfilter-system-user.service", 0, CLD_EXITED); else if (getpwnam("nfsnobody")) @@ -159,6 +250,21 @@ static void test_exec_group(Manager *m) { log_error_errno(errno, "Skipping test_exec_group, could not find nobody/nfsnobody group: %m"); } +static void test_exec_supplementary_groups(Manager *m) { + test(m, "exec-supplementarygroups.service", 0, CLD_EXITED); + test(m, "exec-supplementarygroups-single-group.service", 0, CLD_EXITED); + test(m, "exec-supplementarygroups-single-group-user.service", 0, CLD_EXITED); + test(m, "exec-supplementarygroups-multiple-groups-default-group-user.service", 0, CLD_EXITED); + test(m, "exec-supplementarygroups-multiple-groups-withgid.service", 0, CLD_EXITED); + test(m, "exec-supplementarygroups-multiple-groups-withuid.service", 0, CLD_EXITED); +} + +static void test_exec_dynamic_user(Manager *m) { + test(m, "exec-dynamicuser-fixeduser.service", 0, CLD_EXITED); + test(m, "exec-dynamicuser-fixeduser-one-supplementarygroup.service", 0, CLD_EXITED); + test(m, "exec-dynamicuser-supplementarygroups.service", 0, CLD_EXITED); +} + static void test_exec_environment(Manager *m) { test(m, "exec-environment.service", 0, CLD_EXITED); test(m, "exec-environment-multiple.service", 0, CLD_EXITED); @@ -300,7 +406,7 @@ static int run_tests(UnitFileScope scope, test_function_t *tests) { r = manager_new(scope, true, &m); if (MANAGER_SKIP_TEST(r)) { - printf("Skipping test: manager_new: %s\n", strerror(-r)); + log_notice_errno(r, "Skipping test: manager_new: %m"); return EXIT_TEST_SKIP; } assert_se(r >= 0); @@ -321,11 +427,18 @@ int main(int argc, char *argv[]) { test_exec_ignoresigpipe, test_exec_privatetmp, test_exec_privatedevices, + test_exec_privatedevices_capabilities, + test_exec_protectkernelmodules, + test_exec_readonlypaths, + test_exec_readwritepaths, + test_exec_inaccessiblepaths, test_exec_privatenetwork, test_exec_systemcallfilter, test_exec_systemcallerrornumber, test_exec_user, test_exec_group, + test_exec_supplementary_groups, + test_exec_dynamic_user, test_exec_environment, test_exec_environmentfile, test_exec_passenvironment, diff --git a/src/test/test-fd-util.c b/src/test/test-fd-util.c index 421d3bdeb3..f555bb976c 100644 --- a/src/test/test-fd-util.c +++ b/src/test/test-fd-util.c @@ -31,9 +31,9 @@ static void test_close_many(void) { char name1[] = "/tmp/test-close-many.XXXXXX"; char name2[] = "/tmp/test-close-many.XXXXXX"; - fds[0] = mkostemp_safe(name0, O_RDWR|O_CLOEXEC); - fds[1] = mkostemp_safe(name1, O_RDWR|O_CLOEXEC); - fds[2] = mkostemp_safe(name2, O_RDWR|O_CLOEXEC); + fds[0] = mkostemp_safe(name0); + fds[1] = mkostemp_safe(name1); + fds[2] = mkostemp_safe(name2); close_many(fds, 2); @@ -52,7 +52,7 @@ static void test_close_nointr(void) { char name[] = "/tmp/test-test-close_nointr.XXXXXX"; int fd; - fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(name); assert_se(fd >= 0); assert_se(close_nointr(fd) >= 0); assert_se(close_nointr(fd) < 0); diff --git a/src/test/test-fdset.c b/src/test/test-fdset.c index 282aab1246..adbf99a7ec 100644 --- a/src/test/test-fdset.c +++ b/src/test/test-fdset.c @@ -31,7 +31,7 @@ static void test_fdset_new_fill(void) { _cleanup_fdset_free_ FDSet *fdset = NULL; char name[] = "/tmp/test-fdset_new_fill.XXXXXX"; - fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(name); assert_se(fd >= 0); assert_se(fdset_new_fill(&fdset) >= 0); assert_se(fdset_contains(fdset, fd)); @@ -45,7 +45,7 @@ static void test_fdset_put_dup(void) { _cleanup_fdset_free_ FDSet *fdset = NULL; char name[] = "/tmp/test-fdset_put_dup.XXXXXX"; - fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(name); assert_se(fd >= 0); fdset = fdset_new(); @@ -64,7 +64,7 @@ static void test_fdset_cloexec(void) { int flags = -1; char name[] = "/tmp/test-fdset_cloexec.XXXXXX"; - fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(name); assert_se(fd >= 0); fdset = fdset_new(); @@ -91,7 +91,7 @@ static void test_fdset_close_others(void) { int flags = -1; char name[] = "/tmp/test-fdset_close_others.XXXXXX"; - fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(name); assert_se(fd >= 0); fdset = fdset_new(); @@ -113,7 +113,7 @@ static void test_fdset_remove(void) { FDSet *fdset = NULL; char name[] = "/tmp/test-fdset_remove.XXXXXX"; - fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(name); assert_se(fd >= 0); fdset = fdset_new(); @@ -136,7 +136,7 @@ static void test_fdset_iterate(void) { int c = 0; int a; - fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(name); assert_se(fd >= 0); fdset = fdset_new(); @@ -161,7 +161,7 @@ static void test_fdset_isempty(void) { _cleanup_fdset_free_ FDSet *fdset = NULL; char name[] = "/tmp/test-fdset_isempty.XXXXXX"; - fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(name); assert_se(fd >= 0); fdset = fdset_new(); @@ -179,7 +179,7 @@ static void test_fdset_steal_first(void) { _cleanup_fdset_free_ FDSet *fdset = NULL; char name[] = "/tmp/test-fdset_steal_first.XXXXXX"; - fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(name); assert_se(fd >= 0); fdset = fdset_new(); diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index 79609765e0..92663ef66f 100644 --- a/src/test/test-fileio.c +++ b/src/test/test-fileio.c @@ -45,11 +45,11 @@ static void test_parse_env_file(void) { char **i; unsigned k; - fd = mkostemp_safe(p, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(p); assert_se(fd >= 0); close(fd); - fd = mkostemp_safe(t, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(t); assert_se(fd >= 0); f = fdopen(fd, "w"); @@ -158,11 +158,11 @@ static void test_parse_multiline_env_file(void) { _cleanup_strv_free_ char **a = NULL, **b = NULL; char **i; - fd = mkostemp_safe(p, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(p); assert_se(fd >= 0); close(fd); - fd = mkostemp_safe(t, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(t); assert_se(fd >= 0); f = fdopen(fd, "w"); @@ -211,7 +211,7 @@ static void test_executable_is_script(void) { FILE *f; char *command; - fd = mkostemp_safe(t, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(t); assert_se(fd >= 0); f = fdopen(fd, "w"); @@ -300,7 +300,7 @@ static void test_write_string_stream(void) { int fd; char buf[64]; - fd = mkostemp_safe(fn, O_RDWR); + fd = mkostemp_safe(fn); assert_se(fd >= 0); f = fdopen(fd, "r"); @@ -334,7 +334,7 @@ static void test_write_string_file(void) { char buf[64] = {}; _cleanup_close_ int fd; - fd = mkostemp_safe(fn, O_RDWR); + fd = mkostemp_safe(fn); assert_se(fd >= 0); assert_se(write_string_file(fn, "boohoo", WRITE_STRING_FILE_CREATE) == 0); @@ -350,7 +350,7 @@ static void test_write_string_file_no_create(void) { _cleanup_close_ int fd; char buf[64] = {0}; - fd = mkostemp_safe(fn, O_RDWR); + fd = mkostemp_safe(fn); assert_se(fd >= 0); assert_se(write_string_file("/a/file/which/does/not/exists/i/guess", "boohoo", 0) < 0); @@ -390,7 +390,7 @@ static void test_load_env_file_pairs(void) { _cleanup_strv_free_ char **l = NULL; char **k, **v; - fd = mkostemp_safe(fn, O_RDWR); + fd = mkostemp_safe(fn); assert_se(fd >= 0); r = write_string_file(fn, @@ -433,7 +433,7 @@ static void test_search_and_fopen(void) { int r; FILE *f; - fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(name); assert_se(fd >= 0); close(fd); @@ -469,7 +469,7 @@ static void test_search_and_fopen_nulstr(void) { int r; FILE *f; - fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(name); assert_se(fd >= 0); close(fd); @@ -504,7 +504,7 @@ static void test_writing_tmpfile(void) { IOVEC_SET_STRING(iov[1], ALPHANUMERICAL "\n"); IOVEC_SET_STRING(iov[2], ""); - fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(name); printf("tmpfile: %s", name); r = writev(fd, iov, 3); diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index e0c040f39b..53a3cdc663 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -20,21 +20,114 @@ #include <unistd.h> #include "alloc-util.h" -#include "fileio.h" #include "fd-util.h" +#include "fileio.h" #include "fs-util.h" #include "macro.h" #include "mkdir.h" +#include "path-util.h" #include "rm-rf.h" #include "string-util.h" #include "strv.h" #include "util.h" +static void test_chase_symlinks(void) { + _cleanup_free_ char *result = NULL; + char temp[] = "/tmp/test-chase.XXXXXX"; + const char *top, *p, *q; + int r; + + assert_se(mkdtemp(temp)); + + top = strjoina(temp, "/top"); + assert_se(mkdir(top, 0700) >= 0); + + p = strjoina(top, "/dot"); + assert_se(symlink(".", p) >= 0); + + p = strjoina(top, "/dotdot"); + assert_se(symlink("..", p) >= 0); + + p = strjoina(top, "/dotdota"); + assert_se(symlink("../a", p) >= 0); + + p = strjoina(temp, "/a"); + assert_se(symlink("b", p) >= 0); + + p = strjoina(temp, "/b"); + assert_se(symlink("/usr", p) >= 0); + + p = strjoina(temp, "/start"); + assert_se(symlink("top/dot/dotdota", p) >= 0); + + r = chase_symlinks(p, NULL, &result); + assert_se(r >= 0); + assert_se(path_equal(result, "/usr")); + + result = mfree(result); + r = chase_symlinks(p, temp, &result); + assert_se(r == -ENOENT); + + q = strjoina(temp, "/usr"); + assert_se(mkdir(q, 0700) >= 0); + + r = chase_symlinks(p, temp, &result); + assert_se(r >= 0); + assert_se(path_equal(result, q)); + + p = strjoina(temp, "/slash"); + assert_se(symlink("/", p) >= 0); + + result = mfree(result); + r = chase_symlinks(p, NULL, &result); + assert_se(r >= 0); + assert_se(path_equal(result, "/")); + + result = mfree(result); + r = chase_symlinks(p, temp, &result); + assert_se(r >= 0); + assert_se(path_equal(result, temp)); + + p = strjoina(temp, "/slashslash"); + assert_se(symlink("///usr///", p) >= 0); + + result = mfree(result); + r = chase_symlinks(p, NULL, &result); + assert_se(r >= 0); + assert_se(path_equal(result, "/usr")); + + result = mfree(result); + r = chase_symlinks(p, temp, &result); + assert_se(r >= 0); + assert_se(path_equal(result, q)); + + result = mfree(result); + r = chase_symlinks("/etc/./.././", NULL, &result); + assert_se(r >= 0); + assert_se(path_equal(result, "/")); + + result = mfree(result); + r = chase_symlinks("/etc/./.././", "/etc", &result); + assert_se(r == -EINVAL); + + result = mfree(result); + r = chase_symlinks("/etc/machine-id/foo", NULL, &result); + assert_se(r == -ENOTDIR); + + result = mfree(result); + p = strjoina(temp, "/recursive-symlink"); + assert_se(symlink("recursive-symlink", p) >= 0); + r = chase_symlinks(p, NULL, &result); + assert_se(r == -ELOOP); + + assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); +} + static void test_unlink_noerrno(void) { char name[] = "/tmp/test-close_nointr.XXXXXX"; int fd; - fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(name); assert_se(fd >= 0); assert_se(close_nointr(fd) >= 0); @@ -83,47 +176,59 @@ static void test_get_files_in_directory(void) { } static void test_var_tmp(void) { - char *tmp_dir = NULL; - char *tmpdir_backup = NULL; - const char *default_var_tmp = NULL; - const char *var_name; - bool do_overwrite = true; - - default_var_tmp = "/var/tmp"; - var_name = "TMPDIR"; - - if (getenv(var_name) != NULL) { - tmpdir_backup = strdup(getenv(var_name)); - assert_se(tmpdir_backup != NULL); + _cleanup_free_ char *tmpdir_backup = NULL, *temp_backup = NULL, *tmp_backup = NULL; + const char *tmp_dir = NULL, *t; + + t = getenv("TMPDIR"); + if (t) { + tmpdir_backup = strdup(t); + assert_se(tmpdir_backup); + } + + t = getenv("TEMP"); + if (t) { + temp_backup = strdup(t); + assert_se(temp_backup); } - unsetenv(var_name); + t = getenv("TMP"); + if (t) { + tmp_backup = strdup(t); + assert_se(tmp_backup); + } - var_tmp(&tmp_dir); - assert_se(!strcmp(tmp_dir, default_var_tmp)); + assert(unsetenv("TMPDIR") >= 0); + assert(unsetenv("TEMP") >= 0); + assert(unsetenv("TMP") >= 0); - free(tmp_dir); + assert_se(var_tmp_dir(&tmp_dir) >= 0); + assert_se(streq(tmp_dir, "/var/tmp")); - setenv(var_name, "/tmp", do_overwrite); - assert_se(!strcmp(getenv(var_name), "/tmp")); + assert_se(setenv("TMPDIR", "/tmp", true) >= 0); + assert_se(streq(getenv("TMPDIR"), "/tmp")); - var_tmp(&tmp_dir); - assert_se(!strcmp(tmp_dir, "/tmp")); + assert_se(var_tmp_dir(&tmp_dir) >= 0); + assert_se(streq(tmp_dir, "/tmp")); - free(tmp_dir); + assert_se(setenv("TMPDIR", "/88_does_not_exist_88", true) >= 0); + assert_se(streq(getenv("TMPDIR"), "/88_does_not_exist_88")); - setenv(var_name, "/88_does_not_exist_88", do_overwrite); - assert_se(!strcmp(getenv(var_name), "/88_does_not_exist_88")); + assert_se(var_tmp_dir(&tmp_dir) >= 0); + assert_se(streq(tmp_dir, "/var/tmp")); - var_tmp(&tmp_dir); - assert_se(!strcmp(tmp_dir, default_var_tmp)); + if (tmpdir_backup) { + assert_se(setenv("TMPDIR", tmpdir_backup, true) >= 0); + assert_se(streq(getenv("TMPDIR"), tmpdir_backup)); + } - free(tmp_dir); + if (temp_backup) { + assert_se(setenv("TEMP", temp_backup, true) >= 0); + assert_se(streq(getenv("TEMP"), temp_backup)); + } - if (tmpdir_backup != NULL) { - setenv(var_name, tmpdir_backup, do_overwrite); - assert_se(!strcmp(getenv(var_name), tmpdir_backup)); - free(tmpdir_backup); + if (tmp_backup) { + assert_se(setenv("TMP", tmp_backup, true) >= 0); + assert_se(streq(getenv("TMP"), tmp_backup)); } } @@ -132,6 +237,7 @@ int main(int argc, char *argv[]) { test_readlink_and_make_absolute(); test_get_files_in_directory(); test_var_tmp(); + test_chase_symlinks(); return 0; } diff --git a/src/test/test-glob-util.c b/src/test/test-glob-util.c index 227d4290f0..9eea3eb608 100644 --- a/src/test/test-glob-util.c +++ b/src/test/test-glob-util.c @@ -30,7 +30,7 @@ static void test_glob_exists(void) { int fd = -1; int r; - fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(name); assert_se(fd >= 0); close(fd); diff --git a/src/test/test-hostname-util.c b/src/test/test-hostname-util.c index 17fde9f27e..d2c3ea5e0d 100644 --- a/src/test/test-hostname-util.c +++ b/src/test/test-hostname-util.c @@ -42,6 +42,7 @@ static void test_hostname_is_valid(void) { assert_se(!hostname_is_valid("foo..bar", false)); assert_se(!hostname_is_valid("foo.bar..", false)); assert_se(!hostname_is_valid("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", false)); + assert_se(!hostname_is_valid("au-xph5-rvgrdsb5hcxc-47et3a5vvkrc-server-wyoz4elpdpe3.openstack.local", false)); assert_se(hostname_is_valid("foobar", true)); assert_se(hostname_is_valid("foobar.com", true)); @@ -103,7 +104,7 @@ static void test_read_hostname_config(void) { char *hostname; int fd; - fd = mkostemp_safe(path, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(path); assert(fd > 0); close(fd); diff --git a/src/test/test-id128.c b/src/test/test-id128.c index f01fbdd6b2..1c8e5549da 100644 --- a/src/test/test-id128.c +++ b/src/test/test-id128.c @@ -144,7 +144,7 @@ int main(int argc, char *argv[]) { assert_se(ftruncate(fd, 0) >= 0); assert_se(sd_id128_randomize(&id) >= 0); - assert_se(write(fd, id128_to_uuid_string(id, t), 36) == 36); + assert_se(write(fd, id128_to_uuid_string(id, q), 36) == 36); assert_se(lseek(fd, 0, SEEK_SET) == 0); assert_se(id128_read_fd(fd, ID128_PLAIN, &id2) == -EINVAL); diff --git a/src/test/test-install-root.c b/src/test/test-install-root.c index db1c928660..a98de76b43 100644 --- a/src/test/test-install-root.c +++ b/src/test/test-install-root.c @@ -64,7 +64,7 @@ static void test_basic_mask_and_enable(const char *root) { assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "d.service", NULL) >= 0); assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "d.service", &state) >= 0 && state == UNIT_FILE_DISABLED); - assert_se(unit_file_mask(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("a.service"), false, &changes, &n_changes) >= 0); + assert_se(unit_file_mask(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("a.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == UNIT_FILE_SYMLINK); assert_se(streq(changes[0].source, "/dev/null")); @@ -80,11 +80,11 @@ static void test_basic_mask_and_enable(const char *root) { assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "d.service", &state) >= 0 && state == UNIT_FILE_MASKED); /* Enabling a masked unit should fail! */ - assert_se(unit_file_enable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("a.service"), false, &changes, &n_changes) == -ERFKILL); + assert_se(unit_file_enable(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("a.service"), &changes, &n_changes) == -ERFKILL); unit_file_changes_free(changes, n_changes); changes = NULL; n_changes = 0; - assert_se(unit_file_unmask(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("a.service"), &changes, &n_changes) >= 0); + assert_se(unit_file_unmask(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("a.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == UNIT_FILE_UNLINK); p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/a.service"); @@ -92,7 +92,7 @@ static void test_basic_mask_and_enable(const char *root) { unit_file_changes_free(changes, n_changes); changes = NULL; n_changes = 0; - assert_se(unit_file_enable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("a.service"), false, &changes, &n_changes) == 1); + assert_se(unit_file_enable(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("a.service"), &changes, &n_changes) == 1); assert_se(n_changes == 1); assert_se(changes[0].type == UNIT_FILE_SYMLINK); assert_se(streq(changes[0].source, "/usr/lib/systemd/system/a.service")); @@ -107,12 +107,12 @@ static void test_basic_mask_and_enable(const char *root) { assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "d.service", &state) >= 0 && state == UNIT_FILE_ENABLED); /* Enabling it again should succeed but be a NOP */ - assert_se(unit_file_enable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("a.service"), false, &changes, &n_changes) >= 0); + assert_se(unit_file_enable(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("a.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 0); unit_file_changes_free(changes, n_changes); changes = NULL; n_changes = 0; - assert_se(unit_file_disable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("a.service"), &changes, &n_changes) >= 0); + assert_se(unit_file_disable(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("a.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == UNIT_FILE_UNLINK); p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/a.service"); @@ -126,13 +126,13 @@ static void test_basic_mask_and_enable(const char *root) { assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "d.service", &state) >= 0 && state == UNIT_FILE_DISABLED); /* Disabling a disabled unit must suceed but be a NOP */ - assert_se(unit_file_disable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("a.service"), &changes, &n_changes) >= 0); + assert_se(unit_file_disable(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("a.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 0); unit_file_changes_free(changes, n_changes); changes = NULL; n_changes = 0; /* Let's enable this indirectly via a symlink */ - assert_se(unit_file_enable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("d.service"), false, &changes, &n_changes) >= 0); + assert_se(unit_file_enable(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("d.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == UNIT_FILE_SYMLINK); assert_se(streq(changes[0].source, "/usr/lib/systemd/system/a.service")); @@ -148,7 +148,7 @@ static void test_basic_mask_and_enable(const char *root) { /* Let's try to reenable */ - assert_se(unit_file_reenable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("b.service"), false, &changes, &n_changes) >= 0); + assert_se(unit_file_reenable(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("b.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 2); assert_se(changes[0].type == UNIT_FILE_UNLINK); p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/a.service"); @@ -217,7 +217,7 @@ static void test_linked_units(const char *root) { assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "linked3.service", &state) >= 0 && state == UNIT_FILE_LINKED); /* First, let's link the unit into the search path */ - assert_se(unit_file_link(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("/opt/linked.service"), false, &changes, &n_changes) >= 0); + assert_se(unit_file_link(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("/opt/linked.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == UNIT_FILE_SYMLINK); assert_se(streq(changes[0].source, "/opt/linked.service")); @@ -229,7 +229,7 @@ static void test_linked_units(const char *root) { assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "linked.service", &state) >= 0 && state == UNIT_FILE_LINKED); /* Let's unlink it from the search path again */ - assert_se(unit_file_disable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("linked.service"), &changes, &n_changes) >= 0); + assert_se(unit_file_disable(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("linked.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == UNIT_FILE_UNLINK); p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/linked.service"); @@ -240,7 +240,7 @@ static void test_linked_units(const char *root) { assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "linked.service", NULL) == -ENOENT); /* Now, let's not just link it, but also enable it */ - assert_se(unit_file_enable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("/opt/linked.service"), false, &changes, &n_changes) >= 0); + assert_se(unit_file_enable(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("/opt/linked.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 2); p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/linked.service"); q = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/linked.service"); @@ -262,7 +262,7 @@ static void test_linked_units(const char *root) { assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "linked.service", &state) >= 0 && state == UNIT_FILE_ENABLED); /* And let's unlink it again */ - assert_se(unit_file_disable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("linked.service"), &changes, &n_changes) >= 0); + assert_se(unit_file_disable(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("linked.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 2); p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/linked.service"); q = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/linked.service"); @@ -282,7 +282,7 @@ static void test_linked_units(const char *root) { assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "linked.service", NULL) == -ENOENT); - assert_se(unit_file_enable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("linked2.service"), false, &changes, &n_changes) >= 0); + assert_se(unit_file_enable(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("linked2.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 2); p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/linked2.service"); q = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/linked2.service"); @@ -301,7 +301,7 @@ static void test_linked_units(const char *root) { unit_file_changes_free(changes, n_changes); changes = NULL; n_changes = 0; - assert_se(unit_file_enable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("linked3.service"), false, &changes, &n_changes) >= 0); + assert_se(unit_file_enable(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("linked3.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == UNIT_FILE_SYMLINK); assert_se(startswith(changes[0].path, root)); @@ -325,14 +325,16 @@ static void test_default(const char *root) { assert_se(unit_file_get_default(UNIT_FILE_SYSTEM, root, &def) == -ENOENT); - assert_se(unit_file_set_default(UNIT_FILE_SYSTEM, root, "idontexist.target", false, &changes, &n_changes) == -ENOENT); - assert_se(n_changes == 0); + assert_se(unit_file_set_default(UNIT_FILE_SYSTEM, 0, root, "idontexist.target", &changes, &n_changes) == -ENOENT); + assert_se(n_changes == 1); + assert_se(changes[0].type == -ENOENT); + assert_se(streq_ptr(changes[0].path, "idontexist.target")); unit_file_changes_free(changes, n_changes); changes = NULL; n_changes = 0; assert_se(unit_file_get_default(UNIT_FILE_SYSTEM, root, &def) == -ENOENT); - assert_se(unit_file_set_default(UNIT_FILE_SYSTEM, root, "test-default.target", false, &changes, &n_changes) >= 0); + assert_se(unit_file_set_default(UNIT_FILE_SYSTEM, 0, root, "test-default.target", &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == UNIT_FILE_SYMLINK); assert_se(streq(changes[0].source, "/usr/lib/systemd/system/test-default-real.target")); @@ -362,7 +364,7 @@ static void test_add_dependency(const char *root) { p = strjoina(root, "/usr/lib/systemd/system/add-dependency-test-service.service"); assert_se(symlink("real-add-dependency-test-service.service", p) >= 0); - assert_se(unit_file_add_dependency(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("add-dependency-test-service.service"), "add-dependency-test-target.target", UNIT_WANTS, false, &changes, &n_changes) >= 0); + assert_se(unit_file_add_dependency(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("add-dependency-test-service.service"), "add-dependency-test-target.target", UNIT_WANTS, &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == UNIT_FILE_SYMLINK); assert_se(streq(changes[0].source, "/usr/lib/systemd/system/real-add-dependency-test-service.service")); @@ -399,7 +401,7 @@ static void test_template_enable(const char *root) { assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED); assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@foo.service", &state) >= 0 && state == UNIT_FILE_DISABLED); - assert_se(unit_file_enable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("template@.service"), false, &changes, &n_changes) >= 0); + assert_se(unit_file_enable(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("template@.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == UNIT_FILE_SYMLINK); assert_se(streq(changes[0].source, "/usr/lib/systemd/system/template@.service")); @@ -415,7 +417,7 @@ static void test_template_enable(const char *root) { assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@def.service", &state) >= 0 && state == UNIT_FILE_ENABLED); assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@foo.service", &state) >= 0 && state == UNIT_FILE_DISABLED); - assert_se(unit_file_disable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("template@.service"), &changes, &n_changes) >= 0); + assert_se(unit_file_disable(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("template@.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == UNIT_FILE_UNLINK); assert_se(streq(changes[0].path, p)); @@ -429,7 +431,7 @@ static void test_template_enable(const char *root) { assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED); assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@foo.service", &state) >= 0 && state == UNIT_FILE_DISABLED); - assert_se(unit_file_enable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("template@foo.service"), false, &changes, &n_changes) >= 0); + assert_se(unit_file_enable(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("template@foo.service"), &changes, &n_changes) >= 0); assert_se(changes[0].type == UNIT_FILE_SYMLINK); assert_se(streq(changes[0].source, "/usr/lib/systemd/system/template@.service")); p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/template@foo.service"); @@ -444,7 +446,7 @@ static void test_template_enable(const char *root) { assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED); assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@foo.service", &state) >= 0 && state == UNIT_FILE_ENABLED); - assert_se(unit_file_disable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("template@foo.service"), &changes, &n_changes) >= 0); + assert_se(unit_file_disable(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("template@foo.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == UNIT_FILE_UNLINK); assert_se(streq(changes[0].path, p)); @@ -460,7 +462,7 @@ static void test_template_enable(const char *root) { assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@foo.service", &state) >= 0 && state == UNIT_FILE_DISABLED); assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "template-symlink@quux.service", &state) >= 0 && state == UNIT_FILE_DISABLED); - assert_se(unit_file_enable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("template-symlink@quux.service"), false, &changes, &n_changes) >= 0); + assert_se(unit_file_enable(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("template-symlink@quux.service"), &changes, &n_changes) >= 0); assert_se(changes[0].type == UNIT_FILE_SYMLINK); assert_se(streq(changes[0].source, "/usr/lib/systemd/system/template@.service")); p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/template@quux.service"); @@ -505,7 +507,7 @@ static void test_indirect(const char *root) { assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "indirectb.service", &state) >= 0 && state == UNIT_FILE_DISABLED); assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "indirectc.service", &state) >= 0 && state == UNIT_FILE_INDIRECT); - assert_se(unit_file_enable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("indirectc.service"), false, &changes, &n_changes) >= 0); + assert_se(unit_file_enable(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("indirectc.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == UNIT_FILE_SYMLINK); assert_se(streq(changes[0].source, "/usr/lib/systemd/system/indirectb.service")); @@ -518,7 +520,7 @@ static void test_indirect(const char *root) { assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "indirectb.service", &state) >= 0 && state == UNIT_FILE_ENABLED); assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "indirectc.service", &state) >= 0 && state == UNIT_FILE_INDIRECT); - assert_se(unit_file_disable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("indirectc.service"), &changes, &n_changes) >= 0); + assert_se(unit_file_disable(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("indirectc.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == UNIT_FILE_UNLINK); p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/indirectb.service"); @@ -558,7 +560,7 @@ static void test_preset_and_list(const char *root) { assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_DISABLED); assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED); - assert_se(unit_file_preset(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("preset-yes.service"), UNIT_FILE_PRESET_FULL, false, &changes, &n_changes) >= 0); + assert_se(unit_file_preset(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("preset-yes.service"), UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == UNIT_FILE_SYMLINK); assert_se(streq(changes[0].source, "/usr/lib/systemd/system/preset-yes.service")); @@ -570,7 +572,7 @@ static void test_preset_and_list(const char *root) { assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_ENABLED); assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED); - assert_se(unit_file_disable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("preset-yes.service"), &changes, &n_changes) >= 0); + assert_se(unit_file_disable(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("preset-yes.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == UNIT_FILE_UNLINK); p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/preset-yes.service"); @@ -581,7 +583,7 @@ static void test_preset_and_list(const char *root) { assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_DISABLED); assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED); - assert_se(unit_file_preset(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("preset-no.service"), UNIT_FILE_PRESET_FULL, false, &changes, &n_changes) >= 0); + assert_se(unit_file_preset(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("preset-no.service"), UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0); assert_se(n_changes == 0); unit_file_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -589,7 +591,7 @@ static void test_preset_and_list(const char *root) { assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_DISABLED); assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED); - assert_se(unit_file_preset_all(UNIT_FILE_SYSTEM, false, root, UNIT_FILE_PRESET_FULL, false, &changes, &n_changes) >= 0); + assert_se(unit_file_preset_all(UNIT_FILE_SYSTEM, 0, root, UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0); assert_se(n_changes > 0); @@ -714,7 +716,7 @@ static void test_preset_order(const char *root) { assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "prefix-1.service", &state) >= 0 && state == UNIT_FILE_DISABLED); assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "prefix-2.service", &state) >= 0 && state == UNIT_FILE_DISABLED); - assert_se(unit_file_preset(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("prefix-1.service"), UNIT_FILE_PRESET_FULL, false, &changes, &n_changes) >= 0); + assert_se(unit_file_preset(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("prefix-1.service"), UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == UNIT_FILE_SYMLINK); assert_se(streq(changes[0].source, "/usr/lib/systemd/system/prefix-1.service")); @@ -726,7 +728,7 @@ static void test_preset_order(const char *root) { assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "prefix-1.service", &state) >= 0 && state == UNIT_FILE_ENABLED); assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "prefix-2.service", &state) >= 0 && state == UNIT_FILE_DISABLED); - assert_se(unit_file_preset(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("prefix-2.service"), UNIT_FILE_PRESET_FULL, false, &changes, &n_changes) >= 0); + assert_se(unit_file_preset(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("prefix-2.service"), UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0); assert_se(n_changes == 0); assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "prefix-1.service", &state) >= 0 && state == UNIT_FILE_ENABLED); diff --git a/src/test/test-install.c b/src/test/test-install.c index 0ac85f040a..fb36aa83ca 100644 --- a/src/test/test-install.c +++ b/src/test/test-install.c @@ -70,12 +70,12 @@ int main(int argc, char* argv[]) { log_info("/*** enable **/"); - r = unit_file_enable(UNIT_FILE_SYSTEM, false, NULL, (char**) files, false, &changes, &n_changes); + r = unit_file_enable(UNIT_FILE_SYSTEM, 0, NULL, (char**) files, &changes, &n_changes); assert_se(r >= 0); log_info("/*** enable2 **/"); - r = unit_file_enable(UNIT_FILE_SYSTEM, false, NULL, (char**) files, false, &changes, &n_changes); + r = unit_file_enable(UNIT_FILE_SYSTEM, 0, NULL, (char**) files, &changes, &n_changes); assert_se(r >= 0); dump_changes(changes, n_changes); @@ -89,7 +89,7 @@ int main(int argc, char* argv[]) { changes = NULL; n_changes = 0; - r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, (char**) files, &changes, &n_changes); + r = unit_file_disable(UNIT_FILE_SYSTEM, 0, NULL, (char**) files, &changes, &n_changes); assert_se(r >= 0); dump_changes(changes, n_changes); @@ -103,10 +103,10 @@ int main(int argc, char* argv[]) { changes = NULL; n_changes = 0; - r = unit_file_mask(UNIT_FILE_SYSTEM, false, NULL, (char**) files, false, &changes, &n_changes); + r = unit_file_mask(UNIT_FILE_SYSTEM, 0, NULL, (char**) files, &changes, &n_changes); assert_se(r >= 0); log_info("/*** mask2 ***/"); - r = unit_file_mask(UNIT_FILE_SYSTEM, false, NULL, (char**) files, false, &changes, &n_changes); + r = unit_file_mask(UNIT_FILE_SYSTEM, 0, NULL, (char**) files, &changes, &n_changes); assert_se(r >= 0); dump_changes(changes, n_changes); @@ -120,10 +120,10 @@ int main(int argc, char* argv[]) { changes = NULL; n_changes = 0; - r = unit_file_unmask(UNIT_FILE_SYSTEM, false, NULL, (char**) files, &changes, &n_changes); + r = unit_file_unmask(UNIT_FILE_SYSTEM, 0, NULL, (char**) files, &changes, &n_changes); assert_se(r >= 0); log_info("/*** unmask2 ***/"); - r = unit_file_unmask(UNIT_FILE_SYSTEM, false, NULL, (char**) files, &changes, &n_changes); + r = unit_file_unmask(UNIT_FILE_SYSTEM, 0, NULL, (char**) files, &changes, &n_changes); assert_se(r >= 0); dump_changes(changes, n_changes); @@ -137,7 +137,7 @@ int main(int argc, char* argv[]) { changes = NULL; n_changes = 0; - r = unit_file_mask(UNIT_FILE_SYSTEM, false, NULL, (char**) files, false, &changes, &n_changes); + r = unit_file_mask(UNIT_FILE_SYSTEM, 0, NULL, (char**) files, &changes, &n_changes); assert_se(r >= 0); dump_changes(changes, n_changes); @@ -151,10 +151,10 @@ int main(int argc, char* argv[]) { changes = NULL; n_changes = 0; - r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, (char**) files, &changes, &n_changes); + r = unit_file_disable(UNIT_FILE_SYSTEM, 0, NULL, (char**) files, &changes, &n_changes); assert_se(r >= 0); log_info("/*** disable2 ***/"); - r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, (char**) files, &changes, &n_changes); + r = unit_file_disable(UNIT_FILE_SYSTEM, 0, NULL, (char**) files, &changes, &n_changes); assert_se(r >= 0); dump_changes(changes, n_changes); @@ -168,7 +168,7 @@ int main(int argc, char* argv[]) { changes = NULL; n_changes = 0; - r = unit_file_unmask(UNIT_FILE_SYSTEM, false, NULL, (char**) files, &changes, &n_changes); + r = unit_file_unmask(UNIT_FILE_SYSTEM, 0, NULL, (char**) files, &changes, &n_changes); assert_se(r >= 0); dump_changes(changes, n_changes); @@ -182,7 +182,7 @@ int main(int argc, char* argv[]) { changes = NULL; n_changes = 0; - r = unit_file_enable(UNIT_FILE_SYSTEM, false, NULL, (char**) files2, false, &changes, &n_changes); + r = unit_file_enable(UNIT_FILE_SYSTEM, 0, NULL, (char**) files2, &changes, &n_changes); assert_se(r >= 0); dump_changes(changes, n_changes); @@ -196,7 +196,7 @@ int main(int argc, char* argv[]) { changes = NULL; n_changes = 0; - r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, STRV_MAKE(basename(files2[0])), &changes, &n_changes); + r = unit_file_disable(UNIT_FILE_SYSTEM, 0, NULL, STRV_MAKE(basename(files2[0])), &changes, &n_changes); assert_se(r >= 0); dump_changes(changes, n_changes); @@ -209,7 +209,7 @@ int main(int argc, char* argv[]) { changes = NULL; n_changes = 0; - r = unit_file_link(UNIT_FILE_SYSTEM, false, NULL, (char**) files2, false, &changes, &n_changes); + r = unit_file_link(UNIT_FILE_SYSTEM, 0, NULL, (char**) files2, &changes, &n_changes); assert_se(r >= 0); dump_changes(changes, n_changes); @@ -223,7 +223,7 @@ int main(int argc, char* argv[]) { changes = NULL; n_changes = 0; - r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, STRV_MAKE(basename(files2[0])), &changes, &n_changes); + r = unit_file_disable(UNIT_FILE_SYSTEM, 0, NULL, STRV_MAKE(basename(files2[0])), &changes, &n_changes); assert_se(r >= 0); dump_changes(changes, n_changes); @@ -236,7 +236,7 @@ int main(int argc, char* argv[]) { changes = NULL; n_changes = 0; - r = unit_file_link(UNIT_FILE_SYSTEM, false, NULL, (char**) files2, false, &changes, &n_changes); + r = unit_file_link(UNIT_FILE_SYSTEM, 0, NULL, (char**) files2, &changes, &n_changes); assert_se(r >= 0); dump_changes(changes, n_changes); @@ -250,7 +250,7 @@ int main(int argc, char* argv[]) { changes = NULL; n_changes = 0; - r = unit_file_reenable(UNIT_FILE_SYSTEM, false, NULL, (char**) files2, false, &changes, &n_changes); + r = unit_file_reenable(UNIT_FILE_SYSTEM, 0, NULL, (char**) files2, &changes, &n_changes); assert_se(r >= 0); dump_changes(changes, n_changes); @@ -264,7 +264,7 @@ int main(int argc, char* argv[]) { changes = NULL; n_changes = 0; - r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, STRV_MAKE(basename(files2[0])), &changes, &n_changes); + r = unit_file_disable(UNIT_FILE_SYSTEM, 0, NULL, STRV_MAKE(basename(files2[0])), &changes, &n_changes); assert_se(r >= 0); dump_changes(changes, n_changes); @@ -276,7 +276,7 @@ int main(int argc, char* argv[]) { changes = NULL; n_changes = 0; - r = unit_file_preset(UNIT_FILE_SYSTEM, false, NULL, (char**) files, UNIT_FILE_PRESET_FULL, false, &changes, &n_changes); + r = unit_file_preset(UNIT_FILE_SYSTEM, 0, NULL, (char**) files, UNIT_FILE_PRESET_FULL, &changes, &n_changes); assert_se(r >= 0); dump_changes(changes, n_changes); diff --git a/src/test/test-ipcrm.c b/src/test/test-ipcrm.c index c5bcaf47bb..551eba7215 100644 --- a/src/test/test-ipcrm.c +++ b/src/test/test-ipcrm.c @@ -32,5 +32,5 @@ int main(int argc, char *argv[]) { return EXIT_FAILURE; } - return clean_ipc(uid) < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + return clean_ipc_by_uid(uid) < 0 ? EXIT_FAILURE : EXIT_SUCCESS; } diff --git a/src/test/test-list.c b/src/test/test-list.c index 160064d06a..0ccd745cc9 100644 --- a/src/test/test-list.c +++ b/src/test/test-list.c @@ -132,6 +132,29 @@ int main(int argc, const char *argv[]) { assert_se(items[1].item_prev == &items[3]); assert_se(items[3].item_prev == NULL); + LIST_INSERT_BEFORE(item, head, &items[3], &items[0]); + assert_se(items[2].item_next == NULL); + assert_se(items[1].item_next == &items[2]); + assert_se(items[3].item_next == &items[1]); + assert_se(items[0].item_next == &items[3]); + + assert_se(items[2].item_prev == &items[1]); + assert_se(items[1].item_prev == &items[3]); + assert_se(items[3].item_prev == &items[0]); + assert_se(items[0].item_prev == NULL); + assert_se(head == &items[0]); + + LIST_REMOVE(item, head, &items[0]); + assert_se(LIST_JUST_US(item, &items[0])); + + assert_se(items[2].item_next == NULL); + assert_se(items[1].item_next == &items[2]); + assert_se(items[3].item_next == &items[1]); + + assert_se(items[2].item_prev == &items[1]); + assert_se(items[1].item_prev == &items[3]); + assert_se(items[3].item_prev == NULL); + LIST_INSERT_BEFORE(item, head, NULL, &items[0]); assert_se(items[0].item_next == NULL); assert_se(items[2].item_next == &items[0]); diff --git a/src/test/test-ns.c b/src/test/test-ns.c index 9248f2987c..da7a8b0565 100644 --- a/src/test/test-ns.c +++ b/src/test/test-ns.c @@ -26,13 +26,18 @@ int main(int argc, char *argv[]) { const char * const writable[] = { "/home", + "-/home/lennart/projects/foobar", /* this should be masked automatically */ NULL }; const char * const readonly[] = { - "/", - "/usr", + /* "/", */ + /* "/usr", */ "/boot", + "/lib", + "/usr/lib", + "-/lib64", + "-/usr/lib64", NULL }; @@ -40,13 +45,22 @@ int main(int argc, char *argv[]) { "/home/lennart/projects", NULL }; + + static const NameSpaceInfo ns_info = { + .private_dev = true, + .protect_control_groups = true, + .protect_kernel_tunables = true, + .protect_kernel_modules = true, + }; + char *root_directory; char *projects_directory; - int r; char tmp_dir[] = "/tmp/systemd-private-XXXXXX", var_tmp_dir[] = "/var/tmp/systemd-private-XXXXXX"; + log_set_max_level(LOG_DEBUG); + assert_se(mkdtemp(tmp_dir)); assert_se(mkdtemp(var_tmp_dir)); @@ -63,12 +77,12 @@ int main(int argc, char *argv[]) { log_info("Not chrooted"); r = setup_namespace(root_directory, + &ns_info, (char **) writable, (char **) readonly, (char **) inaccessible, tmp_dir, var_tmp_dir, - true, PROTECT_HOME_NO, PROTECT_SYSTEM_NO, 0); diff --git a/src/test/test-parse-util.c b/src/test/test-parse-util.c index 0a76308f72..d08014100b 100644 --- a/src/test/test-parse-util.c +++ b/src/test/test-parse-util.c @@ -493,6 +493,39 @@ static void test_parse_percent(void) { assert_se(parse_percent("1%%") == -EINVAL); } +static void test_parse_percent_unbounded(void) { + assert_se(parse_percent_unbounded("101%") == 101); + assert_se(parse_percent_unbounded("400%") == 400); +} + +static void test_parse_nice(void) { + int n; + + assert_se(parse_nice("0", &n) >= 0 && n == 0); + assert_se(parse_nice("+0", &n) >= 0 && n == 0); + assert_se(parse_nice("-1", &n) >= 0 && n == -1); + assert_se(parse_nice("-2", &n) >= 0 && n == -2); + assert_se(parse_nice("1", &n) >= 0 && n == 1); + assert_se(parse_nice("2", &n) >= 0 && n == 2); + assert_se(parse_nice("+1", &n) >= 0 && n == 1); + assert_se(parse_nice("+2", &n) >= 0 && n == 2); + assert_se(parse_nice("-20", &n) >= 0 && n == -20); + assert_se(parse_nice("19", &n) >= 0 && n == 19); + assert_se(parse_nice("+19", &n) >= 0 && n == 19); + + + assert_se(parse_nice("", &n) == -EINVAL); + assert_se(parse_nice("-", &n) == -EINVAL); + assert_se(parse_nice("+", &n) == -EINVAL); + assert_se(parse_nice("xx", &n) == -EINVAL); + assert_se(parse_nice("-50", &n) == -ERANGE); + assert_se(parse_nice("50", &n) == -ERANGE); + assert_se(parse_nice("+50", &n) == -ERANGE); + assert_se(parse_nice("-21", &n) == -ERANGE); + assert_se(parse_nice("20", &n) == -ERANGE); + assert_se(parse_nice("+20", &n) == -ERANGE); +} + int main(int argc, char *argv[]) { log_parse_environment(); log_open(); @@ -507,6 +540,8 @@ int main(int argc, char *argv[]) { test_safe_atoi16(); test_safe_atod(); test_parse_percent(); + test_parse_percent_unbounded(); + test_parse_nice(); return 0; } diff --git a/src/test/test-path-util.c b/src/test/test-path-util.c index 6094d4c3e5..a6a09a0031 100644 --- a/src/test/test-path-util.c +++ b/src/test/test-path-util.c @@ -114,7 +114,8 @@ static void test_find_binary(const char *self) { assert_se(find_binary(self, &p) == 0); puts(p); - assert_se(endswith(p, "/lt-test-path-util")); + /* libtool might prefix the binary name with "lt-" */ + assert_se(endswith(p, "/lt-test-path-util") || endswith(p, "/test-path-util")); assert_se(path_is_absolute(p)); free(p); @@ -262,16 +263,37 @@ static void test_strv_resolve(void) { } static void test_path_startswith(void) { - assert_se(path_startswith("/foo/bar/barfoo/", "/foo")); - assert_se(path_startswith("/foo/bar/barfoo/", "/foo/")); - assert_se(path_startswith("/foo/bar/barfoo/", "/")); - assert_se(path_startswith("/foo/bar/barfoo/", "////")); - assert_se(path_startswith("/foo/bar/barfoo/", "/foo//bar/////barfoo///")); - assert_se(path_startswith("/foo/bar/barfoo/", "/foo/bar/barfoo////")); - assert_se(path_startswith("/foo/bar/barfoo/", "/foo/bar///barfoo/")); - assert_se(path_startswith("/foo/bar/barfoo/", "/foo////bar/barfoo/")); - assert_se(path_startswith("/foo/bar/barfoo/", "////foo/bar/barfoo/")); - assert_se(path_startswith("/foo/bar/barfoo/", "/foo/bar/barfoo")); + const char *p; + + p = path_startswith("/foo/bar/barfoo/", "/foo"); + assert_se(streq_ptr(p, "bar/barfoo/")); + + p = path_startswith("/foo/bar/barfoo/", "/foo/"); + assert_se(streq_ptr(p, "bar/barfoo/")); + + p = path_startswith("/foo/bar/barfoo/", "/"); + assert_se(streq_ptr(p, "foo/bar/barfoo/")); + + p = path_startswith("/foo/bar/barfoo/", "////"); + assert_se(streq_ptr(p, "foo/bar/barfoo/")); + + p = path_startswith("/foo/bar/barfoo/", "/foo//bar/////barfoo///"); + assert_se(streq_ptr(p, "")); + + p = path_startswith("/foo/bar/barfoo/", "/foo/bar/barfoo////"); + assert_se(streq_ptr(p, "")); + + p = path_startswith("/foo/bar/barfoo/", "/foo/bar///barfoo/"); + assert_se(streq_ptr(p, "")); + + p = path_startswith("/foo/bar/barfoo/", "/foo////bar/barfoo/"); + assert_se(streq_ptr(p, "")); + + p = path_startswith("/foo/bar/barfoo/", "////foo/bar/barfoo/"); + assert_se(streq_ptr(p, "")); + + p = path_startswith("/foo/bar/barfoo/", "/foo/bar/barfoo"); + assert_se(streq_ptr(p, "")); assert_se(!path_startswith("/foo/bar/barfoo/", "/foo/bar/barfooa/")); assert_se(!path_startswith("/foo/bar/barfoo/", "/foo/bar/barfooa")); @@ -510,7 +532,24 @@ static void test_hidden_or_backup_file(void) { assert_se(!hidden_or_backup_file("test.dpkg-old.foo")); } +static void test_systemd_installation_has_version(const char *path) { + int r; + const unsigned versions[] = {0, 231, atoi(PACKAGE_VERSION), 999}; + unsigned i; + + for (i = 0; i < ELEMENTSOF(versions); i++) { + r = systemd_installation_has_version(path, versions[i]); + assert_se(r >= 0); + log_info("%s has systemd >= %u: %s", + path ?: "Current installation", versions[i], yes_no(r)); + } +} + int main(int argc, char **argv) { + log_set_max_level(LOG_DEBUG); + log_parse_environment(); + log_open(); + test_path(); test_find_binary(argv[0]); test_prefixes(); @@ -525,5 +564,7 @@ int main(int argc, char **argv) { test_filename_is_valid(); test_hidden_or_backup_file(); + test_systemd_installation_has_version(argv[1]); /* NULL is OK */ + return 0; } diff --git a/src/test/test-path.c b/src/test/test-path.c index 62181e22a0..4d3f0e9948 100644 --- a/src/test/test-path.c +++ b/src/test/test-path.c @@ -47,7 +47,7 @@ static int setup_test(Manager **m) { r = manager_new(UNIT_FILE_USER, true, &tmp); if (MANAGER_SKIP_TEST(r)) { - printf("Skipping test: manager_new: %s\n", strerror(-r)); + log_notice_errno(r, "Skipping test: manager_new: %m"); return -EXIT_TEST_SKIP; } assert_se(r >= 0); diff --git a/src/test/test-proc-cmdline.c b/src/test/test-proc-cmdline.c index 80ad5ed98b..4101678f19 100644 --- a/src/test/test-proc-cmdline.c +++ b/src/test/test-proc-cmdline.c @@ -25,15 +25,18 @@ #include "string-util.h" #include "util.h" -static int parse_item(const char *key, const char *value) { +static int obj; + +static int parse_item(const char *key, const char *value, void *data) { assert_se(key); + assert_se(data == &obj); log_info("kernel cmdline option <%s> = <%s>", key, strna(value)); return 0; } static void test_parse_proc_cmdline(void) { - assert_se(parse_proc_cmdline(parse_item) >= 0); + assert_se(parse_proc_cmdline(parse_item, &obj, true) >= 0); } static void test_runlevel_to_target(void) { diff --git a/src/test/test-process-util.c b/src/test/test-process-util.c index 562ad4acb8..9ada46b1e9 100644 --- a/src/test/test-process-util.c +++ b/src/test/test-process-util.c @@ -40,6 +40,7 @@ #include "stdio-util.h" #include "string-util.h" #include "terminal-util.h" +#include "test-helper.h" #include "util.h" #include "virt.h" @@ -357,7 +358,7 @@ int main(int argc, char *argv[]) { (void) parse_pid(argv[1], &pid); test_get_process_comm(pid); } else { - test_get_process_comm(1); + TEST_REQ_RUNNING_SYSTEMD(test_get_process_comm(1)); test_get_process_comm(getpid()); } diff --git a/src/test/test-sched-prio.c b/src/test/test-sched-prio.c index c068f5c39e..7b37910c33 100644 --- a/src/test/test-sched-prio.c +++ b/src/test/test-sched-prio.c @@ -40,7 +40,7 @@ int main(int argc, char *argv[]) { assert_se(set_unit_path(TEST_DIR) >= 0); r = manager_new(UNIT_FILE_USER, true, &m); if (MANAGER_SKIP_TEST(r)) { - printf("Skipping test: manager_new: %s\n", strerror(-r)); + log_notice_errno(r, "Skipping test: manager_new: %m"); return EXIT_TEST_SKIP; } assert_se(r >= 0); diff --git a/src/test/test-seccomp.c b/src/test/test-seccomp.c new file mode 100644 index 0000000000..43d1567288 --- /dev/null +++ b/src/test/test-seccomp.c @@ -0,0 +1,136 @@ +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdlib.h> +#include <sys/eventfd.h> +#include <unistd.h> + +#include "fd-util.h" +#include "macro.h" +#include "process-util.h" +#include "seccomp-util.h" +#include "string-util.h" +#include "util.h" + +static void test_seccomp_arch_to_string(void) { + uint32_t a, b; + const char *name; + + a = seccomp_arch_native(); + assert_se(a > 0); + name = seccomp_arch_to_string(a); + assert_se(name); + assert_se(seccomp_arch_from_string(name, &b) >= 0); + assert_se(a == b); +} + +static void test_architecture_table(void) { + const char *n, *n2; + + NULSTR_FOREACH(n, + "native\0" + "x86\0" + "x86-64\0" + "x32\0" + "arm\0" + "arm64\0" + "mips\0" + "mips64\0" + "mips64-n32\0" + "mips-le\0" + "mips64-le\0" + "mips64-le-n32\0" + "ppc\0" + "ppc64\0" + "ppc64-le\0" + "s390\0" + "s390x\0") { + uint32_t c; + + assert_se(seccomp_arch_from_string(n, &c) >= 0); + n2 = seccomp_arch_to_string(c); + log_info("seccomp-arch: %s → 0x%"PRIx32" → %s", n, c, n2); + assert_se(streq_ptr(n, n2)); + } +} + +static void test_syscall_filter_set_find(void) { + assert_se(!syscall_filter_set_find(NULL)); + assert_se(!syscall_filter_set_find("")); + assert_se(!syscall_filter_set_find("quux")); + assert_se(!syscall_filter_set_find("@quux")); + + assert_se(syscall_filter_set_find("@clock") == syscall_filter_sets + SYSCALL_FILTER_SET_CLOCK); + assert_se(syscall_filter_set_find("@default") == syscall_filter_sets + SYSCALL_FILTER_SET_DEFAULT); + assert_se(syscall_filter_set_find("@raw-io") == syscall_filter_sets + SYSCALL_FILTER_SET_RAW_IO); +} + +static void test_filter_sets(void) { + unsigned i; + int r; + + if (!is_seccomp_available()) + return; + + if (geteuid() != 0) + return; + + for (i = 0; i < _SYSCALL_FILTER_SET_MAX; i++) { + pid_t pid; + + log_info("Testing %s", syscall_filter_sets[i].name); + + pid = fork(); + assert_se(pid >= 0); + + if (pid == 0) { /* Child? */ + int fd; + + if (i == SYSCALL_FILTER_SET_DEFAULT) /* if we look at the default set, whitelist instead of blacklist */ + r = seccomp_load_filter_set(SCMP_ACT_ERRNO(EPERM), syscall_filter_sets + i, SCMP_ACT_ALLOW); + else + r = seccomp_load_filter_set(SCMP_ACT_ALLOW, syscall_filter_sets + i, SCMP_ACT_ERRNO(EPERM)); + if (r < 0) + _exit(EXIT_FAILURE); + + /* Test the sycall filter with one random system call */ + fd = eventfd(0, EFD_NONBLOCK|EFD_CLOEXEC); + if (IN_SET(i, SYSCALL_FILTER_SET_IO_EVENT, SYSCALL_FILTER_SET_DEFAULT)) + assert_se(fd < 0 && errno == EPERM); + else { + assert_se(fd >= 0); + safe_close(fd); + } + + _exit(EXIT_SUCCESS); + } + + assert_se(wait_for_terminate_and_warn(syscall_filter_sets[i].name, pid, true) == EXIT_SUCCESS); + } +} + +int main(int argc, char *argv[]) { + + test_seccomp_arch_to_string(); + test_architecture_table(); + test_syscall_filter_set_find(); + test_filter_sets(); + + return 0; +} diff --git a/src/test/test-sigbus.c b/src/test/test-sigbus.c index 17b81747be..02b8e24308 100644 --- a/src/test/test-sigbus.c +++ b/src/test/test-sigbus.c @@ -29,6 +29,9 @@ int main(int argc, char *argv[]) { void *addr = NULL; uint8_t *p; +#ifdef __SANITIZE_ADDRESS__ + return EXIT_TEST_SKIP; +#endif sigbus_install(); assert_se(sigbus_pop(&addr) == 0); diff --git a/src/test/test-stat-util.c b/src/test/test-stat-util.c index a10227f823..6c34250a01 100644 --- a/src/test/test-stat-util.c +++ b/src/test/test-stat-util.c @@ -31,7 +31,7 @@ static void test_files_same(void) { char name[] = "/tmp/test-files_same.XXXXXX"; char name_alias[] = "/tmp/test-files_same.alias"; - fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(name); assert_se(fd >= 0); assert_se(symlink(name, name_alias) >= 0); @@ -47,7 +47,7 @@ static void test_is_symlink(void) { char name_link[] = "/tmp/test-is_symlink.link"; _cleanup_close_ int fd = -1; - fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(name); assert_se(fd >= 0); assert_se(symlink(name, name_link) >= 0); diff --git a/src/test/test-strv.c b/src/test/test-strv.c index 841a36782f..88da69e2d7 100644 --- a/src/test/test-strv.c +++ b/src/test/test-strv.c @@ -54,6 +54,25 @@ static void test_specifier_printf(void) { puts(w); } +static void test_str_in_set(void) { + assert_se(STR_IN_SET("x", "x", "y", "z")); + assert_se(!STR_IN_SET("X", "x", "y", "z")); + assert_se(!STR_IN_SET("", "x", "y", "z")); + assert_se(STR_IN_SET("x", "w", "x")); +} + +static void test_strptr_in_set(void) { + assert_se(STRPTR_IN_SET("x", "x", "y", "z")); + assert_se(!STRPTR_IN_SET("X", "x", "y", "z")); + assert_se(!STRPTR_IN_SET("", "x", "y", "z")); + assert_se(STRPTR_IN_SET("x", "w", "x")); + + assert_se(!STRPTR_IN_SET(NULL, "x", "y", "z")); + assert_se(!STRPTR_IN_SET(NULL, "")); + /* strv cannot contain a null, hence the result below */ + assert_se(!STRPTR_IN_SET(NULL, NULL)); +} + static const char* const input_table_multiple[] = { "one", "two", @@ -434,9 +453,14 @@ static void test_strv_foreach_backwards(void) { assert_se(a); - STRV_FOREACH_BACKWARDS(check, a) { + STRV_FOREACH_BACKWARDS(check, a) assert_se(streq_ptr(*check, input_table_multiple[i--])); - } + + STRV_FOREACH_BACKWARDS(check, (char**) NULL) + assert_not_reached("Let's see that we check empty strv right, too."); + + STRV_FOREACH_BACKWARDS(check, (char**) { NULL }) + assert_not_reached("Let's see that we check empty strv right, too."); } static void test_strv_foreach_pair(void) { @@ -703,6 +727,8 @@ static void test_strv_fnmatch(void) { int main(int argc, char *argv[]) { test_specifier_printf(); + test_str_in_set(); + test_strptr_in_set(); test_strv_foreach(); test_strv_foreach_backwards(); test_strv_foreach_pair(); diff --git a/src/test/test-tables.c b/src/test/test-tables.c index 0be74921fc..294d219869 100644 --- a/src/test/test-tables.c +++ b/src/test/test-tables.c @@ -48,6 +48,7 @@ #include "unit-name.h" #include "unit.h" #include "util.h" +#include "virt.h" int main(int argc, char **argv) { test_table(architecture, ARCHITECTURE); @@ -63,7 +64,7 @@ int main(int argc, char **argv) { test_table(device_state, DEVICE_STATE); test_table(exec_input, EXEC_INPUT); test_table(exec_output, EXEC_OUTPUT); - test_table(failure_action, FAILURE_ACTION); + test_table(emergency_action, EMERGENCY_ACTION); test_table(job_mode, JOB_MODE); test_table(job_result, JOB_RESULT); test_table(job_state, JOB_STATE); @@ -114,6 +115,7 @@ int main(int argc, char **argv) { test_table(unit_load_state, UNIT_LOAD_STATE); test_table(unit_type, UNIT_TYPE); test_table(locale_variable, VARIABLE_LC); + test_table(virtualization, VIRTUALIZATION); test_table_sparse(object_compressed, OBJECT_COMPRESSED); diff --git a/src/test/test-terminal-util.c b/src/test/test-terminal-util.c index 84b448a095..373a1b70ba 100644 --- a/src/test/test-terminal-util.c +++ b/src/test/test-terminal-util.c @@ -50,7 +50,7 @@ static void test_read_one_char(void) { char name[] = "/tmp/test-read_one_char.XXXXXX"; int fd; - fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(name); assert_se(fd >= 0); file = fdopen(fd, "r+"); assert_se(file); diff --git a/src/test/test-time.c b/src/test/test-time.c index ee7d55c5ab..7078a0374d 100644 --- a/src/test/test-time.c +++ b/src/test/test-time.c @@ -19,6 +19,7 @@ #include "strv.h" #include "time-util.h" +#include "random-util.h" static void test_parse_sec(void) { usec_t u; @@ -201,6 +202,48 @@ static void test_usec_sub(void) { assert_se(usec_sub(USEC_INFINITY, 5) == USEC_INFINITY); } +static void test_format_timestamp(void) { + unsigned i; + + for (i = 0; i < 100; i++) { + char buf[MAX(FORMAT_TIMESTAMP_MAX, FORMAT_TIMESPAN_MAX)]; + usec_t x, y; + + random_bytes(&x, sizeof(x)); + x = x % (2147483600 * USEC_PER_SEC) + 1; + + assert_se(format_timestamp(buf, sizeof(buf), x)); + log_info("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + assert_se(x / USEC_PER_SEC == y / USEC_PER_SEC); + + assert_se(format_timestamp_utc(buf, sizeof(buf), x)); + log_info("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + assert_se(x / USEC_PER_SEC == y / USEC_PER_SEC); + + assert_se(format_timestamp_us(buf, sizeof(buf), x)); + log_info("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + assert_se(x == y); + + assert_se(format_timestamp_us_utc(buf, sizeof(buf), x)); + log_info("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + assert_se(x == y); + + assert_se(format_timestamp_relative(buf, sizeof(buf), x)); + log_info("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + + /* The two calls above will run with a slightly different local time. Make sure we are in the same + * range however, but give enough leeway that this is unlikely to explode. And of course, + * format_timestamp_relative() scales the accuracy with the distance from the current time up to one + * month, cover for that too. */ + assert_se(y > x ? y - x : x - y <= USEC_PER_MONTH + USEC_PER_DAY); + } +} + int main(int argc, char *argv[]) { uintmax_t x; @@ -214,6 +257,7 @@ int main(int argc, char *argv[]) { test_get_timezones(); test_usec_add(); test_usec_sub(); + test_format_timestamp(); /* Ensure time_t is signed */ assert_cc((time_t) -1 < (time_t) 1); diff --git a/src/test/test-tmpfiles.c b/src/test/test-tmpfiles.c index b34ebeefb2..f35e6793b7 100644 --- a/src/test/test-tmpfiles.c +++ b/src/test/test-tmpfiles.c @@ -51,7 +51,7 @@ int main(int argc, char** argv) { log_debug("link1: %s", ans); assert_se(endswith(ans, " (deleted)")); - fd2 = mkostemp_safe(pattern, O_RDWR|O_CLOEXEC); + fd2 = mkostemp_safe(pattern); assert_se(fd >= 0); assert_se(unlink(pattern) == 0); diff --git a/src/test/test-unit-file.c b/src/test/test-unit-file.c index ade0ff2a63..12f48bf435 100644 --- a/src/test/test-unit-file.c +++ b/src/test/test-unit-file.c @@ -56,12 +56,12 @@ static int test_unit_file_get_set(void) { r = unit_file_get_list(UNIT_FILE_SYSTEM, NULL, h, NULL, NULL); if (r == -EPERM || r == -EACCES) { - printf("Skipping test: unit_file_get_list: %s", strerror(-r)); + log_notice_errno(r, "Skipping test: unit_file_get_list: %m"); return EXIT_TEST_SKIP; } - log_full(r == 0 ? LOG_INFO : LOG_ERR, - "unit_file_get_list: %s", strerror(-r)); + log_full_errno(r == 0 ? LOG_INFO : LOG_ERR, r, + "unit_file_get_list: %m"); if (r < 0) return EXIT_FAILURE; @@ -117,7 +117,7 @@ static void test_config_parse_exec(void) { r = manager_new(UNIT_FILE_USER, true, &m); if (MANAGER_SKIP_TEST(r)) { - printf("Skipping test: manager_new: %s\n", strerror(-r)); + log_notice_errno(r, "Skipping test: manager_new: %m"); return; } @@ -485,7 +485,7 @@ static void test_load_env_file_1(void) { char name[] = "/tmp/test-load-env-file.XXXXXX"; _cleanup_close_ int fd; - fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(name); assert_se(fd >= 0); assert_se(write(fd, env_file_1, sizeof(env_file_1)) == sizeof(env_file_1)); @@ -508,7 +508,7 @@ static void test_load_env_file_2(void) { char name[] = "/tmp/test-load-env-file.XXXXXX"; _cleanup_close_ int fd; - fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(name); assert_se(fd >= 0); assert_se(write(fd, env_file_2, sizeof(env_file_2)) == sizeof(env_file_2)); @@ -526,7 +526,7 @@ static void test_load_env_file_3(void) { char name[] = "/tmp/test-load-env-file.XXXXXX"; _cleanup_close_ int fd; - fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(name); assert_se(fd >= 0); assert_se(write(fd, env_file_3, sizeof(env_file_3)) == sizeof(env_file_3)); @@ -542,7 +542,7 @@ static void test_load_env_file_4(void) { _cleanup_close_ int fd; int r; - fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(name); assert_se(fd >= 0); assert_se(write(fd, env_file_4, sizeof(env_file_4)) == sizeof(env_file_4)); @@ -562,7 +562,7 @@ static void test_load_env_file_5(void) { char name[] = "/tmp/test-load-env-file.XXXXXX"; _cleanup_close_ int fd; - fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC); + fd = mkostemp_safe(name); assert_se(fd >= 0); assert_se(write(fd, env_file_5, sizeof(env_file_5)) == sizeof(env_file_5)); @@ -589,7 +589,7 @@ static void test_install_printf(void) { assert_se(specifier_machine_id('m', NULL, NULL, &mid) >= 0 && mid); assert_se(specifier_boot_id('b', NULL, NULL, &bid) >= 0 && bid); assert_se((host = gethostname_malloc())); - assert_se((user = getusername_malloc())); + assert_se((user = uid_to_name(getuid()))); assert_se(asprintf(&uid, UID_FMT, getuid()) >= 0); #define expect(src, pattern, result) \ diff --git a/src/test/test-user-util.c b/src/test/test-user-util.c index 8d1ec19f17..2a344a9f93 100644 --- a/src/test/test-user-util.c +++ b/src/test/test-user-util.c @@ -61,6 +61,88 @@ static void test_uid_ptr(void) { assert_se(PTR_TO_UID(UID_TO_PTR(1000)) == 1000); } +static void test_valid_user_group_name(void) { + assert_se(!valid_user_group_name(NULL)); + assert_se(!valid_user_group_name("")); + assert_se(!valid_user_group_name("1")); + assert_se(!valid_user_group_name("65535")); + assert_se(!valid_user_group_name("-1")); + assert_se(!valid_user_group_name("-kkk")); + assert_se(!valid_user_group_name("rööt")); + assert_se(!valid_user_group_name(".")); + assert_se(!valid_user_group_name("eff.eff")); + assert_se(!valid_user_group_name("foo\nbar")); + assert_se(!valid_user_group_name("0123456789012345678901234567890123456789")); + assert_se(!valid_user_group_name_or_id("aaa:bbb")); + + assert_se(valid_user_group_name("root")); + assert_se(valid_user_group_name("lennart")); + assert_se(valid_user_group_name("LENNART")); + assert_se(valid_user_group_name("_kkk")); + assert_se(valid_user_group_name("kkk-")); + assert_se(valid_user_group_name("kk-k")); + + assert_se(valid_user_group_name("some5")); + assert_se(!valid_user_group_name("5some")); + assert_se(valid_user_group_name("INNER5NUMBER")); +} + +static void test_valid_user_group_name_or_id(void) { + assert_se(!valid_user_group_name_or_id(NULL)); + assert_se(!valid_user_group_name_or_id("")); + assert_se(valid_user_group_name_or_id("0")); + assert_se(valid_user_group_name_or_id("1")); + assert_se(valid_user_group_name_or_id("65534")); + assert_se(!valid_user_group_name_or_id("65535")); + assert_se(valid_user_group_name_or_id("65536")); + assert_se(!valid_user_group_name_or_id("-1")); + assert_se(!valid_user_group_name_or_id("-kkk")); + assert_se(!valid_user_group_name_or_id("rööt")); + assert_se(!valid_user_group_name_or_id(".")); + assert_se(!valid_user_group_name_or_id("eff.eff")); + assert_se(!valid_user_group_name_or_id("foo\nbar")); + assert_se(!valid_user_group_name_or_id("0123456789012345678901234567890123456789")); + assert_se(!valid_user_group_name_or_id("aaa:bbb")); + + assert_se(valid_user_group_name_or_id("root")); + assert_se(valid_user_group_name_or_id("lennart")); + assert_se(valid_user_group_name_or_id("LENNART")); + assert_se(valid_user_group_name_or_id("_kkk")); + assert_se(valid_user_group_name_or_id("kkk-")); + assert_se(valid_user_group_name_or_id("kk-k")); + + assert_se(valid_user_group_name_or_id("some5")); + assert_se(!valid_user_group_name_or_id("5some")); + assert_se(valid_user_group_name_or_id("INNER5NUMBER")); +} + +static void test_valid_gecos(void) { + + assert_se(!valid_gecos(NULL)); + assert_se(valid_gecos("")); + assert_se(valid_gecos("test")); + assert_se(valid_gecos("Ãœmläüt")); + assert_se(!valid_gecos("In\nvalid")); + assert_se(!valid_gecos("In:valid")); +} + +static void test_valid_home(void) { + + assert_se(!valid_home(NULL)); + assert_se(!valid_home("")); + assert_se(!valid_home(".")); + assert_se(!valid_home("/home/..")); + assert_se(!valid_home("/home/../")); + assert_se(!valid_home("/home\n/foo")); + assert_se(!valid_home("./piep")); + assert_se(!valid_home("piep")); + assert_se(!valid_home("/home/user:lennart")); + + assert_se(valid_home("/")); + assert_se(valid_home("/home")); + assert_se(valid_home("/home/foo")); +} + int main(int argc, char*argv[]) { test_uid_to_name_one(0, "root"); @@ -75,5 +157,10 @@ int main(int argc, char*argv[]) { test_parse_uid(); test_uid_ptr(); + test_valid_user_group_name(); + test_valid_user_group_name_or_id(); + test_valid_gecos(); + test_valid_home(); + return 0; } diff --git a/src/timedate/timedated.c b/src/timedate/timedated.c index ffec609c69..490929e93b 100644 --- a/src/timedate/timedated.c +++ b/src/timedate/timedated.c @@ -637,7 +637,7 @@ static int method_set_ntp(sd_bus_message *m, void *userdata, sd_bus_error *error return r; c->use_ntp = enabled; - log_info("Set NTP to %s", enabled ? "enabled" : "disabled"); + log_info("Set NTP to %sd", enable_disable(enabled)); (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL); diff --git a/src/timesync/timesyncd-conf.c b/src/timesync/timesyncd-conf.c index 20c64a3354..bf25b112e1 100644 --- a/src/timesync/timesyncd-conf.c +++ b/src/timesync/timesyncd-conf.c @@ -98,7 +98,7 @@ int config_parse_servers( int manager_parse_config_file(Manager *m) { assert(m); - return config_parse_many(PKGSYSCONFDIR "/timesyncd.conf", + return config_parse_many_nulstr(PKGSYSCONFDIR "/timesyncd.conf", CONF_PATHS_NULSTR("systemd/timesyncd.conf.d"), "Time\0", config_item_perf_lookup, timesyncd_gperf_lookup, diff --git a/src/timesync/timesyncd-server.c b/src/timesync/timesyncd-server.c index 6bda86fe6e..57a7bf2c25 100644 --- a/src/timesync/timesyncd-server.c +++ b/src/timesync/timesyncd-server.c @@ -61,8 +61,7 @@ ServerAddress* server_address_free(ServerAddress *a) { manager_set_server_address(a->name->manager, NULL); } - free(a); - return NULL; + return mfree(a); } int server_name_new( @@ -137,9 +136,7 @@ ServerName *server_name_free(ServerName *n) { log_debug("Removed server %s.", n->string); free(n->string); - free(n); - - return NULL; + return mfree(n); } void server_name_flush_addresses(ServerName *n) { diff --git a/src/tty-ask-password-agent/tty-ask-password-agent.c b/src/tty-ask-password-agent/tty-ask-password-agent.c index 8851af449d..b45490be1a 100644 --- a/src/tty-ask-password-agent/tty-ask-password-agent.c +++ b/src/tty-ask-password-agent/tty-ask-password-agent.c @@ -827,7 +827,7 @@ static int ask_on_consoles(int argc, char *argv[]) { break; } - if (!is_clean_exit(status.si_code, status.si_status, NULL)) + if (!is_clean_exit(status.si_code, status.si_status, EXIT_CLEAN_DAEMON, NULL)) log_error("Password agent failed with: %d", status.si_status); terminate_agents(pids); diff --git a/src/udev/collect/collect.c b/src/udev/collect/collect.c index 349585b634..0e973cd521 100644 --- a/src/udev/collect/collect.c +++ b/src/udev/collect/collect.c @@ -85,16 +85,16 @@ static void usage(void) */ static int prepare(char *dir, char *filename) { - char buf[512]; + char buf[PATH_MAX]; int r, fd; r = mkdir(dir, 0700); if (r < 0 && errno != EEXIST) return -errno; - xsprintf(buf, "%s/%s", dir, filename); + snprintf(buf, sizeof buf, "%s/%s", dir, filename); - fd = open(buf,O_RDWR|O_CREAT|O_CLOEXEC, S_IRUSR|S_IWUSR); + fd = open(buf, O_RDWR|O_CREAT|O_CLOEXEC, S_IRUSR|S_IWUSR); if (fd < 0) fprintf(stderr, "Cannot open %s: %m\n", buf); diff --git a/src/udev/net/ethtool-util.c b/src/udev/net/ethtool-util.c index c00ff79123..708a665576 100644 --- a/src/udev/net/ethtool-util.c +++ b/src/udev/net/ethtool-util.c @@ -25,6 +25,7 @@ #include "conf-parser.h" #include "ethtool-util.h" #include "log.h" +#include "socket-util.h" #include "string-table.h" #include "strxcpyx.h" #include "util.h" @@ -46,15 +47,22 @@ static const char* const wol_table[_WOL_MAX] = { DEFINE_STRING_TABLE_LOOKUP(wol, WakeOnLan); DEFINE_CONFIG_PARSE_ENUM(config_parse_wol, wol, WakeOnLan, "Failed to parse WakeOnLan setting"); +static const char* const netdev_feature_table[_NET_DEV_FEAT_MAX] = { + [NET_DEV_FEAT_GSO] = "tx-generic-segmentation", + [NET_DEV_FEAT_GRO] = "rx-gro", + [NET_DEV_FEAT_LRO] = "rx-lro", + [NET_DEV_FEAT_TSO] = "tx-tcp-segmentation", + [NET_DEV_FEAT_UFO] = "tx-udp-fragmentation", +}; + int ethtool_connect(int *ret) { int fd; assert_return(ret, -EINVAL); - fd = socket(PF_INET, SOCK_DGRAM, 0); + fd = socket_ioctl_fd(); if (fd < 0) - return -errno; - + return fd; *ret = fd; return 0; @@ -206,3 +214,112 @@ int ethtool_set_wol(int *fd, const char *ifname, WakeOnLan wol) { return 0; } + +static int ethtool_get_stringset(int *fd, struct ifreq *ifr, int stringset_id, struct ethtool_gstrings **gstrings) { + _cleanup_free_ struct ethtool_gstrings *strings = NULL; + struct { + struct ethtool_sset_info info; + uint32_t space; + } buffer = { + .info = { + .cmd = ETHTOOL_GSSET_INFO, + .sset_mask = UINT64_C(1) << stringset_id, + }, + }; + unsigned len; + int r; + + ifr->ifr_data = (void *) &buffer.info; + + r = ioctl(*fd, SIOCETHTOOL, ifr); + if (r < 0) + return -errno; + + if (!buffer.info.sset_mask) + return -EINVAL; + + len = buffer.info.data[0]; + + strings = malloc0(sizeof(struct ethtool_gstrings) + len * ETH_GSTRING_LEN); + if (!strings) + return -ENOMEM; + + strings->cmd = ETHTOOL_GSTRINGS; + strings->string_set = stringset_id; + strings->len = len; + + ifr->ifr_data = (void *) strings; + + r = ioctl(*fd, SIOCETHTOOL, ifr); + if (r < 0) + return -errno; + + *gstrings = strings; + strings = NULL; + + return 0; +} + +static int find_feature_index(struct ethtool_gstrings *strings, const char *feature) { + unsigned i; + + for (i = 0; i < strings->len; i++) { + if (streq((char *) &strings->data[i * ETH_GSTRING_LEN], feature)) + return i; + } + + return -1; +} + +int ethtool_set_features(int *fd, const char *ifname, NetDevFeature *features) { + _cleanup_free_ struct ethtool_gstrings *strings = NULL; + struct ethtool_sfeatures *sfeatures; + int block, bit, i, r; + struct ifreq ifr = {}; + + if (*fd < 0) { + r = ethtool_connect(fd); + if (r < 0) + return log_warning_errno(r, "link_config: could not connect to ethtool: %m"); + } + + strscpy(ifr.ifr_name, IFNAMSIZ, ifname); + + r = ethtool_get_stringset(fd, &ifr, ETH_SS_FEATURES, &strings); + if (r < 0) + return log_warning_errno(r, "link_config: could not get ethtool features for %s", ifname); + + sfeatures = alloca0(sizeof(struct ethtool_gstrings) + DIV_ROUND_UP(strings->len, 32U) * sizeof(sfeatures->features[0])); + sfeatures->cmd = ETHTOOL_SFEATURES; + sfeatures->size = DIV_ROUND_UP(strings->len, 32U); + + for (i = 0; i < _NET_DEV_FEAT_MAX; i++) { + + if (features[i] != -1) { + + r = find_feature_index(strings, netdev_feature_table[i]); + if (r < 0) { + log_warning_errno(r, "link_config: could not find feature: %s", netdev_feature_table[i]); + continue; + } + + block = r / 32; + bit = r % 32; + + sfeatures->features[block].valid |= 1 << bit; + + if (features[i]) + sfeatures->features[block].requested |= 1 << bit; + else + sfeatures->features[block].requested &= ~(1 << bit); + } + } + + ifr.ifr_data = (void *) sfeatures; + + r = ioctl(*fd, SIOCETHTOOL, &ifr); + if (r < 0) + return log_warning_errno(r, "link_config: could not set ethtool features for %s", ifname); + + return 0; +} diff --git a/src/udev/net/ethtool-util.h b/src/udev/net/ethtool-util.h index 7716516e76..0744164653 100644 --- a/src/udev/net/ethtool-util.h +++ b/src/udev/net/ethtool-util.h @@ -38,11 +38,22 @@ typedef enum WakeOnLan { _WOL_INVALID = -1 } WakeOnLan; +typedef enum NetDevFeature { + NET_DEV_FEAT_GSO, + NET_DEV_FEAT_GRO, + NET_DEV_FEAT_LRO, + NET_DEV_FEAT_TSO, + NET_DEV_FEAT_UFO, + _NET_DEV_FEAT_MAX, + _NET_DEV_FEAT_INVALID = -1 +} NetDevFeature; + int ethtool_connect(int *ret); int ethtool_get_driver(int *fd, const char *ifname, char **ret); int ethtool_set_speed(int *fd, const char *ifname, unsigned int speed, Duplex duplex); int ethtool_set_wol(int *fd, const char *ifname, WakeOnLan wol); +int ethtool_set_features(int *fd, const char *ifname, NetDevFeature *features); const char *duplex_to_string(Duplex d) _const_; Duplex duplex_from_string(const char *d) _pure_; diff --git a/src/udev/net/link-config-gperf.gperf b/src/udev/net/link-config-gperf.gperf index b25e4b3344..f8b85cbd13 100644 --- a/src/udev/net/link-config-gperf.gperf +++ b/src/udev/net/link-config-gperf.gperf @@ -16,22 +16,27 @@ struct ConfigPerfItem; %struct-type %includes %% -Match.MACAddress, config_parse_hwaddr, 0, offsetof(link_config, match_mac) -Match.OriginalName, config_parse_ifnames, 0, offsetof(link_config, match_name) -Match.Path, config_parse_strv, 0, offsetof(link_config, match_path) -Match.Driver, config_parse_strv, 0, offsetof(link_config, match_driver) -Match.Type, config_parse_strv, 0, offsetof(link_config, match_type) -Match.Host, config_parse_net_condition, CONDITION_HOST, offsetof(link_config, match_host) -Match.Virtualization, config_parse_net_condition, CONDITION_VIRTUALIZATION, offsetof(link_config, match_virt) -Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(link_config, match_kernel) -Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(link_config, match_arch) -Link.Description, config_parse_string, 0, offsetof(link_config, description) -Link.MACAddressPolicy, config_parse_mac_policy, 0, offsetof(link_config, mac_policy) -Link.MACAddress, config_parse_hwaddr, 0, offsetof(link_config, mac) -Link.NamePolicy, config_parse_name_policy, 0, offsetof(link_config, name_policy) -Link.Name, config_parse_ifname, 0, offsetof(link_config, name) -Link.Alias, config_parse_ifalias, 0, offsetof(link_config, alias) -Link.MTUBytes, config_parse_iec_size, 0, offsetof(link_config, mtu) -Link.BitsPerSecond, config_parse_si_size, 0, offsetof(link_config, speed) -Link.Duplex, config_parse_duplex, 0, offsetof(link_config, duplex) -Link.WakeOnLan, config_parse_wol, 0, offsetof(link_config, wol) +Match.MACAddress, config_parse_hwaddr, 0, offsetof(link_config, match_mac) +Match.OriginalName, config_parse_ifnames, 0, offsetof(link_config, match_name) +Match.Path, config_parse_strv, 0, offsetof(link_config, match_path) +Match.Driver, config_parse_strv, 0, offsetof(link_config, match_driver) +Match.Type, config_parse_strv, 0, offsetof(link_config, match_type) +Match.Host, config_parse_net_condition, CONDITION_HOST, offsetof(link_config, match_host) +Match.Virtualization, config_parse_net_condition, CONDITION_VIRTUALIZATION, offsetof(link_config, match_virt) +Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(link_config, match_kernel) +Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(link_config, match_arch) +Link.Description, config_parse_string, 0, offsetof(link_config, description) +Link.MACAddressPolicy, config_parse_mac_policy, 0, offsetof(link_config, mac_policy) +Link.MACAddress, config_parse_hwaddr, 0, offsetof(link_config, mac) +Link.NamePolicy, config_parse_name_policy, 0, offsetof(link_config, name_policy) +Link.Name, config_parse_ifname, 0, offsetof(link_config, name) +Link.Alias, config_parse_ifalias, 0, offsetof(link_config, alias) +Link.MTUBytes, config_parse_iec_size, 0, offsetof(link_config, mtu) +Link.BitsPerSecond, config_parse_si_size, 0, offsetof(link_config, speed) +Link.Duplex, config_parse_duplex, 0, offsetof(link_config, duplex) +Link.WakeOnLan, config_parse_wol, 0, offsetof(link_config, wol) +Link.GenericSegmentationOffload, config_parse_tristate, 0, offsetof(link_config, features[NET_DEV_FEAT_GSO]) +Link.TCPSegmentationOffload, config_parse_tristate, 0, offsetof(link_config, features[NET_DEV_FEAT_TSO]) +Link.UDPSegmentationOffload, config_parse_tristate, 0, offsetof(link_config, features[NET_DEV_FEAT_UFO]) +Link.GenericReceiveOffload, config_parse_tristate, 0, offsetof(link_config, features[NET_DEV_FEAT_GRO]) +Link.LargeReceiveOffload, config_parse_tristate, 0, offsetof(link_config, features[NET_DEV_FEAT_LRO]) diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c index c66504102f..ece9248c2a 100644 --- a/src/udev/net/link-config.c +++ b/src/udev/net/link-config.c @@ -168,6 +168,8 @@ static int load_link(link_config_ctx *ctx, const char *filename) { link->wol = _WOL_INVALID; link->duplex = _DUP_INVALID; + memset(&link->features, -1, _NET_DEV_FEAT_MAX); + r = config_parse(NULL, filename, file, "Match\0Link\0Ethernet\0", config_item_perf_lookup, link_config_gperf_lookup, @@ -189,20 +191,12 @@ static int load_link(link_config_ctx *ctx, const char *filename) { } static bool enable_name_policy(void) { - _cleanup_free_ char *line = NULL; - const char *word, *state; + _cleanup_free_ char *value = NULL; int r; - size_t l; - r = proc_cmdline(&line); - if (r < 0) { - log_warning_errno(r, "Failed to read /proc/cmdline, ignoring: %m"); - return true; - } - - FOREACH_WORD_QUOTED(word, l, line, state) - if (strneq(word, "net.ifnames=0", l)) - return false; + r = get_proc_cmdline_key("net.ifnames=", &value); + if (r > 0 && streq(value, "0")) + return false; return true; } @@ -397,6 +391,10 @@ int link_config_apply(link_config_ctx *ctx, link_config *config, log_warning_errno(r, "Could not set WakeOnLan of %s to %s: %m", old_name, wol_to_string(config->wol)); + r = ethtool_set_features(&ctx->ethtool_fd, old_name, config->features); + if (r < 0) + log_warning_errno(r, "Could not set offload features of %s: %m", old_name); + ifindex = udev_device_get_ifindex(device); if (ifindex <= 0) { log_warning("Could not find ifindex"); diff --git a/src/udev/net/link-config.h b/src/udev/net/link-config.h index 9df5529d05..91cc0357c4 100644 --- a/src/udev/net/link-config.h +++ b/src/udev/net/link-config.h @@ -70,6 +70,7 @@ struct link_config { size_t speed; Duplex duplex; WakeOnLan wol; + NetDevFeature features[_NET_DEV_FEAT_MAX]; LIST_FIELDS(link_config, links); }; diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c index a7be2a4eed..fe9d6f4482 100644 --- a/src/udev/udev-builtin-net_id.c +++ b/src/udev/udev-builtin-net_id.c @@ -35,10 +35,12 @@ * Type of names: * b<number> — BCMA bus core number * c<bus_id> — CCW bus group name, without leading zeros [s390] - * o<index>[d<dev_port>] — on-board device index number - * s<slot>[f<function>][d<dev_port>] — hotplug slot index number + * o<index>[n<phys_port_name>|d<dev_port>] + * — on-board device index number + * s<slot>[f<function>][n<phys_port_name>|d<dev_port>] + * — hotplug slot index number * x<MAC> — MAC address - * [P<domain>]p<bus>s<slot>[f<function>][d<dev_port>] + * [P<domain>]p<bus>s<slot>[f<function>][n<phys_port_name>|d<dev_port>] * — PCI geographical location * [P<domain>]p<bus>s<slot>[f<function>][u<port>][..][c<config>][i<interface>] * — USB port number chain @@ -137,7 +139,7 @@ static int dev_pci_onboard(struct udev_device *dev, struct netnames *names) { unsigned dev_port = 0; size_t l; char *s; - const char *attr; + const char *attr, *port_name; int idx; /* ACPI _DSM — device specific method for naming a PCI or PCI Express device */ @@ -164,10 +166,15 @@ static int dev_pci_onboard(struct udev_device *dev, struct netnames *names) { if (attr) dev_port = strtol(attr, NULL, 10); + /* kernel provided front panel port name for multiple port PCI device */ + port_name = udev_device_get_sysattr_value(dev, "phys_port_name"); + s = names->pci_onboard; l = sizeof(names->pci_onboard); l = strpcpyf(&s, l, "o%d", idx); - if (dev_port > 0) + if (port_name) + l = strpcpyf(&s, l, "n%s", port_name); + else if (dev_port > 0) l = strpcpyf(&s, l, "d%d", dev_port); if (l == 0) names->pci_onboard[0] = '\0'; @@ -202,9 +209,9 @@ static int dev_pci_slot(struct udev_device *dev, struct netnames *names) { unsigned domain, bus, slot, func, dev_port = 0; size_t l; char *s; - const char *attr; + const char *attr, *port_name; struct udev_device *pci = NULL; - char slots[256], str[256]; + char slots[PATH_MAX]; _cleanup_closedir_ DIR *dir = NULL; struct dirent *dent; int hotplug_slot = 0, err = 0; @@ -217,6 +224,9 @@ static int dev_pci_slot(struct udev_device *dev, struct netnames *names) { if (attr) dev_port = strtol(attr, NULL, 10); + /* kernel provided front panel port name for multiple port PCI device */ + port_name = udev_device_get_sysattr_value(dev, "phys_port_name"); + /* compose a name based on the raw kernel's PCI bus, slot numbers */ s = names->pci_path; l = sizeof(names->pci_path); @@ -225,7 +235,9 @@ static int dev_pci_slot(struct udev_device *dev, struct netnames *names) { l = strpcpyf(&s, l, "p%us%u", bus, slot); if (func > 0 || is_pci_multifunction(names->pcidev)) l = strpcpyf(&s, l, "f%u", func); - if (dev_port > 0) + if (port_name) + l = strpcpyf(&s, l, "n%s", port_name); + else if (dev_port > 0) l = strpcpyf(&s, l, "d%u", dev_port); if (l == 0) names->pci_path[0] = '\0'; @@ -236,7 +248,8 @@ static int dev_pci_slot(struct udev_device *dev, struct netnames *names) { err = -ENOENT; goto out; } - xsprintf(slots, "%s/slots", udev_device_get_syspath(pci)); + + snprintf(slots, sizeof slots, "%s/slots", udev_device_get_syspath(pci)); dir = opendir(slots); if (!dir) { err = -errno; @@ -245,8 +258,7 @@ static int dev_pci_slot(struct udev_device *dev, struct netnames *names) { for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) { int i; - char *rest; - char *address; + char *rest, *address, str[PATH_MAX]; if (dent->d_name[0] == '.') continue; @@ -255,7 +267,8 @@ static int dev_pci_slot(struct udev_device *dev, struct netnames *names) { continue; if (i < 1) continue; - xsprintf(str, "%s/%s/address", slots, dent->d_name); + + snprintf(str, sizeof str, "%s/%s/address", slots, dent->d_name); if (read_one_line_file(str, &address) >= 0) { /* match slot address with device by stripping the function */ if (strneq(address, udev_device_get_sysname(names->pcidev), strlen(address))) @@ -275,7 +288,9 @@ static int dev_pci_slot(struct udev_device *dev, struct netnames *names) { l = strpcpyf(&s, l, "s%d", hotplug_slot); if (func > 0 || is_pci_multifunction(names->pcidev)) l = strpcpyf(&s, l, "f%d", func); - if (dev_port > 0) + if (port_name) + l = strpcpyf(&s, l, "n%s", port_name); + else if (dev_port > 0) l = strpcpyf(&s, l, "d%d", dev_port); if (l == 0) names->pci_slot[0] = '\0'; diff --git a/src/udev/udev-builtin-path_id.c b/src/udev/udev-builtin-path_id.c index 6e9adc6e96..1825ee75a7 100644 --- a/src/udev/udev-builtin-path_id.c +++ b/src/udev/udev-builtin-path_id.c @@ -693,6 +693,15 @@ static int builtin_path_id(struct udev_device *dev, int argc, char *argv[], bool parent = skip_subsystem(parent, "iucv"); supported_transport = true; supported_parent = true; + } else if (streq(subsys, "nvme")) { + const char *nsid = udev_device_get_sysattr_value(dev, "nsid"); + + if (nsid) { + path_prepend(&path, "nvme-%s", nsid); + parent = skip_subsystem(parent, "nvme"); + supported_parent = true; + supported_transport = true; + } } if (parent) diff --git a/src/udev/udev-ctrl.c b/src/udev/udev-ctrl.c index f68a09d7a8..7717ac7924 100644 --- a/src/udev/udev-ctrl.c +++ b/src/udev/udev-ctrl.c @@ -211,8 +211,7 @@ struct udev_ctrl_connection *udev_ctrl_get_connection(struct udev_ctrl *uctrl) { err: if (conn->sock >= 0) close(conn->sock); - free(conn); - return NULL; + return mfree(conn); } struct udev_ctrl_connection *udev_ctrl_connection_ref(struct udev_ctrl_connection *conn) { diff --git a/src/udev/udev-node.c b/src/udev/udev-node.c index 5d2997fd8f..43004bc0bc 100644 --- a/src/udev/udev-node.c +++ b/src/udev/udev-node.c @@ -337,7 +337,7 @@ out: void udev_node_add(struct udev_device *dev, bool apply, mode_t mode, uid_t uid, gid_t gid, struct udev_list *seclabel_list) { - char filename[UTIL_PATH_SIZE]; + char filename[sizeof("/dev/block/:") + 2*DECIMAL_STR_MAX(unsigned)]; struct udev_list_entry *list_entry; log_debug("handling device node '%s', devnum=%s, mode=%#o, uid="UID_FMT", gid="GID_FMT, @@ -360,7 +360,7 @@ void udev_node_add(struct udev_device *dev, bool apply, void udev_node_remove(struct udev_device *dev) { struct udev_list_entry *list_entry; - char filename[UTIL_PATH_SIZE]; + char filename[sizeof("/dev/block/:") + 2*DECIMAL_STR_MAX(unsigned)]; /* remove/update symlinks, remove symlinks from name index */ udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev)) diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c index 26fa52cf6c..f6c416bf70 100644 --- a/src/udev/udev-rules.c +++ b/src/udev/udev-rules.c @@ -1583,8 +1583,7 @@ struct udev_rules *udev_rules_unref(struct udev_rules *rules) { strbuf_cleanup(rules->strbuf); free(rules->uids); free(rules->gids); - free(rules); - return NULL; + return mfree(rules); } bool udev_rules_check_timestamp(struct udev_rules *rules) { @@ -2219,10 +2218,16 @@ void udev_rules_apply_to_event(struct udev_rules *rules, rule->rule.filename_line); break; case TK_A_SECLABEL: { + char label_str[UTIL_LINE_SIZE] = {}; const char *name, *label; name = rules_str(rules, cur->key.attr_off); - label = rules_str(rules, cur->key.value_off); + udev_event_apply_format(event, rules_str(rules, cur->key.value_off), label_str, sizeof(label_str)); + if (label_str[0] != '\0') + label = label_str; + else + label = rules_str(rules, cur->key.value_off); + if (cur->key.op == OP_ASSIGN || cur->key.op == OP_ASSIGN_FINAL) udev_list_cleanup(&event->seclabel_list); udev_list_entry_add(&event->seclabel_list, name, label); diff --git a/src/udev/udev-watch.c b/src/udev/udev-watch.c index 9ce5e975de..bc9096ed0c 100644 --- a/src/udev/udev-watch.c +++ b/src/udev/udev-watch.c @@ -89,7 +89,7 @@ unlink: } void udev_watch_begin(struct udev *udev, struct udev_device *dev) { - char filename[UTIL_PATH_SIZE]; + char filename[sizeof("/run/udev/watch/") + DECIMAL_STR_MAX(int)]; int wd; int r; @@ -116,7 +116,7 @@ void udev_watch_begin(struct udev *udev, struct udev_device *dev) { void udev_watch_end(struct udev *udev, struct udev_device *dev) { int wd; - char filename[UTIL_PATH_SIZE]; + char filename[sizeof("/run/udev/watch/") + DECIMAL_STR_MAX(int)]; if (inotify_fd < 0) return; @@ -135,7 +135,7 @@ void udev_watch_end(struct udev *udev, struct udev_device *dev) { } struct udev_device *udev_watch_lookup(struct udev *udev, int wd) { - char filename[UTIL_PATH_SIZE]; + char filename[sizeof("/run/udev/watch/") + DECIMAL_STR_MAX(int)]; char device[UTIL_NAME_SIZE]; ssize_t len; diff --git a/src/udev/udevadm-control.c b/src/udev/udevadm-control.c index 989decbe95..6f8e96a123 100644 --- a/src/udev/udevadm-control.c +++ b/src/udev/udevadm-control.c @@ -20,6 +20,7 @@ #include <string.h> #include <unistd.h> +#include "time-util.h" #include "udev-util.h" #include "udev.h" @@ -60,7 +61,7 @@ static int adm_control(struct udev *udev, int argc, char *argv[]) { }; if (getuid() != 0) { - fprintf(stderr, "root privileges required\n"); + log_error("root privileges required"); return 1; } @@ -81,7 +82,7 @@ static int adm_control(struct udev *udev, int argc, char *argv[]) { i = util_log_priority(optarg); if (i < 0) { - fprintf(stderr, "invalid number '%s'\n", optarg); + log_error("invalid number '%s'", optarg); return rc; } if (udev_ctrl_send_set_log_level(uctrl, util_log_priority(optarg), timeout) < 0) @@ -110,7 +111,7 @@ static int adm_control(struct udev *udev, int argc, char *argv[]) { break; case 'p': if (strchr(optarg, '=') == NULL) { - fprintf(stderr, "expect <KEY>=<value> instead of '%s'\n", optarg); + log_error("expect <KEY>=<value> instead of '%s'", optarg); return rc; } if (udev_ctrl_send_set_env(uctrl, optarg, timeout) < 0) @@ -124,7 +125,7 @@ static int adm_control(struct udev *udev, int argc, char *argv[]) { i = strtoul(optarg, &endp, 0); if (endp[0] != '\0' || i < 1) { - fprintf(stderr, "invalid number '%s'\n", optarg); + log_error("invalid number '%s'", optarg); return rc; } if (udev_ctrl_send_set_children_max(uctrl, i, timeout) < 0) @@ -134,13 +135,21 @@ static int adm_control(struct udev *udev, int argc, char *argv[]) { break; } case 't': { + usec_t s; int seconds; + int r; - seconds = atoi(optarg); - if (seconds >= 0) + r = parse_sec(optarg, &s); + if (r < 0) + return log_error_errno(r, "Failed to parse timeout value '%s'.", optarg); + + if (((s + USEC_PER_SEC - 1) / USEC_PER_SEC) > INT_MAX) + log_error("Timeout value is out of range."); + else { + seconds = s != USEC_INFINITY ? (int) ((s + USEC_PER_SEC - 1) / USEC_PER_SEC) : INT_MAX; timeout = seconds; - else - fprintf(stderr, "invalid timeout value\n"); + rc = 0; + } break; } case 'h': @@ -150,9 +159,9 @@ static int adm_control(struct udev *udev, int argc, char *argv[]) { } if (optind < argc) - fprintf(stderr, "Extraneous argument: %s\n", argv[optind]); + log_error("Extraneous argument: %s", argv[optind]); else if (optind == 1) - fprintf(stderr, "Option missing\n"); + log_error("Option missing"); return rc; } diff --git a/src/udev/udevd.c b/src/udev/udevd.c index a893a2b3d9..badbab6205 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -776,9 +776,9 @@ static void manager_reload(Manager *manager) { manager->rules = udev_rules_unref(manager->rules); udev_builtin_exit(manager->udev); - sd_notify(false, - "READY=1\n" - "STATUS=Processing..."); + sd_notifyf(false, + "READY=1\n" + "STATUS=Processing with %u children at max", arg_children_max); } static void event_queue_start(Manager *manager) { @@ -1000,6 +1000,10 @@ static int on_ctrl_msg(sd_event_source *s, int fd, uint32_t revents, void *userd if (i >= 0) { log_debug("udevd message (SET_MAX_CHILDREN) received, children_max=%i", i); arg_children_max = i; + + (void) sd_notifyf(false, + "READY=1\n" + "STATUS=Processing with %u children at max", arg_children_max); } if (udev_ctrl_get_ping(ctrl_msg) > 0) @@ -1358,49 +1362,33 @@ static int listen_fds(int *rctrl, int *rnetlink) { * udev.exec-delay=<number of seconds> delay execution of every executed program * udev.event-timeout=<number of seconds> seconds to wait before terminating an event */ -static int parse_proc_cmdline_item(const char *key, const char *value) { - const char *full_key = key; - int r; +static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { + int r = 0; assert(key); if (!value) return 0; - if (startswith(key, "rd.")) - key += strlen("rd."); - - if (startswith(key, "udev.")) - key += strlen("udev."); - else - return 0; - - if (streq(key, "log-priority")) { - int prio; - - prio = util_log_priority(value); - if (prio < 0) - goto invalid; - log_set_max_level(prio); - } else if (streq(key, "children-max")) { + if (streq(key, "udev.log-priority") && value) { + r = util_log_priority(value); + if (r >= 0) + log_set_max_level(r); + } else if (streq(key, "udev.event-timeout") && value) { + r = safe_atou64(value, &arg_event_timeout_usec); + if (r >= 0) { + arg_event_timeout_usec *= USEC_PER_SEC; + arg_event_timeout_warn_usec = (arg_event_timeout_usec / 3) ? : 1; + } + } else if (streq(key, "udev.children-max") && value) r = safe_atou(value, &arg_children_max); - if (r < 0) - goto invalid; - } else if (streq(key, "exec-delay")) { + else if (streq(key, "udev.exec-delay") && value) r = safe_atoi(value, &arg_exec_delay); - if (r < 0) - goto invalid; - } else if (streq(key, "event-timeout")) { - r = safe_atou64(value, &arg_event_timeout_usec); - if (r < 0) - goto invalid; - arg_event_timeout_usec *= USEC_PER_SEC; - arg_event_timeout_warn_usec = (arg_event_timeout_usec / 3) ? : 1; - } + else if (startswith(key, "udev.")) + log_warning("Unknown udev kernel command line option \"%s\"", key); - return 0; -invalid: - log_warning("invalid %s ignored: %s", full_key, value); + if (r < 0) + log_warning_errno(r, "Failed to parse \"%s=%s\", ignoring: %m", key, value); return 0; } @@ -1627,9 +1615,9 @@ static int run(int fd_ctrl, int fd_uevent, const char *cgroup) { if (r < 0) log_error_errno(r, "failed to apply permissions on static device nodes: %m"); - (void) sd_notify(false, - "READY=1\n" - "STATUS=Processing..."); + (void) sd_notifyf(false, + "READY=1\n" + "STATUS=Processing with %u children at max", arg_children_max); r = sd_event_loop(manager->event); if (r < 0) { @@ -1661,7 +1649,7 @@ int main(int argc, char *argv[]) { if (r <= 0) goto exit; - r = parse_proc_cmdline(parse_proc_cmdline_item); + r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, true); if (r < 0) log_warning_errno(r, "failed to parse kernel command line, ignoring: %m"); @@ -1734,8 +1722,13 @@ int main(int argc, char *argv[]) { log_info("starting version " VERSION); /* connect /dev/null to stdin, stdout, stderr */ - if (log_get_max_level() < LOG_DEBUG) - (void) make_null_stdio(); + if (log_get_max_level() < LOG_DEBUG) { + r = make_null_stdio(); + if (r < 0) + log_warning_errno(r, "Failed to redirect standard streams to /dev/null: %m"); + } + + pid = fork(); switch (pid) { diff --git a/src/update-done/update-done.c b/src/update-done/update-done.c index da306a4444..48c2a3fff4 100644 --- a/src/update-done/update-done.c +++ b/src/update-done/update-done.c @@ -18,72 +18,65 @@ ***/ #include "fd-util.h" +#include "fileio.h" #include "io-util.h" #include "selinux-util.h" #include "util.h" #define MESSAGE \ - "This file was created by systemd-update-done. Its only \n" \ - "purpose is to hold a timestamp of the time this directory\n" \ - "was updated. See systemd-update-done.service(8).\n" + "# This file was created by systemd-update-done. Its only \n" \ + "# purpose is to hold a timestamp of the time this directory\n" \ + "# was updated. See systemd-update-done.service(8).\n" static int apply_timestamp(const char *path, struct timespec *ts) { struct timespec twice[2] = { *ts, *ts }; - struct stat st; + _cleanup_fclose_ FILE *f = NULL; + int fd = -1; + int r; assert(path); assert(ts); - if (stat(path, &st) >= 0) { - /* Is the timestamp file already newer than the OS? If - * so, there's nothing to do. We ignore the nanosecond - * component of the timestamp, since some file systems - * do not support any better accuracy than 1s and we - * have no way to identify the accuracy - * available. Most notably ext4 on small disks (where - * 128 byte inodes are used) does not support better - * accuracy than 1s. */ - if (st.st_mtim.tv_sec > ts->tv_sec) - return 0; - - /* It is older? Then let's update it */ - if (utimensat(AT_FDCWD, path, twice, AT_SYMLINK_NOFOLLOW) < 0) { - - if (errno == EROFS) - return log_debug("Can't update timestamp file %s, file system is read-only.", path); + /* + * We store the timestamp both as mtime of the file and in the file itself, + * to support filesystems which cannot store nanosecond-precision timestamps. + * Hence, don't bother updating the file, let's just rewrite it. + */ - return log_error_errno(errno, "Failed to update timestamp on %s: %m", path); - } + r = mac_selinux_create_file_prepare(path, S_IFREG); + if (r < 0) + return log_error_errno(r, "Failed to set SELinux context for %s: %m", path); - } else if (errno == ENOENT) { - _cleanup_close_ int fd = -1; - int r; + fd = open(path, O_CREAT|O_WRONLY|O_TRUNC|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0644); + mac_selinux_create_file_clear(); - /* The timestamp file doesn't exist yet? Then let's create it. */ + if (fd < 0) { + if (errno == EROFS) + return log_debug("Can't create timestamp file %s, file system is read-only.", path); - r = mac_selinux_create_file_prepare(path, S_IFREG); - if (r < 0) - return log_error_errno(r, "Failed to set SELinux context for %s: %m", path); - - fd = open(path, O_CREAT|O_EXCL|O_WRONLY|O_TRUNC|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0644); - mac_selinux_create_file_clear(); + return log_error_errno(errno, "Failed to create/open timestamp file %s: %m", path); + } - if (fd < 0) { - if (errno == EROFS) - return log_debug("Can't create timestamp file %s, file system is read-only.", path); + f = fdopen(fd, "we"); + if (!f) { + safe_close(fd); + return log_error_errno(errno, "Failed to fdopen() timestamp file %s: %m", path); + } - return log_error_errno(errno, "Failed to create timestamp file %s: %m", path); - } + (void) fprintf(f, + MESSAGE + "TIMESTAMP_NSEC=" NSEC_FMT "\n", + timespec_load_nsec(ts)); - (void) loop_write(fd, MESSAGE, strlen(MESSAGE), false); + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write timestamp file: %m"); - if (futimens(fd, twice) < 0) - return log_error_errno(errno, "Failed to update timestamp on %s: %m", path); - } else - log_error_errno(errno, "Failed to stat() timestamp file %s: %m", path); + if (futimens(fd, twice) < 0) + return log_error_errno(errno, "Failed to update timestamp on %s: %m", path); return 0; } diff --git a/src/update-utmp/update-utmp.c b/src/update-utmp/update-utmp.c index 8ae4a8a833..a8efe8e91f 100644 --- a/src/update-utmp/update-utmp.c +++ b/src/update-utmp/update-utmp.c @@ -34,6 +34,7 @@ #include "log.h" #include "macro.h" #include "special.h" +#include "strv.h" #include "unit-name.h" #include "util.h" #include "utmp-wtmp.h" @@ -107,7 +108,7 @@ static int get_current_runlevel(Context *c) { if (r < 0) return log_warning_errno(r, "Failed to get state: %s", bus_error_message(&error, r)); - if (streq(state, "active") || streq(state, "reloading")) + if (STR_IN_SET(state, "active", "reloading")) return table[i].runlevel; } diff --git a/src/vconsole/90-vconsole.rules.in b/src/vconsole/90-vconsole.rules.in index 35b9ad5151..84b4d575bd 100644 --- a/src/vconsole/90-vconsole.rules.in +++ b/src/vconsole/90-vconsole.rules.in @@ -7,4 +7,4 @@ # Each vtcon keeps its own state of fonts. # -ACTION=="add", SUBSYSTEM=="vtconsole", KERNEL=="vtcon*", RUN+="@rootlibexecdir@/systemd-vconsole-setup" +ACTION=="add", SUBSYSTEM=="vtconsole", KERNEL=="vtcon*", ATTR{name}!="*dummy device", RUN+="@rootlibexecdir@/systemd-vconsole-setup" diff --git a/src/vconsole/vconsole-setup.c b/src/vconsole/vconsole-setup.c index 1118118450..a0ab5990fc 100644 --- a/src/vconsole/vconsole-setup.c +++ b/src/vconsole/vconsole-setup.c @@ -2,6 +2,7 @@ This file is part of systemd. Copyright 2010 Kay Sievers + Copyright 2016 Michal Soltys <soltys@ziu.info> 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 @@ -27,6 +28,7 @@ #include <stdio.h> #include <stdlib.h> #include <sys/ioctl.h> +#include <termios.h> #include <unistd.h> #include "alloc-util.h" @@ -50,67 +52,85 @@ static bool is_vconsole(int fd) { return ioctl(fd, TIOCLINUX, data) >= 0; } -static int disable_utf8(int fd) { - int r = 0, k; +static bool is_allocated(unsigned int idx) { + char vcname[strlen("/dev/vcs") + DECIMAL_STR_MAX(int)]; - if (ioctl(fd, KDSKBMODE, K_XLATE) < 0) - r = -errno; - - k = loop_write(fd, "\033%@", 3, false); - if (k < 0) - r = k; + xsprintf(vcname, "/dev/vcs%i", idx); + return access(vcname, F_OK) == 0; +} - k = write_string_file("/sys/module/vt/parameters/default_utf8", "0", 0); - if (k < 0) - r = k; +static bool is_allocated_byfd(int fd) { + struct vt_stat vcs = {}; - if (r < 0) - log_warning_errno(r, "Failed to disable UTF-8: %m"); + if (ioctl(fd, VT_GETSTATE, &vcs) < 0) { + log_warning_errno(errno, "VT_GETSTATE failed: %m"); + return false; + } + return is_allocated(vcs.v_active); +} - return r; +static bool is_settable(int fd) { + int r, curr_mode; + + r = ioctl(fd, KDGKBMODE, &curr_mode); + /* + * Make sure we only adjust consoles in K_XLATE or K_UNICODE mode. + * Otherwise we would (likely) interfere with X11's processing of the + * key events. + * + * http://lists.freedesktop.org/archives/systemd-devel/2013-February/008573.html + */ + return r == 0 && IN_SET(curr_mode, K_XLATE, K_UNICODE); } -static int enable_utf8(int fd) { - int r = 0, k; - long current = 0; - - if (ioctl(fd, KDGKBMODE, ¤t) < 0 || current == K_XLATE) { - /* - * Change the current keyboard to unicode, unless it - * is currently in raw or off mode anyway. We - * shouldn't interfere with X11's processing of the - * key events. - * - * http://lists.freedesktop.org/archives/systemd-devel/2013-February/008573.html - * - */ - - if (ioctl(fd, KDSKBMODE, K_UNICODE) < 0) - r = -errno; +static int toggle_utf8(const char *name, int fd, bool utf8) { + int r; + struct termios tc = {}; + + assert(name); + + r = ioctl(fd, KDSKBMODE, utf8 ? K_UNICODE : K_XLATE); + if (r < 0) + return log_warning_errno(errno, "Failed to %s UTF-8 kbdmode on %s: %m", enable_disable(utf8), name); + + r = loop_write(fd, utf8 ? "\033%G" : "\033%@", 3, false); + if (r < 0) + return log_warning_errno(r, "Failed to %s UTF-8 term processing on %s: %m", enable_disable(utf8), name); + + r = tcgetattr(fd, &tc); + if (r >= 0) { + if (utf8) + tc.c_iflag |= IUTF8; + else + tc.c_iflag &= ~IUTF8; + r = tcsetattr(fd, TCSANOW, &tc); } + if (r < 0) + return log_warning_errno(errno, "Failed to %s iutf8 flag on %s: %m", enable_disable(utf8), name); - k = loop_write(fd, "\033%G", 3, false); - if (k < 0) - r = k; + log_debug("UTF-8 kbdmode %sd on %s", enable_disable(utf8), name); + return 0; +} - k = write_string_file("/sys/module/vt/parameters/default_utf8", "1", 0); - if (k < 0) - r = k; +static int toggle_utf8_sysfs(bool utf8) { + int r; + r = write_string_file("/sys/module/vt/parameters/default_utf8", one_zero(utf8), 0); if (r < 0) - log_warning_errno(r, "Failed to enable UTF-8: %m"); + return log_warning_errno(r, "Failed to %s sysfs UTF-8 flag: %m", enable_disable(utf8)); - return r; + log_debug("Sysfs UTF-8 flag %sd", enable_disable(utf8)); + return 0; } static int keyboard_load_and_wait(const char *vc, const char *map, const char *map_toggle, bool utf8) { const char *args[8]; - int i = 0, r; + int i = 0; pid_t pid; /* An empty map means kernel map */ if (isempty(map)) - return 1; + return 0; args[i++] = KBD_LOADKEYS; args[i++] = "-q"; @@ -135,34 +155,31 @@ static int keyboard_load_and_wait(const char *vc, const char *map, const char *m _exit(EXIT_FAILURE); } - r = wait_for_terminate_and_warn(KBD_LOADKEYS, pid, true); - if (r < 0) - return r; - - return r == 0; + return wait_for_terminate_and_warn(KBD_LOADKEYS, pid, true); } static int font_load_and_wait(const char *vc, const char *font, const char *map, const char *unimap) { const char *args[9]; - int i = 0, r; + int i = 0; pid_t pid; - /* An empty font means kernel font */ - if (isempty(font)) - return 1; + /* Any part can be set independently */ + if (isempty(font) && isempty(map) && isempty(unimap)) + return 0; args[i++] = KBD_SETFONT; args[i++] = "-C"; args[i++] = vc; - args[i++] = font; - if (map) { + if (!isempty(map)) { args[i++] = "-m"; args[i++] = map; } - if (unimap) { + if (!isempty(unimap)) { args[i++] = "-u"; args[i++] = unimap; } + if (!isempty(font)) + args[i++] = font; args[i++] = NULL; pid = fork(); @@ -177,11 +194,7 @@ static int font_load_and_wait(const char *vc, const char *font, const char *map, _exit(EXIT_FAILURE); } - r = wait_for_terminate_and_warn(KBD_SETFONT, pid, true); - if (r < 0) - return r; - - return r == 0; + return wait_for_terminate_and_warn(KBD_SETFONT, pid, true); } /* @@ -189,13 +202,21 @@ static int font_load_and_wait(const char *vc, const char *font, const char *map, * we update all possibly already allocated VTs with the configured * font. It also allows to restart systemd-vconsole-setup.service, * to apply a new font to all VTs. + * + * We also setup per-console utf8 related stuff: kbdmode, term + * processing, stty iutf8. */ -static void font_copy_to_all_vcs(int fd) { +static void setup_remaining_vcs(int fd, bool utf8) { + struct console_font_op cfo = { + .op = KD_FONT_OP_GET, + .width = UINT_MAX, .height = UINT_MAX, + .charcount = UINT_MAX, + }; struct vt_stat vcs = {}; - unsigned char map8[E_TABSZ]; - unsigned short map16[E_TABSZ]; + struct unimapinit adv = {}; struct unimapdesc unimapd; _cleanup_free_ struct unipair* unipairs = NULL; + _cleanup_free_ void *fontbuf = NULL; int i, r; unipairs = new(struct unipair, USHRT_MAX); @@ -207,52 +228,96 @@ static void font_copy_to_all_vcs(int fd) { /* get active, and 16 bit mask of used VT numbers */ r = ioctl(fd, VT_GETSTATE, &vcs); if (r < 0) { - log_debug_errno(errno, "VT_GETSTATE failed, ignoring: %m"); + log_warning_errno(errno, "VT_GETSTATE failed, ignoring remaining consoles: %m"); return; } - for (i = 1; i <= 15; i++) { - char vcname[strlen("/dev/vcs") + DECIMAL_STR_MAX(int)]; - _cleanup_close_ int vcfd = -1; - struct console_font_op cfo = {}; + /* get metadata of the current font (width, height, count) */ + r = ioctl(fd, KDFONTOP, &cfo); + if (r < 0) + log_warning_errno(errno, "KD_FONT_OP_GET failed while trying to get the font metadata: %m"); + else { + /* verify parameter sanity first */ + if (cfo.width > 32 || cfo.height > 32 || cfo.charcount > 512) + log_warning("Invalid font metadata - width: %u (max 32), height: %u (max 32), count: %u (max 512)", + cfo.width, cfo.height, cfo.charcount); + else { + /* + * Console fonts supported by the kernel are limited in size to 32 x 32 and maximum 512 + * characters. Thus with 1 bit per pixel it requires up to 65536 bytes. The height always + * requries 32 per glyph, regardless of the actual height - see the comment above #define + * max_font_size 65536 in drivers/tty/vt/vt.c for more details. + */ + fontbuf = malloc((cfo.width + 7) / 8 * 32 * cfo.charcount); + if (!fontbuf) { + log_oom(); + return; + } + /* get fonts from source console */ + cfo.data = fontbuf; + r = ioctl(fd, KDFONTOP, &cfo); + if (r < 0) + log_warning_errno(errno, "KD_FONT_OP_GET failed while trying to read the font data: %m"); + else { + unimapd.entries = unipairs; + unimapd.entry_ct = USHRT_MAX; + r = ioctl(fd, GIO_UNIMAP, &unimapd); + if (r < 0) + log_warning_errno(errno, "GIO_UNIMAP failed while trying to read unicode mappings: %m"); + else + cfo.op = KD_FONT_OP_SET; + } + } + } + + if (cfo.op != KD_FONT_OP_SET) + log_warning("Fonts will not be copied to remaining consoles"); + + for (i = 1; i <= 63; i++) { + char ttyname[strlen("/dev/tty") + DECIMAL_STR_MAX(int)]; + _cleanup_close_ int fd_d = -1; - if (i == vcs.v_active) + if (i == vcs.v_active || !is_allocated(i)) continue; - /* skip non-allocated ttys */ - xsprintf(vcname, "/dev/vcs%i", i); - if (access(vcname, F_OK) < 0) + /* try to open terminal */ + xsprintf(ttyname, "/dev/tty%i", i); + fd_d = open_terminal(ttyname, O_RDWR|O_CLOEXEC); + if (fd_d < 0) { + log_warning_errno(fd_d, "Unable to open tty%i, fonts will not be copied: %m", i); continue; + } - xsprintf(vcname, "/dev/tty%i", i); - vcfd = open_terminal(vcname, O_RDWR|O_CLOEXEC); - if (vcfd < 0) + if (!is_settable(fd_d)) continue; - /* copy font from active VT, where the font was uploaded to */ - cfo.op = KD_FONT_OP_COPY; - cfo.height = vcs.v_active-1; /* tty1 == index 0 */ - (void) ioctl(vcfd, KDFONTOP, &cfo); + toggle_utf8(ttyname, fd_d, utf8); - /* copy map of 8bit chars */ - if (ioctl(fd, GIO_SCRNMAP, map8) >= 0) - (void) ioctl(vcfd, PIO_SCRNMAP, map8); + if (cfo.op != KD_FONT_OP_SET) + continue; - /* copy map of 8bit chars -> 16bit Unicode values */ - if (ioctl(fd, GIO_UNISCRNMAP, map16) >= 0) - (void) ioctl(vcfd, PIO_UNISCRNMAP, map16); + r = ioctl(fd_d, KDFONTOP, &cfo); + if (r < 0) { + log_warning_errno(errno, "KD_FONT_OP_SET failed, fonts will not be copied to tty%i: %m", i); + continue; + } /* copy unicode translation table */ /* unimapd is a ushort count and a pointer to an array of struct unipair { ushort, ushort } */ - unimapd.entries = unipairs; - unimapd.entry_ct = USHRT_MAX; - if (ioctl(fd, GIO_UNIMAP, &unimapd) >= 0) { - struct unimapinit adv = { 0, 0, 0 }; + r = ioctl(fd_d, PIO_UNIMAPCLR, &adv); + if (r < 0) { + log_warning_errno(errno, "PIO_UNIMAPCLR failed, unimaps might be incorrect for tty%i: %m", i); + continue; + } - (void) ioctl(vcfd, PIO_UNIMAPCLR, &adv); - (void) ioctl(vcfd, PIO_UNIMAP, &unimapd); + r = ioctl(fd_d, PIO_UNIMAP, &unimapd); + if (r < 0) { + log_warning_errno(errno, "PIO_UNIMAP failed, unimaps might be incorrect for tty%i: %m", i); + continue; } + + log_debug("Font and unimap successfully copied to %s", ttyname); } } @@ -289,6 +354,16 @@ int main(int argc, char **argv) { return EXIT_FAILURE; } + if (!is_allocated_byfd(fd)) { + log_error("Virtual console %s is not allocated.", vc); + return EXIT_FAILURE; + } + + if (!is_settable(fd)) { + log_error("Virtual console %s is not in K_XLATE or K_UNICODE.", vc); + return EXIT_FAILURE; + } + utf8 = is_locale_utf8(); r = parse_env_file("/etc/vconsole.conf", NEWLINE, @@ -306,8 +381,12 @@ int main(int argc, char **argv) { if (detect_container() <= 0) { r = parse_env_file("/proc/cmdline", WHITESPACE, "vconsole.keymap", &vc_keymap, - "vconsole.keymap.toggle", &vc_keymap_toggle, + "vconsole.keymap_toggle", &vc_keymap_toggle, "vconsole.font", &vc_font, + "vconsole.font_map", &vc_font_map, + "vconsole.font_unimap", &vc_font_unimap, + /* compatibility with obsolete multiple-dot scheme */ + "vconsole.keymap.toggle", &vc_keymap_toggle, "vconsole.font.map", &vc_font_map, "vconsole.font.unimap", &vc_font_unimap, NULL); @@ -316,17 +395,17 @@ int main(int argc, char **argv) { log_warning_errno(r, "Failed to read /proc/cmdline: %m"); } - if (utf8) - (void) enable_utf8(fd); - else - (void) disable_utf8(fd); + toggle_utf8_sysfs(utf8); + toggle_utf8(vc, fd, utf8); + font_ok = font_load_and_wait(vc, vc_font, vc_font_map, vc_font_unimap) == 0; + keyboard_ok = keyboard_load_and_wait(vc, vc_keymap, vc_keymap_toggle, utf8) == 0; - font_ok = font_load_and_wait(vc, vc_font, vc_font_map, vc_font_unimap) > 0; - keyboard_ok = keyboard_load_and_wait(vc, vc_keymap, vc_keymap_toggle, utf8) > 0; - - /* Only copy the font when we executed setfont successfully */ - if (font_copy && font_ok) - (void) font_copy_to_all_vcs(fd); + if (font_copy) { + if (font_ok) + setup_remaining_vcs(fd, utf8); + else + log_warning("Setting source virtual console failed, ignoring remaining ones"); + } return font_ok && keyboard_ok ? EXIT_SUCCESS : EXIT_FAILURE; } |