diff options
Diffstat (limited to 'src/basic')
56 files changed, 1586 insertions, 334 deletions
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_; |