diff options
author | Martin Pitt <mpitt@debian.org> | 2017-03-02 10:44:39 +0100 |
---|---|---|
committer | Martin Pitt <mpitt@debian.org> | 2017-03-02 10:44:39 +0100 |
commit | 2897b343851c95927e26f45bea8c40da605dbed1 (patch) | |
tree | c15ec2f4b562d39a818acc5d65ae58944791dba9 /src/basic | |
parent | 8a584da2774aca0b14c8aacef574e93d943d470e (diff) | |
download | systemd-2897b343851c95927e26f45bea8c40da605dbed1.tar.gz |
New upstream version 233
Diffstat (limited to 'src/basic')
76 files changed, 3355 insertions, 1036 deletions
diff --git a/src/basic/MurmurHash2.c b/src/basic/MurmurHash2.c index 9020793930..a282a21201 100644 --- a/src/basic/MurmurHash2.c +++ b/src/basic/MurmurHash2.c @@ -69,9 +69,9 @@ uint32_t MurmurHash2 ( const void * key, int len, uint32_t seed ) switch(len) { - case 3: h ^= data[2] << 16; - case 2: h ^= data[1] << 8; - case 1: h ^= data[0]; + case 3: h ^= data[2] << 16; /* fall through */ + case 2: h ^= data[1] << 8; /* fall through */ + case 1: h ^= data[0]; /* fall through */ h *= m; }; diff --git a/src/basic/af-list.c b/src/basic/af-list.c index 3fac9c508b..4b291d177b 100644 --- a/src/basic/af-list.c +++ b/src/basic/af-list.c @@ -23,7 +23,7 @@ #include "af-list.h" #include "macro.h" -static const struct af_name* lookup_af(register const char *str, register unsigned int len); +static const struct af_name* lookup_af(register const char *str, register GPERF_LEN_TYPE len); #include "af-from-name.h" #include "af-to-name.h" diff --git a/src/basic/architecture.c b/src/basic/architecture.c index b74dc0db78..5a3dc08a4a 100644 --- a/src/basic/architecture.c +++ b/src/basic/architecture.c @@ -123,7 +123,8 @@ int uname_architecture(void) { { "crisv32", ARCHITECTURE_CRIS }, #elif defined(__nios2__) { "nios2", ARCHITECTURE_NIOS2 }, -#elif defined(__riscv__) +#elif defined(__riscv__) || defined(__riscv) + /* __riscv__ is obsolete, remove in 2018 */ { "riscv32", ARCHITECTURE_RISCV32 }, { "riscv64", ARCHITECTURE_RISCV64 }, # if __SIZEOF_POINTER__ == 4 diff --git a/src/basic/architecture.h b/src/basic/architecture.h index 5a77c31932..46883719d1 100644 --- a/src/basic/architecture.h +++ b/src/basic/architecture.h @@ -124,13 +124,21 @@ int uname_architecture(void); #elif defined(__sparc__) # define native_architecture() ARCHITECTURE_SPARC # define LIB_ARCH_TUPLE "sparc-linux-gnu" -#elif defined(__mips64__) +#elif defined(__mips64) && defined(__LP64__) # if __BYTE_ORDER == __BIG_ENDIAN # define native_architecture() ARCHITECTURE_MIPS64 -# error "Missing LIB_ARCH_TUPLE for MIPS64" +# define LIB_ARCH_TUPLE "mips64-linux-gnuabi64" # else # define native_architecture() ARCHITECTURE_MIPS64_LE -# error "Missing LIB_ARCH_TUPLE for MIPS64_LE" +# define LIB_ARCH_TUPLE "mips64el-linux-gnuabi64" +# endif +#elif defined(__mips64) +# if __BYTE_ORDER == __BIG_ENDIAN +# define native_architecture() ARCHITECTURE_MIPS64 +# define LIB_ARCH_TUPLE "mips64-linux-gnuabin32" +# else +# define native_architecture() ARCHITECTURE_MIPS64_LE +# define LIB_ARCH_TUPLE "mips64el-linux-gnuabin32" # endif #elif defined(__mips__) # if __BYTE_ORDER == __BIG_ENDIAN @@ -150,6 +158,7 @@ int uname_architecture(void); # else # define native_architecture() ARCHITECTURE_ARM64 # define LIB_ARCH_TUPLE "aarch64-linux-gnu" +# define SECONDARY_ARCHITECTURE ARCHITECTURE_ARM # endif #elif defined(__arm__) # if __BYTE_ORDER == __BIG_ENDIAN @@ -186,14 +195,15 @@ int uname_architecture(void); # define LIB_ARCH_TUPLE "m68k-linux-gnu" #elif defined(__tilegx__) # define native_architecture() ARCHITECTURE_TILEGX -# error "Missing LIB_ARCH_TUPLE for TILEGX" +# define LIB_ARCH_TUPLE "tilegx-linux-gnu" #elif defined(__cris__) # define native_architecture() ARCHITECTURE_CRIS # error "Missing LIB_ARCH_TUPLE for CRIS" #elif defined(__nios2__) # define native_architecture() ARCHITECTURE_NIOS2 # define LIB_ARCH_TUPLE "nios2-linux-gnu" -#elif defined(__riscv__) +#elif defined(__riscv__) || defined(__riscv) + /* __riscv__ is obsolete, remove in 2018 */ # if __SIZEOF_POINTER__ == 4 # define native_architecture() ARCHITECTURE_RISCV32 # define LIB_ARCH_TUPLE "riscv32-linux-gnu" diff --git a/src/basic/arphrd-list.c b/src/basic/arphrd-list.c index 6792d1ee3f..2d598dc66f 100644 --- a/src/basic/arphrd-list.c +++ b/src/basic/arphrd-list.c @@ -23,7 +23,7 @@ #include "arphrd-list.h" #include "macro.h" -static const struct arphrd_name* lookup_arphrd(register const char *str, register unsigned int len); +static const struct arphrd_name* lookup_arphrd(register const char *str, register GPERF_LEN_TYPE len); #include "arphrd-from-name.h" #include "arphrd-to-name.h" diff --git a/src/basic/btrfs-util.c b/src/basic/btrfs-util.c index 359d85f2e8..5505499312 100644 --- a/src/basic/btrfs-util.c +++ b/src/basic/btrfs-util.c @@ -20,6 +20,7 @@ #include <errno.h> #include <fcntl.h> #include <inttypes.h> +#include <linux/fs.h> #include <linux/loop.h> #include <stddef.h> #include <stdio.h> @@ -38,6 +39,7 @@ #include "alloc-util.h" #include "btrfs-ctree.h" #include "btrfs-util.h" +#include "chattr-util.h" #include "copy.h" #include "fd-util.h" #include "fileio.h" @@ -45,6 +47,7 @@ #include "macro.h" #include "missing.h" #include "path-util.h" +#include "rm-rf.h" #include "selinux-util.h" #include "smack-util.h" #include "sparse-endian.h" @@ -1642,7 +1645,7 @@ static int subvol_snapshot_children(int old_fd, int new_fd, const char *subvolum if (old_child_fd < 0) return -errno; - np = strjoin(subvolume, "/", ino_args.name, NULL); + np = strjoin(subvolume, "/", ino_args.name); if (!np) return -ENOMEM; @@ -1718,28 +1721,46 @@ int btrfs_subvol_snapshot_fd(int old_fd, const char *new_path, BtrfsSnapshotFlag if (r < 0) return r; if (r == 0) { + bool plain_directory = false; + + /* If the source isn't a proper subvolume, fail unless fallback is requested */ if (!(flags & BTRFS_SNAPSHOT_FALLBACK_COPY)) return -EISDIR; r = btrfs_subvol_make(new_path); - if (r < 0) - return r; + if (r == -ENOTTY && (flags & BTRFS_SNAPSHOT_FALLBACK_DIRECTORY)) { + /* If the destination doesn't support subvolumes, then use a plain directory, if that's requested. */ + if (mkdir(new_path, 0755) < 0) + return r; - r = copy_directory_fd(old_fd, new_path, true); - if (r < 0) { - (void) btrfs_subvol_remove(new_path, BTRFS_REMOVE_QUOTA); + plain_directory = true; + } else if (r < 0) return r; - } + + r = copy_directory_fd(old_fd, new_path, COPY_MERGE|COPY_REFLINK); + if (r < 0) + goto fallback_fail; if (flags & BTRFS_SNAPSHOT_READ_ONLY) { - r = btrfs_subvol_set_read_only(new_path, true); - if (r < 0) { - (void) btrfs_subvol_remove(new_path, BTRFS_REMOVE_QUOTA); - return r; + + if (plain_directory) { + /* Plain directories have no recursive read-only flag, but something pretty close to + * it: the IMMUTABLE bit. Let's use this here, if this is requested. */ + + if (flags & BTRFS_SNAPSHOT_FALLBACK_IMMUTABLE) + (void) chattr_path(new_path, FS_IMMUTABLE_FL, FS_IMMUTABLE_FL); + } else { + r = btrfs_subvol_set_read_only(new_path, true); + if (r < 0) + goto fallback_fail; } } return 0; + + fallback_fail: + (void) rm_rf(new_path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); + return r; } r = extract_subvolume_name(new_path, &subvolume); diff --git a/src/basic/btrfs-util.h b/src/basic/btrfs-util.h index 1d852d502c..04a2e1274b 100644 --- a/src/basic/btrfs-util.h +++ b/src/basic/btrfs-util.h @@ -45,10 +45,12 @@ typedef struct BtrfsQuotaInfo { } BtrfsQuotaInfo; typedef enum BtrfsSnapshotFlags { - BTRFS_SNAPSHOT_FALLBACK_COPY = 1, + BTRFS_SNAPSHOT_FALLBACK_COPY = 1, /* If the source isn't a subvolume, reflink everything */ BTRFS_SNAPSHOT_READ_ONLY = 2, BTRFS_SNAPSHOT_RECURSIVE = 4, BTRFS_SNAPSHOT_QUOTA = 8, + BTRFS_SNAPSHOT_FALLBACK_DIRECTORY = 16, /* If the destination doesn't support subvolumes, reflink/copy instead */ + BTRFS_SNAPSHOT_FALLBACK_IMMUTABLE = 32, /* When we can't create a subvolume, use the FS_IMMUTABLE attribute for indicating read-only */ } BtrfsSnapshotFlags; typedef enum BtrfsRemoveFlags { diff --git a/src/basic/build.h b/src/basic/build.h index 633c2aaccb..91312bd2a3 100644 --- a/src/basic/build.h +++ b/src/basic/build.h @@ -133,6 +133,8 @@ #define _IDN_FEATURE_ "-IDN" #endif +#define _CGROUP_HIEARCHY_ "default-hierarchy=" DEFAULT_HIERARCHY_NAME + #define SYSTEMD_FEATURES \ _PAM_FEATURE_ " " \ _AUDIT_FEATURE_ " " \ @@ -152,4 +154,5 @@ _BLKID_FEATURE_ " " \ _ELFUTILS_FEATURE_ " " \ _KMOD_FEATURE_ " " \ - _IDN_FEATURE_ + _IDN_FEATURE_ " " \ + _CGROUP_HIEARCHY_ diff --git a/src/basic/calendarspec.c b/src/basic/calendarspec.c index fda293fcb9..2323eb8555 100644 --- a/src/basic/calendarspec.c +++ b/src/basic/calendarspec.c @@ -18,7 +18,9 @@ ***/ #include <alloca.h> +#include <ctype.h> #include <errno.h> +#include <limits.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> @@ -32,9 +34,9 @@ #include "parse-util.h" #include "string-util.h" -/* Longest valid date/time range is 1970..2199 */ -#define MAX_RANGE_LEN 230 -#define BITS_WEEKDAYS 127 +#define BITS_WEEKDAYS 127 +#define MIN_YEAR 1970 +#define MAX_YEAR 2199 static void free_chain(CalendarComponent *c) { CalendarComponent *n; @@ -64,9 +66,14 @@ void calendar_spec_free(CalendarSpec *c) { static int component_compare(const void *_a, const void *_b) { CalendarComponent * const *a = _a, * const *b = _b; - if ((*a)->value < (*b)->value) + if ((*a)->start < (*b)->start) return -1; - if ((*a)->value > (*b)->value) + if ((*a)->start > (*b)->start) + return 1; + + if ((*a)->stop < (*b)->stop) + return -1; + if ((*a)->stop > (*b)->stop) return 1; if ((*a)->repeat < (*b)->repeat) @@ -77,15 +84,24 @@ static int component_compare(const void *_a, const void *_b) { return 0; } -static void sort_chain(CalendarComponent **c) { +static void normalize_chain(CalendarComponent **c) { unsigned n = 0, k; CalendarComponent **b, *i, **j, *next; assert(c); - for (i = *c; i; i = i->next) + for (i = *c; i; i = i->next) { n++; + /* + * While we're counting the chain, also normalize `stop` + * so the length of the range is a multiple of `repeat` + */ + if (i->stop > i->start && i->repeat > 0) + i->stop -= (i->stop - i->start) % i->repeat; + + } + if (n <= 1) return; @@ -100,8 +116,7 @@ static void sort_chain(CalendarComponent **c) { /* Drop non-unique entries */ for (k = n-1; k > 0; k--) { - if (b[k-1]->value == next->value && - b[k-1]->repeat == next->repeat) { + if (component_compare(&b[k-1], &next) == 0) { free(b[k-1]); continue; } @@ -117,15 +132,19 @@ static void fix_year(CalendarComponent *c) { /* Turns 12 → 2012, 89 → 1989 */ while (c) { - CalendarComponent *n = c->next; + if (c->start >= 0 && c->start < 70) + c->start += 2000; - if (c->value >= 0 && c->value < 70) - c->value += 2000; + if (c->stop >= 0 && c->stop < 70) + c->stop += 2000; - if (c->value >= 70 && c->value < 100) - c->value += 1900; + if (c->start >= 70 && c->start < 100) + c->start += 1900; - c = n; + if (c->stop >= 70 && c->stop < 100) + c->stop += 1900; + + c = c->next; } } @@ -135,30 +154,54 @@ int calendar_spec_normalize(CalendarSpec *c) { if (c->weekdays_bits <= 0 || c->weekdays_bits >= BITS_WEEKDAYS) c->weekdays_bits = -1; + if (c->end_of_month && !c->day) + c->end_of_month = false; + fix_year(c->year); - sort_chain(&c->year); - sort_chain(&c->month); - sort_chain(&c->day); - sort_chain(&c->hour); - sort_chain(&c->minute); - sort_chain(&c->microsecond); + normalize_chain(&c->year); + normalize_chain(&c->month); + normalize_chain(&c->day); + normalize_chain(&c->hour); + normalize_chain(&c->minute); + normalize_chain(&c->microsecond); return 0; } -_pure_ static bool chain_valid(CalendarComponent *c, int from, int to) { +_pure_ static bool chain_valid(CalendarComponent *c, int from, int to, bool end_of_month) { if (!c) return true; - if (c->value < from || c->value > to) - return false; + /* Forbid dates more than 28 days from the end of the month */ + if (end_of_month) + to -= 3; - if (c->value + c->repeat > to) + if (c->start < from || c->start > to) return false; + /* + * c->repeat must be short enough so at least one repetition may + * occur before the end of the interval. For dates scheduled + * relative to the end of the month, c->start and c->stop + * correspond to the Nth last day of the month. + */ + if (c->stop >= 0) { + if (c->stop < from || c ->stop > to) + return false; + + if (c->start + c->repeat > c->stop) + return false; + } else { + if (end_of_month && c->start - c->repeat < from) + return false; + + if (!end_of_month && c->start + c->repeat > to) + return false; + } + if (c->next) - return chain_valid(c->next, from, to); + return chain_valid(c->next, from, to, end_of_month); return true; } @@ -169,22 +212,22 @@ _pure_ bool calendar_spec_valid(CalendarSpec *c) { if (c->weekdays_bits > BITS_WEEKDAYS) return false; - if (!chain_valid(c->year, 1970, 2199)) + if (!chain_valid(c->year, MIN_YEAR, MAX_YEAR, false)) return false; - if (!chain_valid(c->month, 1, 12)) + if (!chain_valid(c->month, 1, 12, false)) return false; - if (!chain_valid(c->day, 1, 31)) + if (!chain_valid(c->day, 1, 31, c->end_of_month)) return false; - if (!chain_valid(c->hour, 0, 23)) + if (!chain_valid(c->hour, 0, 23, false)) return false; - if (!chain_valid(c->minute, 0, 59)) + if (!chain_valid(c->minute, 0, 59, false)) return false; - if (!chain_valid(c->microsecond, 0, 60*USEC_PER_SEC-1)) + if (!chain_valid(c->microsecond, 0, 60*USEC_PER_SEC-1, false)) return false; return true; @@ -240,6 +283,8 @@ static void format_weekdays(FILE *f, const CalendarSpec *c) { } static void format_chain(FILE *f, int space, const CalendarComponent *c, bool usec) { + int d = usec ? (int) USEC_PER_SEC : 1; + assert(f); if (!c) { @@ -247,23 +292,27 @@ static void format_chain(FILE *f, int space, const CalendarComponent *c, bool us return; } - assert(c->value >= 0); - if (!usec) - fprintf(f, "%0*i", space, c->value); - else if (c->value % USEC_PER_SEC == 0) - fprintf(f, "%0*i", space, (int) (c->value / USEC_PER_SEC)); - else - fprintf(f, "%0*i.%06i", space, (int) (c->value / USEC_PER_SEC), (int) (c->value % USEC_PER_SEC)); - - if (c->repeat > 0) { - if (!usec) - fprintf(f, "/%i", c->repeat); - else if (c->repeat % USEC_PER_SEC == 0) - fprintf(f, "/%i", (int) (c->repeat / USEC_PER_SEC)); - else - fprintf(f, "/%i.%06i", (int) (c->repeat / USEC_PER_SEC), (int) (c->repeat % USEC_PER_SEC)); + if (usec && c->start == 0 && c->repeat == USEC_PER_SEC && !c->next) { + fputc('*', f); + return; } + assert(c->start >= 0); + + fprintf(f, "%0*i", space, c->start / d); + if (c->start % d > 0) + fprintf(f, ".%06i", c->start % d); + + if (c->stop > 0) + fprintf(f, "..%0*i", space, c->stop / d); + if (c->stop % d > 0) + fprintf(f, ".%06i", c->stop % d); + + if (c->repeat > 0 && !(c->stop > 0 && c->repeat == d)) + fprintf(f, "/%i", c->repeat / d); + if (c->repeat % d > 0) + fprintf(f, ".%06i", c->repeat % d); + if (c->next) { fputc(',', f); format_chain(f, space, c->next, usec); @@ -291,7 +340,7 @@ int calendar_spec_to_string(const CalendarSpec *c, char **p) { format_chain(f, 4, c->year, false); fputc('-', f); format_chain(f, 2, c->month, false); - fputc('-', f); + fputc(c->end_of_month ? '~' : '-', f); format_chain(f, 2, c->day, false); fputc(' ', f); format_chain(f, 2, c->hour, false); @@ -358,9 +407,6 @@ static int parse_weekdays(const char **p, CalendarSpec *c) { for (;;) { unsigned i; - if (!first && **p == ' ') - return 0; - for (i = 0; i < ELEMENTSOF(day_nr); i++) { size_t skip; @@ -416,7 +462,7 @@ static int parse_weekdays(const char **p, CalendarSpec *c) { return -EINVAL; l = day_nr[i].nr; - *p += 1; + *p += 2; /* Support ranges with "-" for backwards compatibility */ } else if (**p == '-') { @@ -424,28 +470,38 @@ static int parse_weekdays(const char **p, CalendarSpec *c) { return -EINVAL; l = day_nr[i].nr; - } else + *p += 1; + + } else if (**p == ',') { l = -1; + *p += 1; + } + + /* Allow a trailing comma but not an open range */ + if (**p == 0 || **p == ' ') { + *p += strspn(*p, " "); + return l < 0 ? 0 : -EINVAL; + } - *p += 1; first = false; } } -static int parse_component_decimal(const char **p, bool usec, unsigned long *res) { +static int parse_component_decimal(const char **p, bool usec, int *res) { unsigned long value; const char *e = NULL; char *ee = NULL; int r; + if (!isdigit(**p)) + return -EINVAL; + errno = 0; value = strtoul(*p, &ee, 10); if (errno > 0) return -errno; if (ee == *p) return -EINVAL; - if ((unsigned long) (int) value != value) - return -ERANGE; e = ee; if (usec) { @@ -453,7 +509,9 @@ static int parse_component_decimal(const char **p, bool usec, unsigned long *res return -ERANGE; value *= USEC_PER_SEC; - if (*e == '.') { + + /* One "." is a decimal point, but ".." is a range separator */ + if (e[0] == '.' && e[1] != '.') { unsigned add; e++; @@ -467,6 +525,9 @@ static int parse_component_decimal(const char **p, bool usec, unsigned long *res } } + if (value > INT_MAX) + return -ERANGE; + *p = e; *res = value; @@ -482,7 +543,8 @@ static int const_chain(int value, CalendarComponent **c) { if (!cc) return -ENOMEM; - cc->value = value; + cc->start = value; + cc->stop = -1; cc->repeat = 0; cc->next = *c; @@ -492,9 +554,8 @@ static int const_chain(int value, CalendarComponent **c) { } static int prepend_component(const char **p, bool usec, CalendarComponent **c) { - unsigned long i, value, range_end, range_inc, repeat = 0; + int r, start, stop = -1, repeat = 0; CalendarComponent *cc; - int r; const char *e; assert(p); @@ -502,10 +563,19 @@ static int prepend_component(const char **p, bool usec, CalendarComponent **c) { e = *p; - r = parse_component_decimal(&e, usec, &value); + r = parse_component_decimal(&e, usec, &start); if (r < 0) return r; + if (e[0] == '.' && e[1] == '.') { + e += 2; + r = parse_component_decimal(&e, usec, &stop); + if (r < 0) + return r; + + repeat = usec ? USEC_PER_SEC : 1; + } + if (*e == '/') { e++; r = parse_component_decimal(&e, usec, &repeat); @@ -514,40 +584,17 @@ static int prepend_component(const char **p, bool usec, CalendarComponent **c) { if (repeat == 0) return -ERANGE; - } else if (e[0] == '.' && e[1] == '.') { - e += 2; - r = parse_component_decimal(&e, usec, &range_end); - if (r < 0) - return r; - - if (value >= range_end) - return -EINVAL; - - range_inc = usec ? USEC_PER_SEC : 1; - - /* Don't allow impossibly large ranges... */ - if (range_end - value >= MAX_RANGE_LEN * range_inc) - return -EINVAL; - - /* ...or ranges with only a single element */ - if (range_end - value < range_inc) - return -EINVAL; - - for (i = value; i <= range_end; i += range_inc) { - r = const_chain(i, c); - if (r < 0) - return r; - } } - if (*e != 0 && *e != ' ' && *e != ',' && *e != '-' && *e != ':') + if (*e != 0 && *e != ' ' && *e != ',' && *e != '-' && *e != '~' && *e != ':') return -EINVAL; cc = new0(CalendarComponent, 1); if (!cc) return -ENOMEM; - cc->value = value; + cc->start = start; + cc->stop = stop; cc->repeat = repeat; cc->next = *c; @@ -620,7 +667,9 @@ static int parse_date(const char **p, CalendarSpec *c) { return 0; } - if (*t != '-') { + if (*t == '~') + c->end_of_month = true; + else if (*t != '-') { free_chain(first); return -EINVAL; } @@ -638,9 +687,15 @@ static int parse_date(const char **p, CalendarSpec *c) { c->month = first; c->day = second; return 0; + } else if (c->end_of_month) { + free_chain(first); + free_chain(second); + return -EINVAL; } - if (*t != '-') { + if (*t == '~') + c->end_of_month = true; + else if (*t != '-') { free_chain(first); free_chain(second); return -EINVAL; @@ -654,7 +709,7 @@ static int parse_date(const char **p, CalendarSpec *c) { return r; } - /* Got tree parts, hence it is year, month and day */ + /* Got three parts, hence it is year, month and day */ if (*t == ' ' || *t == 0) { *p = t + strspn(t, " "); c->year = first; @@ -680,14 +735,9 @@ static int parse_calendar_time(const char **p, CalendarSpec *c) { t = *p; - if (*t == 0) { - /* If no time is specified at all, but a date of some - * kind, then this means 00:00:00 */ - if (c->day || c->weekdays_bits > 0) - goto null_hour; - - goto finish; - } + /* If no time is specified at all, then this means 00:00:00 */ + if (*t == 0) + goto null_hour; r = parse_chain(&t, false, &h); if (r < 0) @@ -704,12 +754,8 @@ static int parse_calendar_time(const char **p, CalendarSpec *c) { goto fail; /* Already at the end? Then it's hours and minutes, and seconds are 0 */ - if (*t == 0) { - if (m != NULL) - goto null_second; - - goto finish; - } + if (*t == 0) + goto null_second; if (*t != ':') { r = -EINVAL; @@ -765,9 +811,6 @@ int calendar_spec_from_string(const char *p, CalendarSpec **spec) { assert(p); assert(spec); - if (isempty(p)) - return -EINVAL; - c = new0(CalendarSpec, 1); if (!c) return -ENOMEM; @@ -806,6 +849,11 @@ int calendar_spec_from_string(const char *p, CalendarSpec **spec) { } } + if (isempty(p)) { + r = -EINVAL; + goto fail; + } + if (strcaseeq(p, "minutely")) { r = const_chain(0, &c->microsecond); if (r < 0) @@ -965,9 +1013,23 @@ fail: return r; } -static int find_matching_component(const CalendarComponent *c, int *val) { - const CalendarComponent *n; - int d = -1; +static int find_end_of_month(struct tm *tm, bool utc, int day) { + struct tm t = *tm; + + t.tm_mon++; + t.tm_mday = 1 - day; + + if (mktime_or_timegm(&t, utc) < 0 || + t.tm_mon != tm->tm_mon) + return -1; + + return t.tm_mday; +} + +static int find_matching_component(const CalendarSpec *spec, const CalendarComponent *c, + struct tm *tm, int *val) { + const CalendarComponent *p = c; + int start, stop, d = -1; bool d_set = false; int r; @@ -977,27 +1039,36 @@ static int find_matching_component(const CalendarComponent *c, int *val) { return 0; while (c) { - n = c->next; + start = c->start; + stop = c->stop; + + if (spec->end_of_month && p == spec->day) { + start = find_end_of_month(tm, spec->utc, start); + stop = find_end_of_month(tm, spec->utc, stop); + + if (stop > 0) + SWAP_TWO(start, stop); + } - if (c->value >= *val) { + if (start >= *val) { - if (!d_set || c->value < d) { - d = c->value; + if (!d_set || start < d) { + d = start; d_set = true; } } else if (c->repeat > 0) { int k; - k = c->value + c->repeat * ((*val - c->value + c->repeat -1) / c->repeat); + k = start + c->repeat * ((*val - start + c->repeat - 1) / c->repeat); - if (!d_set || k < d) { + if ((!d_set || k < d) && (stop < 0 || k <= stop)) { d = k; d_set = true; } } - c = n; + c = c->next; } if (!d_set) @@ -1014,7 +1085,15 @@ static bool tm_out_of_bounds(const struct tm *tm, bool utc) { t = *tm; - if (mktime_or_timegm(&t, utc) == (time_t) -1) + if (mktime_or_timegm(&t, utc) < 0) + return true; + + /* + * Set an upper bound on the year so impossible dates like "*-02-31" + * don't cause find_next() to loop forever. tm_year contains years + * since 1900, so adjust it accordingly. + */ + if (tm->tm_year + 1900 > MAX_YEAR) return true; /* Did any normalization take place? If so, it was out of bounds before */ @@ -1035,7 +1114,7 @@ static bool matches_weekday(int weekdays_bits, const struct tm *tm, bool utc) { return true; t = *tm; - if (mktime_or_timegm(&t, utc) == (time_t) -1) + if (mktime_or_timegm(&t, utc) < 0) return false; k = t.tm_wday == 0 ? 6 : t.tm_wday - 1; @@ -1059,7 +1138,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) { c.tm_isdst = spec->dst; c.tm_year += 1900; - r = find_matching_component(spec->year, &c.tm_year); + r = find_matching_component(spec, spec->year, &c, &c.tm_year); c.tm_year -= 1900; if (r > 0) { @@ -1073,7 +1152,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) { return -ENOENT; c.tm_mon += 1; - r = find_matching_component(spec->month, &c.tm_mon); + r = find_matching_component(spec, spec->month, &c, &c.tm_mon); c.tm_mon -= 1; if (r > 0) { @@ -1088,7 +1167,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) { continue; } - r = find_matching_component(spec->day, &c.tm_mday); + r = find_matching_component(spec, spec->day, &c, &c.tm_mday); if (r > 0) c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { @@ -1104,7 +1183,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) { continue; } - r = find_matching_component(spec->hour, &c.tm_hour); + r = find_matching_component(spec, spec->hour, &c, &c.tm_hour); if (r > 0) c.tm_min = c.tm_sec = tm_usec = 0; if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { @@ -1113,7 +1192,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) { continue; } - r = find_matching_component(spec->minute, &c.tm_min); + r = find_matching_component(spec, spec->minute, &c, &c.tm_min); if (r > 0) c.tm_sec = tm_usec = 0; if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { @@ -1123,7 +1202,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) { } c.tm_sec = c.tm_sec * USEC_PER_SEC + tm_usec; - r = find_matching_component(spec->microsecond, &c.tm_sec); + r = find_matching_component(spec, spec->microsecond, &c, &c.tm_sec); tm_usec = c.tm_sec % USEC_PER_SEC; c.tm_sec /= USEC_PER_SEC; @@ -1148,6 +1227,9 @@ int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next) assert(spec); assert(next); + if (usec > USEC_TIMESTAMP_FORMATTABLE_MAX) + return -EINVAL; + usec++; t = (time_t) (usec / USEC_PER_SEC); assert_se(localtime_or_gmtime_r(&t, &tm, spec->utc)); @@ -1158,7 +1240,7 @@ int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next) return r; t = mktime_or_timegm(&tm, spec->utc); - if (t == (time_t) -1) + if (t < 0) return -EINVAL; *next = (usec_t) t * USEC_PER_SEC + tm_usec; diff --git a/src/basic/calendarspec.h b/src/basic/calendarspec.h index c6087228fd..3d8798de0b 100644 --- a/src/basic/calendarspec.h +++ b/src/basic/calendarspec.h @@ -28,7 +28,8 @@ #include "util.h" typedef struct CalendarComponent { - int value; + int start; + int stop; int repeat; struct CalendarComponent *next; @@ -36,6 +37,7 @@ typedef struct CalendarComponent { typedef struct CalendarSpec { int weekdays_bits; + bool end_of_month; bool utc; int dst; diff --git a/src/basic/cap-list.c b/src/basic/cap-list.c index 3e773a06f5..d68cc78d05 100644 --- a/src/basic/cap-list.c +++ b/src/basic/cap-list.c @@ -26,7 +26,7 @@ #include "parse-util.h" #include "util.h" -static const struct capability_name* lookup_capability(register const char *str, register unsigned int len); +static const struct capability_name* lookup_capability(register const char *str, register GPERF_LEN_TYPE len); #include "cap-from-name.h" #include "cap-to-name.h" diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c index cede835920..bda5c555ad 100644 --- a/src/basic/cgroup-util.c +++ b/src/basic/cgroup-util.c @@ -38,7 +38,7 @@ #include "extract-word.h" #include "fd-util.h" #include "fileio.h" -#include "formats-util.h" +#include "format-util.h" #include "fs-util.h" #include "log.h" #include "login-util.h" @@ -182,8 +182,7 @@ int cg_read_subgroup(DIR *d, char **fn) { if (de->d_type != DT_DIR) continue; - if (streq(de->d_name, ".") || - streq(de->d_name, "..")) + if (dot_or_dot_dot(de->d_name)) continue; b = strdup(de->d_name); @@ -209,6 +208,18 @@ int cg_rmdir(const char *controller, const char *path) { if (r < 0 && errno != ENOENT) return -errno; + r = cg_hybrid_unified(); + if (r < 0) + return r; + if (r == 0) + return 0; + + if (streq(controller, SYSTEMD_CGROUP_CONTROLLER)) { + r = cg_rmdir(SYSTEMD_CGROUP_CONTROLLER_LEGACY, path); + if (r < 0) + log_warning_errno(r, "Failed to remove compat systemd cgroup %s: %m", path); + } + return 0; } @@ -345,7 +356,7 @@ int cg_kill_recursive( while ((r = cg_read_subgroup(d, &fn)) > 0) { _cleanup_free_ char *p = NULL; - p = strjoin(path, "/", fn, NULL); + p = strjoin(path, "/", fn); free(fn); if (!p) return -ENOMEM; @@ -479,7 +490,7 @@ int cg_migrate_recursive( while ((r = cg_read_subgroup(d, &fn)) > 0) { _cleanup_free_ char *p = NULL; - p = strjoin(pfrom, "/", fn, NULL); + p = strjoin(pfrom, "/", fn); free(fn); if (!p) return -ENOMEM; @@ -543,6 +554,13 @@ static const char *controller_to_dirname(const char *controller) { * just cuts off the name= prefixed used for named * hierarchies, if it is specified. */ + if (streq(controller, SYSTEMD_CGROUP_CONTROLLER)) { + if (cg_hybrid_unified() > 0) + controller = SYSTEMD_CGROUP_CONTROLLER_HYBRID; + else + controller = SYSTEMD_CGROUP_CONTROLLER_LEGACY; + } + e = startswith(controller, "name="); if (e) return e; @@ -562,11 +580,11 @@ static int join_path_legacy(const char *controller, const char *path, const char if (isempty(path) && isempty(suffix)) t = strappend("/sys/fs/cgroup/", dn); else if (isempty(path)) - t = strjoin("/sys/fs/cgroup/", dn, "/", suffix, NULL); + t = strjoin("/sys/fs/cgroup/", dn, "/", suffix); else if (isempty(suffix)) - t = strjoin("/sys/fs/cgroup/", dn, "/", path, NULL); + t = strjoin("/sys/fs/cgroup/", dn, "/", path); else - t = strjoin("/sys/fs/cgroup/", dn, "/", path, "/", suffix, NULL); + t = strjoin("/sys/fs/cgroup/", dn, "/", path, "/", suffix); if (!t) return -ENOMEM; @@ -586,7 +604,7 @@ static int join_path_unified(const char *path, const char *suffix, char **fs) { else if (isempty(suffix)) t = strappend("/sys/fs/cgroup/", path); else - t = strjoin("/sys/fs/cgroup/", path, "/", suffix, NULL); + t = strjoin("/sys/fs/cgroup/", path, "/", suffix); if (!t) return -ENOMEM; @@ -595,7 +613,7 @@ static int join_path_unified(const char *path, const char *suffix, char **fs) { } int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs) { - int unified, r; + int r; assert(fs); @@ -613,7 +631,7 @@ int cg_get_path(const char *controller, const char *path, const char *suffix, ch else if (!path) t = strdup(suffix); else - t = strjoin(path, "/", suffix, NULL); + t = strjoin(path, "/", suffix); if (!t) return -ENOMEM; @@ -624,11 +642,10 @@ int cg_get_path(const char *controller, const char *path, const char *suffix, ch if (!cg_controller_is_valid(controller)) return -EINVAL; - unified = cg_all_unified(); - if (unified < 0) - return unified; - - if (unified > 0) + r = cg_all_unified(); + if (r < 0) + return r; + if (r > 0) r = join_path_unified(path, suffix, fs); else r = join_path_legacy(controller, path, suffix, fs); @@ -640,7 +657,7 @@ int cg_get_path(const char *controller, const char *path, const char *suffix, ch } static int controller_is_accessible(const char *controller) { - int unified; + int r; assert(controller); @@ -652,10 +669,10 @@ static int controller_is_accessible(const char *controller) { if (!cg_controller_is_valid(controller)) return -EINVAL; - unified = cg_all_unified(); - if (unified < 0) - return unified; - if (unified > 0) { + r = cg_all_unified(); + if (r < 0) + return r; + if (r > 0) { /* We don't support named hierarchies if we are using * the unified hierarchy. */ @@ -709,7 +726,7 @@ static int trim_cb(const char *path, const struct stat *sb, int typeflag, struct int cg_trim(const char *controller, const char *path, bool delete_root) { _cleanup_free_ char *fs = NULL; - int r = 0; + int r = 0, q; assert(path); @@ -732,6 +749,15 @@ int cg_trim(const char *controller, const char *path, bool delete_root) { return -errno; } + q = cg_hybrid_unified(); + if (q < 0) + return q; + if (q > 0 && streq(controller, SYSTEMD_CGROUP_CONTROLLER)) { + q = cg_trim(SYSTEMD_CGROUP_CONTROLLER_LEGACY, path, delete_root); + if (q < 0) + log_warning_errno(q, "Failed to trim compat systemd cgroup %s: %m", path); + } + return r; } @@ -755,6 +781,16 @@ int cg_create(const char *controller, const char *path) { return -errno; } + r = cg_hybrid_unified(); + if (r < 0) + return r; + + if (r > 0 && streq(controller, SYSTEMD_CGROUP_CONTROLLER)) { + r = cg_create(SYSTEMD_CGROUP_CONTROLLER_LEGACY, path); + if (r < 0) + log_warning_errno(r, "Failed to create compat systemd cgroup %s: %m", path); + } + return 1; } @@ -792,7 +828,21 @@ int cg_attach(const char *controller, const char *path, pid_t pid) { xsprintf(c, PID_FMT "\n", pid); - return write_string_file(fs, c, 0); + r = write_string_file(fs, c, 0); + if (r < 0) + return r; + + r = cg_hybrid_unified(); + if (r < 0) + return r; + + if (r > 0 && streq(controller, SYSTEMD_CGROUP_CONTROLLER)) { + r = cg_attach(SYSTEMD_CGROUP_CONTROLLER_LEGACY, path, pid); + if (r < 0) + log_warning_errno(r, "Failed to attach %d to compat systemd cgroup %s: %m", pid, path); + } + + return 0; } int cg_attach_fallback(const char *controller, const char *path, pid_t pid) { @@ -841,7 +891,20 @@ int cg_set_group_access( if (r < 0) return r; - return chmod_and_chown(fs, mode, uid, gid); + r = chmod_and_chown(fs, mode, uid, gid); + if (r < 0) + return r; + + r = cg_hybrid_unified(); + if (r < 0) + return r; + if (r > 0 && streq(controller, SYSTEMD_CGROUP_CONTROLLER)) { + r = cg_set_group_access(SYSTEMD_CGROUP_CONTROLLER_LEGACY, path, mode, uid, gid); + if (r < 0) + log_warning_errno(r, "Failed to set group access on compat systemd cgroup %s: %m", path); + } + + return 0; } int cg_set_task_access( @@ -852,7 +915,7 @@ int cg_set_task_access( gid_t gid) { _cleanup_free_ char *fs = NULL, *procs = NULL; - int r, unified; + int r; assert(path); @@ -870,16 +933,24 @@ int cg_set_task_access( if (r < 0) return r; - unified = cg_unified(controller); - if (unified < 0) - return unified; - if (unified) - return 0; + r = cg_unified_controller(controller); + if (r < 0) + return r; + if (r == 0) { + /* Compatibility, Always keep values for "tasks" in sync with + * "cgroup.procs" */ + if (cg_get_path(controller, path, "tasks", &procs) >= 0) + (void) chmod_and_chown(procs, mode, uid, gid); + } - /* Compatibility, Always keep values for "tasks" in sync with - * "cgroup.procs" */ - if (cg_get_path(controller, path, "tasks", &procs) >= 0) - (void) chmod_and_chown(procs, mode, uid, gid); + r = cg_hybrid_unified(); + if (r < 0) + return r; + if (r > 0 && streq(controller, SYSTEMD_CGROUP_CONTROLLER)) { + r = cg_set_task_access(SYSTEMD_CGROUP_CONTROLLER_LEGACY, path, mode, uid, gid); + if (r < 0) + log_warning_errno(r, "Failed to set task access on compat systemd cgroup %s: %m", path); + } return 0; } @@ -924,7 +995,7 @@ int cg_get_xattr(const char *controller, const char *path, const char *name, voi int cg_pid_get_path(const char *controller, pid_t pid, char **path) { _cleanup_fclose_ FILE *f = NULL; char line[LINE_MAX]; - const char *fs; + const char *fs, *controller_str; size_t cs = 0; int unified; @@ -937,11 +1008,17 @@ int cg_pid_get_path(const char *controller, pid_t pid, char **path) { } else controller = SYSTEMD_CGROUP_CONTROLLER; - unified = cg_unified(controller); + unified = cg_unified_controller(controller); if (unified < 0) return unified; - if (unified == 0) - cs = strlen(controller); + if (unified == 0) { + if (streq(controller, SYSTEMD_CGROUP_CONTROLLER)) + controller_str = SYSTEMD_CGROUP_CONTROLLER_LEGACY; + else + controller_str = controller; + + cs = strlen(controller_str); + } fs = procfs_file_alloca(pid, "cgroup"); f = fopen(fs, "re"); @@ -978,7 +1055,7 @@ int cg_pid_get_path(const char *controller, pid_t pid, char **path) { *e = 0; FOREACH_WORD_SEPARATOR(word, k, l, ",", state) { - if (k == cs && memcmp(word, controller, cs) == 0) { + if (k == cs && memcmp(word, controller_str, cs) == 0) { found = true; break; } @@ -1002,14 +1079,14 @@ int cg_pid_get_path(const char *controller, pid_t pid, char **path) { int cg_install_release_agent(const char *controller, const char *agent) { _cleanup_free_ char *fs = NULL, *contents = NULL; const char *sc; - int r, unified; + int r; assert(agent); - unified = cg_unified(controller); - if (unified < 0) - return unified; - if (unified) /* doesn't apply to unified hierarchy */ + r = cg_unified_controller(controller); + if (r < 0) + return r; + if (r > 0) /* doesn't apply to unified hierarchy */ return -EOPNOTSUPP; r = cg_get_path(controller, NULL, "release_agent", &fs); @@ -1055,12 +1132,12 @@ int cg_install_release_agent(const char *controller, const char *agent) { int cg_uninstall_release_agent(const char *controller) { _cleanup_free_ char *fs = NULL; - int r, unified; + int r; - unified = cg_unified(controller); - if (unified < 0) - return unified; - if (unified) /* Doesn't apply to unified hierarchy */ + r = cg_unified_controller(controller); + if (r < 0) + return r; + if (r > 0) /* Doesn't apply to unified hierarchy */ return -EOPNOTSUPP; r = cg_get_path(controller, NULL, "notify_on_release", &fs); @@ -1105,7 +1182,7 @@ int cg_is_empty(const char *controller, const char *path) { } int cg_is_empty_recursive(const char *controller, const char *path) { - int unified, r; + int r; assert(path); @@ -1113,11 +1190,10 @@ int cg_is_empty_recursive(const char *controller, const char *path) { if (controller && (isempty(path) || path_equal(path, "/"))) return false; - unified = cg_unified(controller); - if (unified < 0) - return unified; - - if (unified > 0) { + r = cg_unified_controller(controller); + if (r < 0) + return r; + if (r > 0) { _cleanup_free_ char *t = NULL; /* On the unified hierarchy we can check empty state @@ -1145,7 +1221,7 @@ int cg_is_empty_recursive(const char *controller, const char *path) { while ((r = cg_read_subgroup(d, &fn)) > 0) { _cleanup_free_ char *p = NULL; - p = strjoin(path, "/", fn, NULL); + p = strjoin(path, "/", fn); free(fn); if (!p) return -ENOMEM; @@ -1834,6 +1910,9 @@ bool cg_controller_is_valid(const char *p) { if (!p) return false; + if (streq(p, SYSTEMD_CGROUP_CONTROLLER)) + return true; + s = startswith(p, "name="); if (s) p = s; @@ -1987,7 +2066,7 @@ int cg_get_keyed_attribute(const char *controller, const char *path, const char int cg_create_everywhere(CGroupMask supported, CGroupMask mask, const char *path) { CGroupController c; - int r, unified; + int r; /* This one will create a cgroup in our private tree, but also * duplicate it in the trees specified in mask, and remove it @@ -1999,10 +2078,10 @@ 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_all_unified(); - if (unified < 0) - return unified; - if (unified > 0) + r = cg_all_unified(); + if (r < 0) + return r; + if (r > 0) return 0; /* Otherwise, do the same in the other hierarchies */ @@ -2023,16 +2102,16 @@ int cg_create_everywhere(CGroupMask supported, CGroupMask mask, const char *path int cg_attach_everywhere(CGroupMask supported, const char *path, pid_t pid, cg_migrate_callback_t path_callback, void *userdata) { CGroupController c; - int r, unified; + int r; r = cg_attach(SYSTEMD_CGROUP_CONTROLLER, path, pid); if (r < 0) return r; - unified = cg_all_unified(); - if (unified < 0) - return unified; - if (unified > 0) + r = cg_all_unified(); + if (r < 0) + return r; + if (r > 0) return 0; for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { @@ -2073,7 +2152,7 @@ int cg_attach_many_everywhere(CGroupMask supported, const char *path, Set* pids, int cg_migrate_everywhere(CGroupMask supported, const char *from, const char *to, cg_migrate_callback_t to_callback, void *userdata) { CGroupController c; - int r = 0, unified; + int r = 0, q; if (!path_equal(from, to)) { r = cg_migrate_recursive(SYSTEMD_CGROUP_CONTROLLER, from, SYSTEMD_CGROUP_CONTROLLER, to, CGROUP_REMOVE); @@ -2081,10 +2160,10 @@ int cg_migrate_everywhere(CGroupMask supported, const char *from, const char *to return r; } - unified = cg_all_unified(); - if (unified < 0) - return unified; - if (unified > 0) + q = cg_all_unified(); + if (q < 0) + return q; + if (q > 0) return r; for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { @@ -2108,16 +2187,16 @@ int cg_migrate_everywhere(CGroupMask supported, const char *from, const char *to int cg_trim_everywhere(CGroupMask supported, const char *path, bool delete_root) { CGroupController c; - int r, unified; + int r, q; r = cg_trim(SYSTEMD_CGROUP_CONTROLLER, path, delete_root); if (r < 0) return r; - unified = cg_all_unified(); - if (unified < 0) - return unified; - if (unified > 0) + q = cg_all_unified(); + if (q < 0) + return q; + if (q > 0) return r; for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { @@ -2134,16 +2213,16 @@ int cg_trim_everywhere(CGroupMask supported, const char *path, bool delete_root) int cg_mask_supported(CGroupMask *ret) { CGroupMask mask = 0; - int r, unified; + int r; /* Determines the mask of supported cgroup controllers. Only * includes controllers we can make sense of and that are * actually accessible. */ - unified = cg_all_unified(); - if (unified < 0) - return unified; - if (unified > 0) { + r = cg_all_unified(); + if (r < 0) + return r; + if (r > 0) { _cleanup_free_ char *root = NULL, *controllers = NULL, *path = NULL; const char *c; @@ -2263,7 +2342,18 @@ int cg_kernel_controllers(Set *controllers) { static thread_local CGroupUnified unified_cache = CGROUP_UNIFIED_UNKNOWN; -static int cg_update_unified(void) { +/* The hybrid mode was initially implemented in v232 and simply mounted cgroup v2 on /sys/fs/cgroup/systemd. This + * unfortunately broke other tools (such as docker) which expected the v1 "name=systemd" hierarchy on + * /sys/fs/cgroup/systemd. From v233 and on, the hybrid mode mountnbs v2 on /sys/fs/cgroup/unified and maintains + * "name=systemd" hierarchy on /sys/fs/cgroup/systemd for compatibility with other tools. + * + * To keep live upgrade working, we detect and support v232 layout. When v232 layout is detected, to keep cgroup v2 + * process management but disable the compat dual layout, we return %true on + * cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER) and %false on cg_hybrid_unified(). + */ +static thread_local bool unified_systemd_v232; + +static int cg_unified_update(void) { struct statfs fs; @@ -2281,54 +2371,83 @@ static int cg_update_unified(void) { if (F_TYPE_EQUAL(fs.f_type, CGROUP2_SUPER_MAGIC)) 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; + if (statfs("/sys/fs/cgroup/unified/", &fs) == 0 && + F_TYPE_EQUAL(fs.f_type, CGROUP2_SUPER_MAGIC)) { + unified_cache = CGROUP_UNIFIED_SYSTEMD; + unified_systemd_v232 = false; + } else if (statfs("/sys/fs/cgroup/systemd/", &fs) == 0 && + F_TYPE_EQUAL(fs.f_type, CGROUP2_SUPER_MAGIC)) { + unified_cache = CGROUP_UNIFIED_SYSTEMD; + unified_systemd_v232 = true; + } else { + if (statfs("/sys/fs/cgroup/systemd/", &fs) < 0) + return -errno; + if (!F_TYPE_EQUAL(fs.f_type, CGROUP_SUPER_MAGIC)) + return -ENOMEDIUM; + unified_cache = CGROUP_UNIFIED_NONE; + } } else return -ENOMEDIUM; return 0; } -int cg_unified(const char *controller) { - +int cg_unified_controller(const char *controller) { int r; - r = cg_update_unified(); + r = cg_unified_update(); 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; + if (unified_cache == CGROUP_UNIFIED_NONE) + return false; + + if (unified_cache >= CGROUP_UNIFIED_ALL) + return true; + + return streq_ptr(controller, SYSTEMD_CGROUP_CONTROLLER); } int cg_all_unified(void) { + int r; - return cg_unified(NULL); + r = cg_unified_update(); + if (r < 0) + return r; + + return unified_cache >= CGROUP_UNIFIED_ALL; } -void cg_unified_flush(void) { +int cg_hybrid_unified(void) { + int r; + + r = cg_unified_update(); + if (r < 0) + return r; + + return unified_cache == CGROUP_UNIFIED_SYSTEMD && !unified_systemd_v232; +} + +int cg_unified_flush(void) { unified_cache = CGROUP_UNIFIED_UNKNOWN; + + return cg_unified_update(); } int cg_enable_everywhere(CGroupMask supported, CGroupMask mask, const char *p) { _cleanup_free_ char *fs = NULL; CGroupController c; - int r, unified; + int r; assert(p); if (supported == 0) return 0; - unified = cg_all_unified(); - if (unified < 0) - return unified; - if (!unified) /* on the legacy hiearchy there's no joining of controllers defined */ + r = cg_all_unified(); + if (r < 0) + return r; + if (r == 0) /* on the legacy hiearchy there's no joining of controllers defined */ return 0; r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, p, "cgroup.subtree_control", &fs); @@ -2360,82 +2479,69 @@ int cg_enable_everywhere(CGroupMask supported, CGroupMask mask, const char *p) { bool cg_is_unified_wanted(void) { static thread_local int wanted = -1; - int r, unified; - - /* If the hierarchy is already mounted, then follow whatever - * was chosen for it. */ - unified = cg_all_unified(); - if (unified >= 0) - return unified; + int r; + bool b; + const bool is_default = DEFAULT_HIERARCHY == CGROUP_UNIFIED_ALL; - /* Otherwise, let's see what the kernel command line has to - * say. Since checking that is expensive, let's cache the - * result. */ + /* If we have a cached value, return that. */ if (wanted >= 0) return wanted; - r = get_proc_cmdline_key("systemd.unified_cgroup_hierarchy", NULL); - if (r > 0) - return (wanted = true); - else { - _cleanup_free_ char *value = NULL; + /* If the hierarchy is already mounted, then follow whatever + * was chosen for it. */ + if (cg_unified_flush() >= 0) + return (wanted = unified_cache >= CGROUP_UNIFIED_ALL); - r = get_proc_cmdline_key("systemd.unified_cgroup_hierarchy=", &value); - if (r < 0) - return false; - if (r == 0) - return (wanted = false); + /* Otherwise, let's see what the kernel command line has to say. + * Since checking is expensive, cache a non-error result. */ + r = proc_cmdline_get_bool("systemd.unified_cgroup_hierarchy", &b); - return (wanted = parse_boolean(value) > 0); - } + return (wanted = r > 0 ? b : is_default); } 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 we have a cached value, return that. */ 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; + /* Check if we have cgroups2 already mounted. */ + if (cg_unified_flush() >= 0 && + unified_cache == CGROUP_UNIFIED_ALL) + return (wanted = false); - r = get_proc_cmdline_key("systemd.legacy_systemd_cgroup_controller=", &value); - if (r < 0) - return true; + /* Otherwise, assume that at least partial legacy is wanted, + * since cgroups2 should already be mounted at this point. */ + return (wanted = true); +} - if (r == 0) - wanted = true; - else - wanted = parse_boolean(value) <= 0; - } +bool cg_is_hybrid_wanted(void) { + static thread_local int wanted = -1; + int r; + bool b; + const bool is_default = DEFAULT_HIERARCHY >= CGROUP_UNIFIED_SYSTEMD; + /* We default to true if the default is "hybrid", obviously, + * but also when the default is "unified", because if we get + * called, it means that unified hierarchy was not mounted. */ - return wanted; -} + /* If we have a cached value, return that. */ + if (wanted >= 0) + return wanted; + + /* If the hierarchy is already mounted, then follow whatever + * was chosen for it. */ + if (cg_unified_flush() >= 0 && + unified_cache == CGROUP_UNIFIED_ALL) + return (wanted = false); + + /* Otherwise, let's see what the kernel command line has to say. + * Since checking is expensive, cache a non-error result. */ + r = proc_cmdline_get_bool("systemd.legacy_systemd_cgroup_controller", &b); -bool cg_is_legacy_systemd_controller_wanted(void) { - return cg_is_legacy_wanted() && !cg_is_unified_systemd_controller_wanted(); + /* The meaning of the kernel option is reversed wrt. to the return value + * of this function, hence the negation. */ + return (wanted = r > 0 ? !b : is_default); } int cg_weight_parse(const char *s, uint64_t *ret) { diff --git a/src/basic/cgroup-util.h b/src/basic/cgroup-util.h index 0aa27c4cd7..a522095d95 100644 --- a/src/basic/cgroup-util.h +++ b/src/basic/cgroup-util.h @@ -241,13 +241,13 @@ int cg_kernel_controllers(Set *controllers); bool cg_ns_supported(void); int cg_all_unified(void); -int cg_unified(const char *controller); -void cg_unified_flush(void); +int cg_hybrid_unified(void); +int cg_unified_controller(const char *controller); +int 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); +bool cg_is_hybrid_wanted(void); const char* cgroup_controller_to_string(CGroupController c) _const_; CGroupController cgroup_controller_from_string(const char *s) _pure_; diff --git a/src/basic/conf-files.c b/src/basic/conf-files.c index c781610e14..b8f0f5d03d 100644 --- a/src/basic/conf-files.c +++ b/src/basic/conf-files.c @@ -43,7 +43,6 @@ static int files_add(Hashmap *h, const char *root, const char *path, const char int r; assert(path); - assert(suffix); dirpath = prefix_roota(root, path); @@ -60,7 +59,7 @@ static int files_add(Hashmap *h, const char *root, const char *path, const char if (!dirent_is_file_with_suffix(de, suffix)) continue; - p = strjoin(dirpath, "/", de->d_name, NULL); + p = strjoin(dirpath, "/", de->d_name); if (!p) return -ENOMEM; @@ -94,7 +93,6 @@ static int conf_files_list_strv_internal(char ***strv, const char *suffix, const int r; assert(strv); - assert(suffix); /* This alters the dirs string array */ if (!path_strv_resolve_uniq(dirs, root)) @@ -126,7 +124,6 @@ int conf_files_list_strv(char ***strv, const char *suffix, const char *root, con _cleanup_strv_free_ char **copy = NULL; assert(strv); - assert(suffix); copy = strv_copy((char**) dirs); if (!copy) @@ -140,7 +137,6 @@ int conf_files_list(char ***strv, const char *suffix, const char *root, const ch va_list ap; assert(strv); - assert(suffix); va_start(ap, dir); dirs = strv_new_ap(dir, ap); @@ -156,7 +152,6 @@ int conf_files_list_nulstr(char ***strv, const char *suffix, const char *root, c _cleanup_strv_free_ char **dirs = NULL; assert(strv); - assert(suffix); dirs = strv_split_nulstr(d); if (!dirs) diff --git a/src/basic/copy.c b/src/basic/copy.c index 9883f5fa31..e120b9eb4e 100644 --- a/src/basic/copy.c +++ b/src/basic/copy.c @@ -45,6 +45,7 @@ #include "strv.h" #include "time-util.h" #include "umask-util.h" +#include "user-util.h" #include "xattr-util.h" #define COPY_BUFFER_SIZE (16*1024u) @@ -68,7 +69,7 @@ static ssize_t try_copy_file_range(int fd_in, loff_t *off_in, return -errno; } -int copy_bytes(int fdf, int fdt, uint64_t max_bytes, bool try_reflink) { +int copy_bytes(int fdf, int fdt, uint64_t max_bytes, CopyFlags copy_flags) { bool try_cfr = true, try_sendfile = true, try_splice = true; int r; size_t m = SSIZE_MAX; /* that is the maximum that sendfile and c_f_r accept */ @@ -77,7 +78,7 @@ int copy_bytes(int fdf, int fdt, uint64_t max_bytes, bool try_reflink) { assert(fdt >= 0); /* Try btrfs reflinks first. */ - if (try_reflink && + if ((copy_flags & COPY_REFLINK) && max_bytes == (uint64_t) -1 && lseek(fdf, 0, SEEK_CUR) == 0 && lseek(fdt, 0, SEEK_CUR) == 0) { @@ -176,7 +177,16 @@ int copy_bytes(int fdf, int fdt, uint64_t max_bytes, bool try_reflink) { return 0; /* return 0 if we hit EOF earlier than the size limit */ } -static int fd_copy_symlink(int df, const char *from, const struct stat *st, int dt, const char *to) { +static int fd_copy_symlink( + int df, + const char *from, + const struct stat *st, + int dt, + const char *to, + uid_t override_uid, + gid_t override_gid, + CopyFlags copy_flags) { + _cleanup_free_ char *target = NULL; int r; @@ -191,13 +201,25 @@ static int fd_copy_symlink(int df, const char *from, const struct stat *st, int if (symlinkat(target, dt, to) < 0) return -errno; - if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0) + if (fchownat(dt, to, + uid_is_valid(override_uid) ? override_uid : st->st_uid, + gid_is_valid(override_gid) ? override_gid : st->st_gid, + AT_SYMLINK_NOFOLLOW) < 0) return -errno; return 0; } -static int fd_copy_regular(int df, const char *from, const struct stat *st, int dt, const char *to) { +static int fd_copy_regular( + int df, + const char *from, + const struct stat *st, + int dt, + const char *to, + uid_t override_uid, + gid_t override_gid, + CopyFlags copy_flags) { + _cleanup_close_ int fdf = -1, fdt = -1; struct timespec ts[2]; int r, q; @@ -214,13 +236,15 @@ static int fd_copy_regular(int df, const char *from, const struct stat *st, int if (fdt < 0) return -errno; - r = copy_bytes(fdf, fdt, (uint64_t) -1, true); + r = copy_bytes(fdf, fdt, (uint64_t) -1, copy_flags); if (r < 0) { unlinkat(dt, to, 0); return r; } - if (fchown(fdt, st->st_uid, st->st_gid) < 0) + if (fchown(fdt, + uid_is_valid(override_uid) ? override_uid : st->st_uid, + gid_is_valid(override_gid) ? override_gid : st->st_gid) < 0) r = -errno; if (fchmod(fdt, st->st_mode & 07777) < 0) @@ -229,7 +253,6 @@ static int fd_copy_regular(int df, const char *from, const struct stat *st, int ts[0] = st->st_atim; ts[1] = st->st_mtim; (void) futimens(fdt, ts); - (void) copy_xattr(fdf, fdt); q = close(fdt); @@ -243,7 +266,15 @@ static int fd_copy_regular(int df, const char *from, const struct stat *st, int return r; } -static int fd_copy_fifo(int df, const char *from, const struct stat *st, int dt, const char *to) { +static int fd_copy_fifo( + int df, + const char *from, + const struct stat *st, + int dt, + const char *to, + uid_t override_uid, + gid_t override_gid, + CopyFlags copy_flags) { int r; assert(from); @@ -254,7 +285,10 @@ static int fd_copy_fifo(int df, const char *from, const struct stat *st, int dt, if (r < 0) return -errno; - if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0) + if (fchownat(dt, to, + uid_is_valid(override_uid) ? override_uid : st->st_uid, + gid_is_valid(override_gid) ? override_gid : st->st_gid, + AT_SYMLINK_NOFOLLOW) < 0) r = -errno; if (fchmodat(dt, to, st->st_mode & 07777, 0) < 0) @@ -263,7 +297,15 @@ static int fd_copy_fifo(int df, const char *from, const struct stat *st, int dt, return r; } -static int fd_copy_node(int df, const char *from, const struct stat *st, int dt, const char *to) { +static int fd_copy_node( + int df, + const char *from, + const struct stat *st, + int dt, + const char *to, + uid_t override_uid, + gid_t override_gid, + CopyFlags copy_flags) { int r; assert(from); @@ -274,7 +316,10 @@ static int fd_copy_node(int df, const char *from, const struct stat *st, int dt, if (r < 0) return -errno; - if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0) + if (fchownat(dt, to, + uid_is_valid(override_uid) ? override_uid : st->st_uid, + gid_is_valid(override_gid) ? override_gid : st->st_gid, + AT_SYMLINK_NOFOLLOW) < 0) r = -errno; if (fchmodat(dt, to, st->st_mode & 07777, 0) < 0) @@ -290,7 +335,9 @@ static int fd_copy_directory( int dt, const char *to, dev_t original_device, - bool merge) { + uid_t override_uid, + gid_t override_gid, + CopyFlags copy_flags) { _cleanup_close_ int fdf = -1, fdt = -1; _cleanup_closedir_ DIR *d = NULL; @@ -316,7 +363,7 @@ static int fd_copy_directory( r = mkdirat(dt, to, st->st_mode & 07777); if (r >= 0) created = true; - else if (errno == EEXIST && merge) + else if (errno == EEXIST && (copy_flags & COPY_MERGE)) created = false; else return -errno; @@ -331,7 +378,7 @@ static int fd_copy_directory( struct stat buf; int q; - if (STR_IN_SET(de->d_name, ".", "..")) + if (dot_or_dot_dot(de->d_name)) continue; if (fstatat(dirfd(d), de->d_name, &buf, AT_SYMLINK_NOFOLLOW) < 0) { @@ -343,19 +390,19 @@ static int fd_copy_directory( continue; if (S_ISREG(buf.st_mode)) - q = fd_copy_regular(dirfd(d), de->d_name, &buf, fdt, de->d_name); + q = fd_copy_regular(dirfd(d), de->d_name, &buf, fdt, de->d_name, override_uid, override_gid, copy_flags); else if (S_ISDIR(buf.st_mode)) - q = fd_copy_directory(dirfd(d), de->d_name, &buf, fdt, de->d_name, original_device, merge); + q = fd_copy_directory(dirfd(d), de->d_name, &buf, fdt, de->d_name, original_device, override_uid, override_gid, copy_flags); else if (S_ISLNK(buf.st_mode)) - q = fd_copy_symlink(dirfd(d), de->d_name, &buf, fdt, de->d_name); + q = fd_copy_symlink(dirfd(d), de->d_name, &buf, fdt, de->d_name, override_uid, override_gid, copy_flags); else if (S_ISFIFO(buf.st_mode)) - q = fd_copy_fifo(dirfd(d), de->d_name, &buf, fdt, de->d_name); + q = fd_copy_fifo(dirfd(d), de->d_name, &buf, fdt, de->d_name, override_uid, override_gid, copy_flags); else if (S_ISBLK(buf.st_mode) || S_ISCHR(buf.st_mode) || S_ISSOCK(buf.st_mode)) - q = fd_copy_node(dirfd(d), de->d_name, &buf, fdt, de->d_name); + q = fd_copy_node(dirfd(d), de->d_name, &buf, fdt, de->d_name, override_uid, override_gid, copy_flags); else q = -EOPNOTSUPP; - if (q == -EEXIST && merge) + if (q == -EEXIST && (copy_flags & COPY_MERGE)) q = 0; if (q < 0) @@ -368,7 +415,9 @@ static int fd_copy_directory( st->st_mtim }; - if (fchown(fdt, st->st_uid, st->st_gid) < 0) + if (fchown(fdt, + uid_is_valid(override_uid) ? override_uid : st->st_uid, + gid_is_valid(override_gid) ? override_gid : st->st_gid) < 0) r = -errno; if (fchmod(fdt, st->st_mode & 07777) < 0) @@ -381,7 +430,7 @@ static int fd_copy_directory( return r; } -int copy_tree_at(int fdf, const char *from, int fdt, const char *to, bool merge) { +int copy_tree_at(int fdf, const char *from, int fdt, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags) { struct stat st; assert(from); @@ -391,24 +440,24 @@ int copy_tree_at(int fdf, const char *from, int fdt, const char *to, bool merge) return -errno; if (S_ISREG(st.st_mode)) - return fd_copy_regular(fdf, from, &st, fdt, to); + return fd_copy_regular(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags); else if (S_ISDIR(st.st_mode)) - return fd_copy_directory(fdf, from, &st, fdt, to, st.st_dev, merge); + return fd_copy_directory(fdf, from, &st, fdt, to, st.st_dev, override_uid, override_gid, copy_flags); else if (S_ISLNK(st.st_mode)) - return fd_copy_symlink(fdf, from, &st, fdt, to); + return fd_copy_symlink(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags); else if (S_ISFIFO(st.st_mode)) - return fd_copy_fifo(fdf, from, &st, fdt, to); + return fd_copy_fifo(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags); else if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode) || S_ISSOCK(st.st_mode)) - return fd_copy_node(fdf, from, &st, fdt, to); + return fd_copy_node(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags); else return -EOPNOTSUPP; } -int copy_tree(const char *from, const char *to, bool merge) { - return copy_tree_at(AT_FDCWD, from, AT_FDCWD, to, merge); +int copy_tree(const char *from, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags) { + return copy_tree_at(AT_FDCWD, from, AT_FDCWD, to, override_uid, override_gid, copy_flags); } -int copy_directory_fd(int dirfd, const char *to, bool merge) { +int copy_directory_fd(int dirfd, const char *to, CopyFlags copy_flags) { struct stat st; assert(dirfd >= 0); @@ -420,10 +469,10 @@ int copy_directory_fd(int dirfd, const char *to, bool merge) { if (!S_ISDIR(st.st_mode)) return -ENOTDIR; - return fd_copy_directory(dirfd, NULL, &st, AT_FDCWD, to, st.st_dev, merge); + return fd_copy_directory(dirfd, NULL, &st, AT_FDCWD, to, st.st_dev, UID_INVALID, GID_INVALID, copy_flags); } -int copy_directory(const char *from, const char *to, bool merge) { +int copy_directory(const char *from, const char *to, CopyFlags copy_flags) { struct stat st; assert(from); @@ -435,10 +484,10 @@ int copy_directory(const char *from, const char *to, bool merge) { if (!S_ISDIR(st.st_mode)) return -ENOTDIR; - return fd_copy_directory(AT_FDCWD, from, &st, AT_FDCWD, to, st.st_dev, merge); + return fd_copy_directory(AT_FDCWD, from, &st, AT_FDCWD, to, st.st_dev, UID_INVALID, GID_INVALID, copy_flags); } -int copy_file_fd(const char *from, int fdt, bool try_reflink) { +int copy_file_fd(const char *from, int fdt, CopyFlags copy_flags) { _cleanup_close_ int fdf = -1; int r; @@ -449,7 +498,7 @@ int copy_file_fd(const char *from, int fdt, bool try_reflink) { if (fdf < 0) return -errno; - r = copy_bytes(fdf, fdt, (uint64_t) -1, try_reflink); + r = copy_bytes(fdf, fdt, (uint64_t) -1, copy_flags); (void) copy_times(fdf, fdt); (void) copy_xattr(fdf, fdt); @@ -457,7 +506,7 @@ int copy_file_fd(const char *from, int fdt, bool try_reflink) { return r; } -int copy_file(const char *from, const char *to, int flags, mode_t mode, unsigned chattr_flags) { +int copy_file(const char *from, const char *to, int flags, mode_t mode, unsigned chattr_flags, CopyFlags copy_flags) { int fdt = -1, r; assert(from); @@ -472,7 +521,7 @@ int copy_file(const char *from, const char *to, int flags, mode_t mode, unsigned if (chattr_flags != 0) (void) chattr_fd(fdt, chattr_flags, (unsigned) -1); - r = copy_file_fd(from, fdt, true); + r = copy_file_fd(from, fdt, copy_flags); if (r < 0) { close(fdt); unlink(to); @@ -487,7 +536,7 @@ int copy_file(const char *from, const char *to, int flags, mode_t mode, unsigned return 0; } -int copy_file_atomic(const char *from, const char *to, mode_t mode, bool replace, unsigned chattr_flags) { +int copy_file_atomic(const char *from, const char *to, mode_t mode, unsigned chattr_flags, CopyFlags copy_flags) { _cleanup_free_ char *t = NULL; int r; @@ -498,18 +547,18 @@ int copy_file_atomic(const char *from, const char *to, mode_t mode, bool replace if (r < 0) return r; - r = copy_file(from, t, O_NOFOLLOW|O_EXCL, mode, chattr_flags); + r = copy_file(from, t, O_NOFOLLOW|O_EXCL, mode, chattr_flags, copy_flags); if (r < 0) return r; - if (replace) { + if (copy_flags & COPY_REPLACE) { r = renameat(AT_FDCWD, t, AT_FDCWD, to); if (r < 0) r = -errno; } else r = rename_noreplace(AT_FDCWD, t, AT_FDCWD, to); if (r < 0) { - (void) unlink_noerrno(t); + (void) unlink(t); return r; } diff --git a/src/basic/copy.h b/src/basic/copy.h index b5d08ebafe..4f3e11423e 100644 --- a/src/basic/copy.h +++ b/src/basic/copy.h @@ -24,13 +24,19 @@ #include <stdint.h> #include <sys/types.h> -int copy_file_fd(const char *from, int to, bool try_reflink); -int copy_file(const char *from, const char *to, int flags, mode_t mode, unsigned chattr_flags); -int copy_file_atomic(const char *from, const char *to, mode_t mode, bool replace, unsigned chattr_flags); -int copy_tree(const char *from, const char *to, bool merge); -int copy_tree_at(int fdf, const char *from, int fdt, const char *to, bool merge); -int copy_directory_fd(int dirfd, const char *to, bool merge); -int copy_directory(const char *from, const char *to, bool merge); -int copy_bytes(int fdf, int fdt, uint64_t max_bytes, bool try_reflink); +typedef enum CopyFlags { + COPY_REFLINK = 0x1, /* try to reflink */ + COPY_MERGE = 0x2, /* merge existing trees with our new one to copy */ + COPY_REPLACE = 0x4, /* replace an existing file if there's one */ +} CopyFlags; + +int copy_file_fd(const char *from, int to, CopyFlags copy_flags); +int copy_file(const char *from, const char *to, int open_flags, mode_t mode, unsigned chattr_flags, CopyFlags copy_flags); +int copy_file_atomic(const char *from, const char *to, mode_t mode, unsigned chattr_flags, CopyFlags copy_flags); +int copy_tree(const char *from, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags); +int copy_tree_at(int fdf, const char *from, int fdt, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags); +int copy_directory_fd(int dirfd, const char *to, CopyFlags copy_flags); +int copy_directory(const char *from, const char *to, CopyFlags copy_flags); +int copy_bytes(int fdf, int fdt, uint64_t max_bytes, CopyFlags copy_flags); int copy_times(int fdf, int fdt); int copy_xattr(int fdf, int fdt); diff --git a/src/basic/def.h b/src/basic/def.h index 2266eff650..200ea973c1 100644 --- a/src/basic/def.h +++ b/src/basic/def.h @@ -36,7 +36,9 @@ /* The default value for the net.unix.max_dgram_qlen sysctl */ #define DEFAULT_UNIX_MAX_DGRAM_QLEN 512UL -#define SYSTEMD_CGROUP_CONTROLLER "name=systemd" +#define SYSTEMD_CGROUP_CONTROLLER_LEGACY "name=systemd" +#define SYSTEMD_CGROUP_CONTROLLER_HYBRID "name=unified" +#define SYSTEMD_CGROUP_CONTROLLER "_systemd" #define SIGNALS_CRASH_HANDLER SIGSEGV,SIGILL,SIGFPE,SIGBUS,SIGQUIT,SIGABRT #define SIGNALS_IGNORE SIGPIPE @@ -73,18 +75,18 @@ #define NOTIFY_BUFFER_MAX PIPE_BUF #ifdef HAVE_SPLIT_USR -#define _CONF_PATHS_SPLIT_USR(n) "/lib/" n "\0" +# define _CONF_PATHS_SPLIT_USR(n) "/lib/" n "\0" #else -#define _CONF_PATHS_SPLIT_USR(n) +# define _CONF_PATHS_SPLIT_USR(n) #endif /* Return a nulstr for a standard cascade of configuration paths, * 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) \ - "/etc/" n "\0" \ - "/run/" n "\0" \ - "/usr/local/lib/" n "\0" \ - "/usr/lib/" n "\0" \ +#define CONF_PATHS_NULSTR(n) \ + "/etc/" n "\0" \ + "/run/" n "\0" \ + "/usr/local/lib/" n "\0" \ + "/usr/lib/" n "\0" \ _CONF_PATHS_SPLIT_USR(n) diff --git a/src/basic/dirent-util.c b/src/basic/dirent-util.c index 59067121b7..6b9d26773e 100644 --- a/src/basic/dirent-util.c +++ b/src/basic/dirent-util.c @@ -70,5 +70,8 @@ bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) { if (de->d_name[0] == '.') return false; + if (!suffix) + return true; + return endswith(de->d_name, suffix); } diff --git a/src/basic/env-util.c b/src/basic/env-util.c index b74290d6fd..1ec574e8a0 100644 --- a/src/basic/env-util.c +++ b/src/basic/env-util.c @@ -26,6 +26,7 @@ #include "alloc-util.h" #include "env-util.h" +#include "escape.h" #include "extract-word.h" #include "macro.h" #include "parse-util.h" @@ -247,7 +248,7 @@ fail: return NULL; } -_pure_ static bool env_match(const char *t, const char *pattern) { +static bool env_match(const char *t, const char *pattern) { assert(t); assert(pattern); @@ -273,6 +274,19 @@ _pure_ static bool env_match(const char *t, const char *pattern) { return false; } +static bool env_entry_has_name(const char *entry, const char *name) { + const char *t; + + assert(entry); + assert(name); + + t = startswith(entry, name); + if (!t) + return false; + + return *t == '='; +} + char **strv_env_delete(char **x, unsigned n_lists, ...) { size_t n, i = 0; char **k, **r; @@ -384,6 +398,35 @@ char **strv_env_unset_many(char **l, ...) { return l; } +int strv_env_replace(char ***l, char *p) { + char **f; + const char *t, *name; + + assert(p); + + /* Replace first occurrence of the env var or add a new one in the + * string list. Drop other occurences. Edits in-place. Does not copy p. + * p must be a valid key=value assignment. + */ + + t = strchr(p, '='); + assert(t); + + name = strndupa(p, t - p); + + for (f = *l; f && *f; f++) + if (env_entry_has_name(*f, name)) { + free_and_replace(*f, p); + strv_env_unset(f + 1, *f); + return 0; + } + + /* We didn't find a match, we need to append p or create a new strv */ + if (strv_push(l, p) < 0) + return -ENOMEM; + return 1; +} + char **strv_env_set(char **x, const char *p) { char **k, **r; @@ -411,7 +454,7 @@ fail: return NULL; } -char *strv_env_get_n(char **l, const char *name, size_t k) { +char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) { char **i; assert(name); @@ -419,18 +462,25 @@ char *strv_env_get_n(char **l, const char *name, size_t k) { if (k <= 0) return NULL; - STRV_FOREACH(i, l) + STRV_FOREACH_BACKWARDS(i, l) if (strneq(*i, name, k) && (*i)[k] == '=') return *i + k + 1; + if (flags & REPLACE_ENV_USE_ENVIRONMENT) { + const char *t; + + t = strndupa(name, k); + return getenv(t); + }; + return NULL; } char *strv_env_get(char **l, const char *name) { assert(name); - return strv_env_get_n(l, name, strlen(name)); + return strv_env_get_n(l, name, strlen(name), 0); } char **strv_env_clean_with_callback(char **e, void (*invalid_callback)(const char *p, void *userdata), void *userdata) { @@ -469,19 +519,26 @@ char **strv_env_clean_with_callback(char **e, void (*invalid_callback)(const cha return e; } -char *replace_env(const char *format, char **env) { +char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) { enum { WORD, CURLY, - VARIABLE + VARIABLE, + VARIABLE_RAW, + TEST, + DEFAULT_VALUE, + ALTERNATE_VALUE, } state = WORD; - const char *e, *word = format; - char *r = NULL, *k; + const char *e, *word = format, *test_value; + char *k; + _cleanup_free_ char *r = NULL; + size_t i, len; + int nest = 0; assert(format); - for (e = format; *e; e ++) { + for (e = format, i = 0; *e && i < n; e ++, i ++) { switch (state) { @@ -494,24 +551,36 @@ char *replace_env(const char *format, char **env) { if (*e == '{') { k = strnappend(r, word, e-word-1); if (!k) - goto fail; + return NULL; free(r); r = k; word = e-1; state = VARIABLE; - + nest++; } else if (*e == '$') { k = strnappend(r, word, e-word); if (!k) - goto fail; + return NULL; free(r); r = k; word = e+1; state = WORD; + + } else if (flags & REPLACE_ENV_ALLOW_BRACELESS && strchr(VALID_CHARS_ENV_NAME, *e)) { + k = strnappend(r, word, e-word-1); + if (!k) + return NULL; + + free(r); + r = k; + + word = e-1; + state = VARIABLE_RAW; + } else state = WORD; break; @@ -520,31 +589,109 @@ char *replace_env(const char *format, char **env) { if (*e == '}') { const char *t; - t = strempty(strv_env_get_n(env, word+2, e-word-2)); + t = strv_env_get_n(env, word+2, e-word-2, flags); k = strappend(r, t); if (!k) - goto fail; + return NULL; free(r); r = k; word = e+1; state = WORD; + } else if (*e == ':') { + if (!(flags & REPLACE_ENV_ALLOW_EXTENDED)) + /* Treat this as unsupported syntax, i.e. do no replacement */ + state = WORD; + else { + len = e-word-2; + state = TEST; + } + } + break; + + case TEST: + if (*e == '-') + state = DEFAULT_VALUE; + else if (*e == '+') + state = ALTERNATE_VALUE; + else { + state = WORD; + break; + } + + test_value = e+1; + break; + + case DEFAULT_VALUE: /* fall through */ + case ALTERNATE_VALUE: + assert(flags & REPLACE_ENV_ALLOW_EXTENDED); + + if (*e == '{') { + nest++; + break; + } + + if (*e != '}') + break; + + nest--; + if (nest == 0) { + const char *t; + _cleanup_free_ char *v = NULL; + + t = strv_env_get_n(env, word+2, len, flags); + + if (t && state == ALTERNATE_VALUE) + t = v = replace_env_n(test_value, e-test_value, env, flags); + else if (!t && state == DEFAULT_VALUE) + t = v = replace_env_n(test_value, e-test_value, env, flags); + + k = strappend(r, t); + if (!k) + return NULL; + + free(r); + r = k; + + word = e+1; + state = WORD; + } + break; + + case VARIABLE_RAW: + assert(flags & REPLACE_ENV_ALLOW_BRACELESS); + + if (!strchr(VALID_CHARS_ENV_NAME, *e)) { + const char *t; + + t = strv_env_get_n(env, word+1, e-word-1, flags); + + k = strappend(r, t); + if (!k) + return NULL; + + free(r); + r = k; + + word = e--; + i--; + state = WORD; } break; } } - k = strnappend(r, word, e-word); - if (!k) - goto fail; + if (state == VARIABLE_RAW) { + const char *t; - free(r); - return k; + assert(flags & REPLACE_ENV_ALLOW_BRACELESS); -fail: - return mfree(r); + t = strv_env_get_n(env, word+1, e-word-1, flags); + return strappend(r, t); + } else + return strnappend(r, word, e-word); } char **replace_env_argv(char **argv, char **env) { @@ -600,7 +747,7 @@ char **replace_env_argv(char **argv, char **env) { } /* If ${FOO} appears as part of a word, replace it by the variable as-is */ - ret[k] = replace_env(*i, env); + ret[k] = replace_env(*i, env, 0); if (!ret[k]) { strv_free(ret); return NULL; @@ -621,3 +768,39 @@ int getenv_bool(const char *p) { return parse_boolean(e); } + +int serialize_environment(FILE *f, char **environment) { + char **e; + + STRV_FOREACH(e, environment) { + _cleanup_free_ char *ce; + + ce = cescape(*e); + if (!ce) + return -ENOMEM; + + fprintf(f, "env=%s\n", *e); + } + + /* caller should call ferror() */ + + return 0; +} + +int deserialize_environment(char ***environment, const char *line) { + char *uce = NULL; + int r; + + assert(line); + assert(environment); + + assert(startswith(line, "env=")); + r = cunescape(line + 4, UNESCAPE_RELAX, &uce); + if (r < 0) + return r; + + if (!env_assignment_is_valid(uce)) + return -EINVAL; + + return strv_env_replace(environment, uce); +} diff --git a/src/basic/env-util.h b/src/basic/env-util.h index b1fef704c2..e88fa6aac0 100644 --- a/src/basic/env-util.h +++ b/src/basic/env-util.h @@ -21,6 +21,7 @@ #include <stdbool.h> #include <stddef.h> +#include <stdio.h> #include "macro.h" @@ -28,9 +29,19 @@ bool env_name_is_valid(const char *e); bool env_value_is_valid(const char *e); bool env_assignment_is_valid(const char *e); -char *replace_env(const char *format, char **env); +enum { + REPLACE_ENV_USE_ENVIRONMENT = 1u, + REPLACE_ENV_ALLOW_BRACELESS = 2u, + REPLACE_ENV_ALLOW_EXTENDED = 4u, +}; + +char *replace_env_n(const char *format, size_t n, char **env, unsigned flags); char **replace_env_argv(char **argv, char **env); +static inline char *replace_env(const char *format, char **env, unsigned flags) { + return replace_env_n(format, strlen(format), env, flags); +} + bool strv_env_is_valid(char **e); #define strv_env_clean(l) strv_env_clean_with_callback(l, NULL, NULL) char **strv_env_clean_with_callback(char **l, void (*invalid_callback)(const char *p, void *userdata), void *userdata); @@ -44,8 +55,12 @@ char **strv_env_delete(char **x, unsigned n_lists, ...); /* New copy */ char **strv_env_set(char **x, const char *p); /* New copy ... */ char **strv_env_unset(char **l, const char *p); /* In place ... */ char **strv_env_unset_many(char **l, ...) _sentinel_; +int strv_env_replace(char ***l, char *p); /* In place ... */ -char *strv_env_get_n(char **l, const char *name, size_t k) _pure_; +char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) _pure_; char *strv_env_get(char **x, const char *n) _pure_; int getenv_bool(const char *p); + +int serialize_environment(FILE *f, char **environment); +int deserialize_environment(char ***environment, const char *line); diff --git a/src/basic/errno-list.c b/src/basic/errno-list.c index 31b66bad5e..c6a01eec8b 100644 --- a/src/basic/errno-list.c +++ b/src/basic/errno-list.c @@ -23,7 +23,7 @@ #include "macro.h" static const struct errno_name* lookup_errno(register const char *str, - register unsigned int len); + register GPERF_LEN_TYPE len); #include "errno-from-name.h" #include "errno-to-name.h" diff --git a/src/basic/exec-util.c b/src/basic/exec-util.c new file mode 100644 index 0000000000..aced9e8e3d --- /dev/null +++ b/src/basic/exec-util.c @@ -0,0 +1,360 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <dirent.h> +#include <errno.h> +#include <sys/prctl.h> +#include <sys/types.h> +#include <unistd.h> +#include <stdio.h> + +#include "alloc-util.h" +#include "conf-files.h" +#include "env-util.h" +#include "exec-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "hashmap.h" +#include "macro.h" +#include "process-util.h" +#include "set.h" +#include "signal-util.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "util.h" + +/* Put this test here for a lack of better place */ +assert_cc(EAGAIN == EWOULDBLOCK); + +static int do_spawn(const char *path, char *argv[], int stdout_fd, pid_t *pid) { + + pid_t _pid; + + if (null_or_empty_path(path)) { + log_debug("%s is empty (a mask).", path); + return 0; + } + + _pid = fork(); + if (_pid < 0) + return log_error_errno(errno, "Failed to fork: %m"); + if (_pid == 0) { + char *_argv[2]; + + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); + + if (stdout_fd >= 0) { + /* If the fd happens to be in the right place, go along with that */ + if (stdout_fd != STDOUT_FILENO && + dup2(stdout_fd, STDOUT_FILENO) < 0) + return -errno; + + fd_cloexec(STDOUT_FILENO, false); + } + + if (!argv) { + _argv[0] = (char*) path; + _argv[1] = NULL; + argv = _argv; + } else + argv[0] = (char*) path; + + execv(path, argv); + log_error_errno(errno, "Failed to execute %s: %m", path); + _exit(EXIT_FAILURE); + } + + log_debug("Spawned %s as " PID_FMT ".", path, _pid); + *pid = _pid; + return 1; +} + +static int do_execute( + char **directories, + usec_t timeout, + gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX], + void* const callback_args[_STDOUT_CONSUME_MAX], + int output_fd, + char *argv[]) { + + _cleanup_hashmap_free_free_ Hashmap *pids = NULL; + _cleanup_strv_free_ char **paths = NULL; + char **path; + int r; + + /* We fork this all off from a child process so that we can somewhat cleanly make + * use of SIGALRM to set a time limit. + * + * If callbacks is nonnull, execution is serial. Otherwise, we default to parallel. + */ + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); + + r = conf_files_list_strv(&paths, NULL, NULL, (const char* const*) directories); + if (r < 0) + return r; + + if (!callbacks) { + pids = hashmap_new(NULL); + if (!pids) + return log_oom(); + } + + /* Abort execution of this process after the timout. We simply rely on SIGALRM as + * default action terminating the process, and turn on alarm(). */ + + if (timeout != USEC_INFINITY) + alarm((timeout + USEC_PER_SEC - 1) / USEC_PER_SEC); + + STRV_FOREACH(path, paths) { + _cleanup_free_ char *t = NULL; + _cleanup_close_ int fd = -1; + pid_t pid; + + t = strdup(*path); + if (!t) + return log_oom(); + + if (callbacks) { + fd = open_serialization_fd(basename(*path)); + if (fd < 0) + return log_error_errno(fd, "Failed to open serialization file: %m"); + } + + r = do_spawn(t, argv, fd, &pid); + if (r <= 0) + continue; + + if (pids) { + r = hashmap_put(pids, PID_TO_PTR(pid), t); + if (r < 0) + return log_oom(); + t = NULL; + } else { + r = wait_for_terminate_and_warn(t, pid, true); + if (r < 0) + continue; + + if (lseek(fd, 0, SEEK_SET) < 0) + return log_error_errno(errno, "Failed to seek on serialization fd: %m"); + + r = callbacks[STDOUT_GENERATE](fd, callback_args[STDOUT_GENERATE]); + fd = -1; + if (r < 0) + return log_error_errno(r, "Failed to process output from %s: %m", *path); + } + } + + if (callbacks) { + r = callbacks[STDOUT_COLLECT](output_fd, callback_args[STDOUT_COLLECT]); + if (r < 0) + return log_error_errno(r, "Callback two failed: %m"); + } + + while (!hashmap_isempty(pids)) { + _cleanup_free_ char *t = NULL; + pid_t pid; + + pid = PTR_TO_PID(hashmap_first_key(pids)); + assert(pid > 0); + + t = hashmap_remove(pids, PID_TO_PTR(pid)); + assert(t); + + wait_for_terminate_and_warn(t, pid, true); + } + + return 0; +} + +int execute_directories( + const char* const* directories, + usec_t timeout, + gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX], + void* const callback_args[_STDOUT_CONSUME_MAX], + char *argv[]) { + + pid_t executor_pid; + char *name; + char **dirs = (char**) directories; + _cleanup_close_ int fd = -1; + int r; + + assert(!strv_isempty(dirs)); + + name = basename(dirs[0]); + assert(!isempty(name)); + + if (callbacks) { + assert(callback_args); + assert(callbacks[STDOUT_GENERATE]); + assert(callbacks[STDOUT_COLLECT]); + assert(callbacks[STDOUT_CONSUME]); + + fd = open_serialization_fd(name); + if (fd < 0) + return log_error_errno(fd, "Failed to open serialization file: %m"); + } + + /* Executes all binaries in the directories serially or in parallel and waits for + * them to finish. Optionally a timeout is applied. If a file with the same name + * exists in more than one directory, the earliest one wins. */ + + executor_pid = fork(); + if (executor_pid < 0) + return log_error_errno(errno, "Failed to fork: %m"); + + if (executor_pid == 0) { + r = do_execute(dirs, timeout, callbacks, callback_args, fd, argv); + _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); + } + + r = wait_for_terminate_and_warn(name, executor_pid, true); + if (r < 0) + return log_error_errno(r, "Execution failed: %m"); + if (r > 0) { + /* non-zero return code from child */ + log_error("Forker process failed."); + return -EREMOTEIO; + } + + if (!callbacks) + return 0; + + if (lseek(fd, 0, SEEK_SET) < 0) + return log_error_errno(errno, "Failed to rewind serialization fd: %m"); + + r = callbacks[STDOUT_CONSUME](fd, callback_args[STDOUT_CONSUME]); + fd = -1; + if (r < 0) + return log_error_errno(r, "Failed to parse returned data: %m"); + return 0; +} + +static int gather_environment_generate(int fd, void *arg) { + char ***env = arg, **x, **y; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_strv_free_ char **new; + int r; + + /* Read a series of VAR=value assignments from fd, use them to update the list of + * variables in env. Also update the exported environment. + * + * fd is always consumed, even on error. + */ + + assert(env); + + f = fdopen(fd, "r"); + if (!f) { + safe_close(fd); + return -errno; + } + + r = load_env_file_pairs(f, NULL, NULL, &new); + if (r < 0) + return r; + + STRV_FOREACH_PAIR(x, y, new) { + char *p; + + if (!env_name_is_valid(*x)) { + log_warning("Invalid variable assignment \"%s=...\", ignoring.", *x); + continue; + } + + p = strjoin(*x, "=", *y); + if (!p) + return -ENOMEM; + + r = strv_env_replace(env, p); + if (r < 0) + return r; + + if (setenv(*x, *y, true) < 0) + return -errno; + } + + return r; +} + +static int gather_environment_collect(int fd, void *arg) { + char ***env = arg; + _cleanup_fclose_ FILE *f = NULL; + int r; + + /* Write out a series of env=cescape(VAR=value) assignments to fd. */ + + assert(env); + + f = fdopen(fd, "w"); + if (!f) { + safe_close(fd); + return -errno; + } + + r = serialize_environment(f, *env); + if (r < 0) + return r; + + if (ferror(f)) + return errno > 0 ? -errno : -EIO; + + return 0; +} + +static int gather_environment_consume(int fd, void *arg) { + char ***env = arg; + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + int r = 0, k; + + /* Read a series of env=cescape(VAR=value) assignments from fd into env. */ + + assert(env); + + f = fdopen(fd, "r"); + if (!f) { + safe_close(fd); + return -errno; + } + + FOREACH_LINE(line, f, return -EIO) { + truncate_nl(line); + + k = deserialize_environment(env, line); + if (k < 0) + log_error_errno(k, "Invalid line \"%s\": %m", line); + if (k < 0 && r == 0) + r = k; + } + + return r; +} + +const gather_stdout_callback_t gather_environment[] = { + gather_environment_generate, + gather_environment_collect, + gather_environment_consume, +}; diff --git a/src/basic/exec-util.h b/src/basic/exec-util.h new file mode 100644 index 0000000000..72009799b2 --- /dev/null +++ b/src/basic/exec-util.h @@ -0,0 +1,40 @@ +/*** + This file is part of systemd. + + Copyright 2017 Zbigniew Jędrzejewski-Szmek + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdbool.h> + +#include "time-util.h" + +typedef int (*gather_stdout_callback_t) (int fd, void *arg); + +enum { + STDOUT_GENERATE, /* from generators to helper process */ + STDOUT_COLLECT, /* from helper process to main process */ + STDOUT_CONSUME, /* process data in main process */ + _STDOUT_CONSUME_MAX, +}; + +int execute_directories( + const char* const* directories, + usec_t timeout, + gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX], + void* const callback_args[_STDOUT_CONSUME_MAX], + char *argv[]); + +extern const gather_stdout_callback_t gather_environment[_STDOUT_CONSUME_MAX]; diff --git a/src/basic/exit-status.c b/src/basic/exit-status.c index 59557f8afe..1e23c32c3f 100644 --- a/src/basic/exit-status.c +++ b/src/basic/exit-status.c @@ -148,6 +148,9 @@ const char* exit_status_to_string(int status, ExitStatusLevel level) { case EXIT_SMACK_PROCESS_LABEL: return "SMACK_PROCESS_LABEL"; + + case EXIT_KEYRING: + return "KEYRING"; } } diff --git a/src/basic/exit-status.h b/src/basic/exit-status.h index 0cfdfd7891..d22b2c00e4 100644 --- a/src/basic/exit-status.h +++ b/src/basic/exit-status.h @@ -82,6 +82,7 @@ enum { EXIT_MAKE_STARTER, EXIT_CHOWN, EXIT_SMACK_PROCESS_LABEL, + EXIT_KEYRING, }; typedef enum ExitStatusLevel { diff --git a/src/basic/extract-word.c b/src/basic/extract-word.c index d6c1228463..f8cac3e911 100644 --- a/src/basic/extract-word.c +++ b/src/basic/extract-word.c @@ -48,7 +48,7 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra /* Bail early if called after last value or with no input */ if (!*p) - goto finish_force_terminate; + goto finish; c = **p; if (!separators) @@ -227,8 +227,8 @@ int extract_first_word_and_warn( *p = save; r = extract_first_word(p, ret, separators, flags|EXTRACT_CUNESCAPE_RELAX); if (r >= 0) { - /* It worked this time, hence it must have been an invalid escape sequence we could correct. */ - log_syntax(unit, LOG_WARNING, filename, line, EINVAL, "Invalid escape sequences in line, correcting: \"%s\"", rvalue); + /* It worked this time, hence it must have been an invalid escape sequence. */ + log_syntax(unit, LOG_WARNING, filename, line, EINVAL, "Ignoring unknown escape sequences: \"%s\"", *ret); return r; } diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index 5c820332a5..19ad20789b 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -24,6 +24,7 @@ #include <sys/stat.h> #include <unistd.h> +#include "dirent-util.h" #include "fd-util.h" #include "fs-util.h" #include "macro.h" @@ -234,12 +235,9 @@ int close_all_fds(const int except[], unsigned n_except) { return r; } - while ((de = readdir(d))) { + FOREACH_DIRENT(de, d, return -errno) { int fd = -1; - if (hidden_or_backup_file(de->d_name)) - continue; - if (safe_atoi(de->d_name, &fd) < 0) /* Let's better ignore this, just in case */ continue; diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 1cfb7a98f5..7c2c2b38f5 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -30,6 +30,7 @@ #include "alloc-util.h" #include "ctype.h" +#include "env-util.h" #include "escape.h" #include "fd-util.h" #include "fileio.h" @@ -553,13 +554,14 @@ static int parse_env_file_internal( } } - if (state == PRE_VALUE || - state == VALUE || - state == VALUE_ESCAPE || - state == SINGLE_QUOTE_VALUE || - state == SINGLE_QUOTE_VALUE_ESCAPE || - state == DOUBLE_QUOTE_VALUE || - state == DOUBLE_QUOTE_VALUE_ESCAPE) { + if (IN_SET(state, + PRE_VALUE, + VALUE, + VALUE_ESCAPE, + SINGLE_QUOTE_VALUE, + SINGLE_QUOTE_VALUE_ESCAPE, + DOUBLE_QUOTE_VALUE, + DOUBLE_QUOTE_VALUE_ESCAPE)) { key[n_key] = 0; @@ -586,14 +588,9 @@ fail: return r; } -static int parse_env_file_push( +static int check_utf8ness_and_warn( const char *filename, unsigned line, - const char *key, char *value, - void *userdata, - int *n_pushed) { - - const char *k; - va_list aq, *ap = userdata; + const char *key, char *value) { if (!utf8_is_valid(key)) { _cleanup_free_ char *p = NULL; @@ -611,6 +608,23 @@ static int parse_env_file_push( return -EINVAL; } + return 0; +} + +static int parse_env_file_push( + const char *filename, unsigned line, + const char *key, char *value, + void *userdata, + int *n_pushed) { + + const char *k; + va_list aq, *ap = userdata; + int r; + + r = check_utf8ness_and_warn(filename, line, key, value); + if (r < 0) + return r; + va_copy(aq, *ap); while ((k = va_arg(aq, const char *))) { @@ -662,27 +676,19 @@ static int load_env_file_push( char *p; int r; - if (!utf8_is_valid(key)) { - _cleanup_free_ char *t = utf8_escape_invalid(key); - - log_error("%s:%u: invalid UTF-8 for key '%s', ignoring.", strna(filename), line, t); - return -EINVAL; - } - - if (value && !utf8_is_valid(value)) { - _cleanup_free_ char *t = utf8_escape_invalid(value); - - log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.", strna(filename), line, key, t); - return -EINVAL; - } + r = check_utf8ness_and_warn(filename, line, key, value); + if (r < 0) + return r; - p = strjoin(key, "=", strempty(value), NULL); + p = strjoin(key, "=", value); if (!p) return -ENOMEM; - r = strv_consume(m, p); - if (r < 0) + r = strv_env_replace(m, p); + if (r < 0) { + free(p); return r; + } if (n_pushed) (*n_pushed)++; @@ -716,19 +722,9 @@ static int load_env_file_push_pairs( char ***m = userdata; int r; - if (!utf8_is_valid(key)) { - _cleanup_free_ char *t = utf8_escape_invalid(key); - - log_error("%s:%u: invalid UTF-8 for key '%s', ignoring.", strna(filename), line, t); - return -EINVAL; - } - - if (value && !utf8_is_valid(value)) { - _cleanup_free_ char *t = utf8_escape_invalid(value); - - log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.", strna(filename), line, key, t); - return -EINVAL; - } + r = check_utf8ness_and_warn(filename, line, key, value); + if (r < 0) + return r; r = strv_extend(m, key); if (r < 0) @@ -767,6 +763,52 @@ int load_env_file_pairs(FILE *f, const char *fname, const char *newline, char ** return 0; } +static int merge_env_file_push( + const char *filename, unsigned line, + const char *key, char *value, + void *userdata, + int *n_pushed) { + + char ***env = userdata; + char *expanded_value; + + assert(env); + + if (!value) { + log_error("%s:%u: invalid syntax (around \"%s\"), ignoring.", strna(filename), line, key); + return 0; + } + + if (!env_name_is_valid(key)) { + log_error("%s:%u: invalid variable name \"%s\", ignoring.", strna(filename), line, key); + free(value); + return 0; + } + + expanded_value = replace_env(value, *env, + REPLACE_ENV_USE_ENVIRONMENT| + REPLACE_ENV_ALLOW_BRACELESS| + REPLACE_ENV_ALLOW_EXTENDED); + if (!expanded_value) + return -ENOMEM; + + free_and_replace(value, expanded_value); + + return load_env_file_push(filename, line, key, value, env, n_pushed); +} + +int merge_env_file( + char ***env, + FILE *f, + const char *fname) { + + /* NOTE: this function supports braceful and braceless variable expansions, + * plus "extended" substitutions, unlike other exported parsing functions. + */ + + return parse_env_file_internal(f, fname, NEWLINE, merge_env_file_push, env, NULL); +} + static void write_env_var(FILE *f, const char *v) { const char *p; @@ -963,9 +1005,9 @@ static int search_and_fopen_internal(const char *path, const char *mode, const c FILE *f; if (root) - p = strjoin(root, *i, "/", path, NULL); + p = strjoin(root, *i, "/", path); else - p = strjoin(*i, "/", path, NULL); + p = strjoin(*i, "/", path); if (!p) return -ENOMEM; @@ -1342,6 +1384,25 @@ int open_tmpfile_linkable(const char *target, int flags, char **ret_path) { return fd; } +int open_serialization_fd(const char *ident) { + int fd = -1; + + fd = memfd_create(ident, MFD_CLOEXEC); + if (fd < 0) { + const char *path; + + path = getpid() == 1 ? "/run/systemd" : "/tmp"; + fd = open_tmpfile_unlinkable(path, O_RDWR|O_CLOEXEC); + if (fd < 0) + return fd; + + log_debug("Serializing %s to %s.", ident, path); + } else + log_debug("Serializing %s to memfd.", ident); + + return fd; +} + int link_tmpfile(int fd, const char *path, const char *target) { assert(fd >= 0); @@ -1409,3 +1470,22 @@ int read_nul_string(FILE *f, char **ret) { return 0; } + +int mkdtemp_malloc(const char *template, char **ret) { + char *p; + + assert(template); + assert(ret); + + p = strdup(template); + if (!p) + return -ENOMEM; + + if (!mkdtemp(p)) { + free(p); + return -errno; + } + + *ret = p; + return 0; +} diff --git a/src/basic/fileio.h b/src/basic/fileio.h index b58c83e64a..e547614cc4 100644 --- a/src/basic/fileio.h +++ b/src/basic/fileio.h @@ -48,6 +48,8 @@ int parse_env_file(const char *fname, const char *separator, ...) _sentinel_; int load_env_file(FILE *f, const char *fname, const char *separator, char ***l); int load_env_file_pairs(FILE *f, const char *fname, const char *separator, char ***l); +int merge_env_file(char ***env, FILE *f, const char *fname); + int write_env_file(const char *fname, char **l); int executable_is_script(const char *path, char **interpreter); @@ -84,7 +86,10 @@ int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space) int open_tmpfile_unlinkable(const char *directory, int flags); int open_tmpfile_linkable(const char *target, int flags, char **ret_path); +int open_serialization_fd(const char *ident); int link_tmpfile(int fd, const char *path, const char *target); int read_nul_string(FILE *f, char **ret); + +int mkdtemp_malloc(const char *template, char **ret); diff --git a/src/basic/formats-util.h b/src/basic/format-util.h index 39a185f59b..39a185f59b 100644 --- a/src/basic/formats-util.h +++ b/src/basic/format-util.h diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index 48952a1c26..8fe19ee4e4 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -17,7 +17,6 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -#include <dirent.h> #include <errno.h> #include <stddef.h> #include <stdio.h> @@ -224,25 +223,25 @@ int readlink_and_make_absolute(const char *p, char **r) { return 0; } -int readlink_and_canonicalize(const char *p, char **r) { +int readlink_and_canonicalize(const char *p, const char *root, char **ret) { char *t, *s; - int j; + int r; assert(p); - assert(r); + assert(ret); - j = readlink_and_make_absolute(p, &t); - if (j < 0) - return j; + r = readlink_and_make_absolute(p, &t); + if (r < 0) + return r; - s = canonicalize_file_name(t); - if (s) { + r = chase_symlinks(t, root, 0, &s); + if (r < 0) + /* If we can't follow up, then let's return the original string, slightly cleaned up. */ + *ret = path_kill_slashes(t); + else { + *ret = s; free(t); - *r = s; - } else - *r = t; - - path_kill_slashes(*r); + } return 0; } @@ -446,6 +445,7 @@ int mkfifo_atomic(const char *path, mode_t mode) { int get_files_in_directory(const char *path, char ***list) { _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; size_t bufsize = 0, n = 0; _cleanup_strv_free_ char **l = NULL; @@ -459,16 +459,7 @@ int get_files_in_directory(const char *path, char ***list) { if (!d) return -errno; - for (;;) { - struct dirent *de; - - errno = 0; - de = readdir(d); - if (!de && errno > 0) - return -errno; - if (!de) - break; - + FOREACH_DIRENT_ALL(de, d, return -errno) { dirent_ensure_type(d, de); if (!dirent_is_file(de)) @@ -598,10 +589,11 @@ 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) { +int chase_symlinks(const char *path, const char *original_root, unsigned flags, 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 */ + bool exists = true; char *todo; int r; @@ -610,26 +602,39 @@ int chase_symlinks(const char *path, const char *_root, char **ret) { /* 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. + * Note that "root" primarily matters if we encounter an absolute symlink. It is also used when following + * relative symlinks to ensure they cannot be used to "escape" the root directory. The path parameter passed is + * assumed to be already prefixed by it, except if the CHASE_PREFIX_ROOT flag is set, in which case it is first + * prefixed accordingly. * * 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; + * at a minimum. + * + * Suggested usage: whenever you want to canonicalize a path, use this function. Pass the absolute path you got + * as-is: fully qualified and relative to your host's root. Optionally, specify the root parameter to tell this + * function what to do when encountering a symlink with an absolute path as directory: prefix it by the + * specified path. + * + * Note: there's also chase_symlinks_prefix() (see below), which as first step prefixes the passed path by the + * passed root. */ - if (_root) { - r = path_make_absolute_cwd(_root, &root); + if (original_root) { + r = path_make_absolute_cwd(original_root, &root); if (r < 0) return r; + + if (flags & CHASE_PREFIX_ROOT) + path = prefix_roota(root, path); } + r = path_make_absolute_cwd(path, &buffer); + if (r < 0) + return r; + fd = open("/", O_CLOEXEC|O_NOFOLLOW|O_PATH); if (fd < 0) return -errno; @@ -665,18 +670,20 @@ int chase_symlinks(const char *path, const char *_root, char **ret) { _cleanup_free_ char *parent = NULL; int fd_parent = -1; + /* If we already are at the top, then going up will not change anything. This is in-line with + * how the kernel handles this. */ if (isempty(done) || path_equal(done, "/")) - return -EINVAL; + continue; parent = dirname_malloc(done); if (!parent) return -ENOMEM; - /* Don't allow this to leave the root dir */ + /* Don't allow this to leave the root dir. */ if (root && path_startswith(done, root) && !path_startswith(parent, root)) - return -EINVAL; + continue; free_and_replace(done, parent); @@ -692,13 +699,32 @@ int chase_symlinks(const char *path, const char *_root, char **ret) { /* Otherwise let's see what this is. */ child = openat(fd, first + n, O_CLOEXEC|O_NOFOLLOW|O_PATH); - if (child < 0) + if (child < 0) { + + if (errno == ENOENT && + (flags & CHASE_NONEXISTENT) && + (isempty(todo) || path_is_safe(todo))) { + + /* If CHASE_NONEXISTENT is set, and the path does not exist, then that's OK, return + * what we got so far. But don't allow this if the remaining path contains "../ or "./" + * or something else weird. */ + + if (!strextend(&done, first, todo, NULL)) + return -ENOMEM; + + exists = false; + break; + } + return -errno; + } if (fstat(child, &st) < 0) return -errno; if (S_ISLNK(st.st_mode)) { + char *joined; + _cleanup_free_ char *destination = NULL; /* This is a symlink, in this case read the destination. But let's make sure we don't follow @@ -722,9 +748,6 @@ int chase_symlinks(const char *path, const char *_root, char **ret) { 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. */ @@ -736,19 +759,17 @@ int chase_symlinks(const char *path, const char *_root, char **ret) { 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. */ + /* 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; + joined = strjoin("/", destination, todo); + if (!joined) + return -ENOMEM; - free(buffer); - todo = buffer = joined; - } + free(buffer); + todo = buffer = joined; continue; } @@ -775,8 +796,10 @@ int chase_symlinks(const char *path, const char *_root, char **ret) { return -ENOMEM; } - *ret = done; - done = NULL; + if (ret) { + *ret = done; + done = NULL; + } - return 0; + return exists; } diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index 31df47cf1e..094acf1799 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -39,7 +39,7 @@ int readlinkat_malloc(int fd, const char *p, char **ret); int readlink_malloc(const char *p, char **r); int readlink_value(const char *p, char **ret); int readlink_and_make_absolute(const char *p, char **r); -int readlink_and_canonicalize(const char *p, char **r); +int readlink_and_canonicalize(const char *p, const char *root, char **r); int readlink_and_make_absolute_root(const char *root, const char *path, char **ret); int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid); @@ -78,4 +78,22 @@ 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); +enum { + CHASE_PREFIX_ROOT = 1, /* If set, the specified path will be prefixed by the specified root before beginning the iteration */ + CHASE_NONEXISTENT = 2, /* If set, it's OK if the path doesn't actually exist. */ +}; + +int chase_symlinks(const char *path_with_prefix, const char *root, unsigned flags, char **ret); + +/* Useful for usage with _cleanup_(), removes a directory and frees the pointer */ +static inline void rmdir_and_free(char *p) { + (void) rmdir(p); + free(p); +} +DEFINE_TRIVIAL_CLEANUP_FUNC(char*, rmdir_and_free); + +static inline void unlink_and_free(char *p) { + (void) unlink(p); + free(p); +} +DEFINE_TRIVIAL_CLEANUP_FUNC(char*, unlink_and_free); diff --git a/src/basic/hexdecoct.c b/src/basic/hexdecoct.c index c5bda6c4d6..2d6e377f0a 100644 --- a/src/basic/hexdecoct.c +++ b/src/basic/hexdecoct.c @@ -72,10 +72,10 @@ int unhexchar(char c) { } char *hexmem(const void *p, size_t l) { - char *r, *z; const uint8_t *x; + char *r, *z; - z = r = malloc(l * 2 + 1); + z = r = new(char, l * 2 + 1); if (!r) return NULL; @@ -97,6 +97,9 @@ int unhexmem(const char *p, size_t l, void **mem, size_t *len) { assert(len); assert(p); + if (l % 2 != 0) + return -EINVAL; + z = r = malloc((l + 1) / 2 + 1); if (!r) return -ENOMEM; @@ -107,12 +110,10 @@ int unhexmem(const char *p, size_t l, void **mem, size_t *len) { a = unhexchar(x[0]); if (a < 0) return a; - else if (x+1 < p + l) { - b = unhexchar(x[1]); - if (b < 0) - return b; - } else - b = 0; + + b = unhexchar(x[1]); + if (b < 0) + return b; *(z++) = (uint8_t) a << 4 | (uint8_t) b; } diff --git a/src/basic/hostname-util.c b/src/basic/hostname-util.c index e44a357287..a94037b303 100644 --- a/src/basic/hostname-util.c +++ b/src/basic/hostname-util.c @@ -55,7 +55,7 @@ char* gethostname_malloc(void) { assert_se(uname(&u) >= 0); if (isempty(u.nodename) || streq(u.nodename, "(none)")) - return strdup(u.sysname); + return strdup(FALLBACK_HOSTNAME); return strdup(u.nodename); } diff --git a/src/basic/in-addr-util.c b/src/basic/in-addr-util.c index aa7ccd1afd..3927df2955 100644 --- a/src/basic/in-addr-util.c +++ b/src/basic/in-addr-util.c @@ -31,15 +31,9 @@ #include "util.h" bool in4_addr_is_null(const struct in_addr *a) { - return a->s_addr == 0; -} + assert(a); -bool in6_addr_is_null(const struct in6_addr *a) { - return - a->s6_addr32[0] == 0 && - a->s6_addr32[1] == 0 && - a->s6_addr32[2] == 0 && - a->s6_addr32[3] == 0; + return a->s_addr == 0; } int in_addr_is_null(int family, const union in_addr_union *u) { @@ -49,16 +43,22 @@ int in_addr_is_null(int family, const union in_addr_union *u) { return in4_addr_is_null(&u->in); if (family == AF_INET6) - return in6_addr_is_null(&u->in6); + return IN6_IS_ADDR_UNSPECIFIED(&u->in6); return -EAFNOSUPPORT; } +bool in4_addr_is_link_local(const struct in_addr *a) { + assert(a); + + return (be32toh(a->s_addr) & UINT32_C(0xFFFF0000)) == (UINT32_C(169) << 24 | UINT32_C(254) << 16); +} + int in_addr_is_link_local(int family, const union in_addr_union *u) { assert(u); if (family == AF_INET) - return (be32toh(u->in.s_addr) & UINT32_C(0xFFFF0000)) == (UINT32_C(169) << 24 | UINT32_C(254) << 16); + return in4_addr_is_link_local(&u->in); if (family == AF_INET6) return IN6_IS_ADDR_LINKLOCAL(&u->in6); @@ -66,12 +66,30 @@ int in_addr_is_link_local(int family, const union in_addr_union *u) { return -EAFNOSUPPORT; } +int in_addr_is_multicast(int family, const union in_addr_union *u) { + assert(u); + + if (family == AF_INET) + return IN_MULTICAST(be32toh(u->in.s_addr)); + + if (family == AF_INET6) + return IN6_IS_ADDR_MULTICAST(&u->in6); + + return -EAFNOSUPPORT; +} + +bool in4_addr_is_localhost(const struct in_addr *a) { + assert(a); + + /* All of 127.x.x.x is localhost. */ + return (be32toh(a->s_addr) & UINT32_C(0xFF000000)) == UINT32_C(127) << 24; +} + int in_addr_is_localhost(int family, const union in_addr_union *u) { assert(u); if (family == AF_INET) - /* All of 127.x.x.x is localhost. */ - return (be32toh(u->in.s_addr) & UINT32_C(0xFF000000)) == UINT32_C(127) << 24; + return in4_addr_is_localhost(&u->in); if (family == AF_INET6) return IN6_IS_ADDR_LOOPBACK(&u->in6); @@ -277,15 +295,14 @@ fallback: } int in_addr_from_string(int family, const char *s, union in_addr_union *ret) { - + union in_addr_union buffer; assert(s); - assert(ret); if (!IN_SET(family, AF_INET, AF_INET6)) return -EAFNOSUPPORT; errno = 0; - if (inet_pton(family, s, ret) <= 0) + if (inet_pton(family, s, ret ?: &buffer) <= 0) return errno > 0 ? -errno : -EINVAL; return 0; @@ -295,18 +312,18 @@ int in_addr_from_string_auto(const char *s, int *family, union in_addr_union *re int r; assert(s); - assert(family); - assert(ret); r = in_addr_from_string(AF_INET, s, ret); if (r >= 0) { - *family = AF_INET; + if (family) + *family = AF_INET; return 0; } r = in_addr_from_string(AF_INET6, s, ret); if (r >= 0) { - *family = AF_INET6; + if (family) + *family = AF_INET6; return 0; } diff --git a/src/basic/in-addr-util.h b/src/basic/in-addr-util.h index d60064aef8..51a5aa67e4 100644 --- a/src/basic/in-addr-util.h +++ b/src/basic/in-addr-util.h @@ -37,11 +37,16 @@ struct in_addr_data { }; bool in4_addr_is_null(const struct in_addr *a); -bool in6_addr_is_null(const struct in6_addr *a); - int in_addr_is_null(int family, const union in_addr_union *u); + +int in_addr_is_multicast(int family, const union in_addr_union *u); + +bool in4_addr_is_link_local(const struct in_addr *a); int in_addr_is_link_local(int family, const union in_addr_union *u); + +bool in4_addr_is_localhost(const struct in_addr *a); int in_addr_is_localhost(int family, const union in_addr_union *u); + int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b); int in_addr_prefix_intersect(int family, const union in_addr_union *a, unsigned aprefixlen, const union in_addr_union *b, unsigned bprefixlen); int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen); diff --git a/src/basic/journal-importer.c b/src/basic/journal-importer.c new file mode 100644 index 0000000000..4c13e46a49 --- /dev/null +++ b/src/basic/journal-importer.c @@ -0,0 +1,481 @@ +/*** + This file is part of systemd. + + Copyright 2014 Zbigniew Jędrzejewski-Szmek + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <unistd.h> + +#include "alloc-util.h" +#include "journal-importer.h" +#include "fd-util.h" +#include "parse-util.h" +#include "string-util.h" + +enum { + IMPORTER_STATE_LINE = 0, /* waiting to read, or reading line */ + IMPORTER_STATE_DATA_START, /* reading binary data header */ + IMPORTER_STATE_DATA, /* reading binary data */ + IMPORTER_STATE_DATA_FINISH, /* expecting newline */ + IMPORTER_STATE_EOF, /* done */ +}; + +static int iovw_put(struct iovec_wrapper *iovw, void* data, size_t len) { + if (!GREEDY_REALLOC(iovw->iovec, iovw->size_bytes, iovw->count + 1)) + return log_oom(); + + iovw->iovec[iovw->count++] = (struct iovec) {data, len}; + return 0; +} + +static void iovw_free_contents(struct iovec_wrapper *iovw) { + iovw->iovec = mfree(iovw->iovec); + iovw->size_bytes = iovw->count = 0; +} + +static void iovw_rebase(struct iovec_wrapper *iovw, char *old, char *new) { + size_t i; + + for (i = 0; i < iovw->count; i++) + iovw->iovec[i].iov_base = (char*) iovw->iovec[i].iov_base - old + new; +} + +size_t iovw_size(struct iovec_wrapper *iovw) { + size_t n = 0, i; + + for (i = 0; i < iovw->count; i++) + n += iovw->iovec[i].iov_len; + + return n; +} + +void journal_importer_cleanup(JournalImporter *imp) { + if (imp->fd >= 0 && !imp->passive_fd) { + log_debug("Closing %s (fd=%d)", imp->name ?: "importer", imp->fd); + safe_close(imp->fd); + } + + free(imp->buf); + iovw_free_contents(&imp->iovw); +} + +static char* realloc_buffer(JournalImporter *imp, size_t size) { + char *b, *old = imp->buf; + + b = GREEDY_REALLOC(imp->buf, imp->size, size); + if (!b) + return NULL; + + iovw_rebase(&imp->iovw, old, imp->buf); + + return b; +} + +static int get_line(JournalImporter *imp, char **line, size_t *size) { + ssize_t n; + char *c = NULL; + + assert(imp); + assert(imp->state == IMPORTER_STATE_LINE); + assert(imp->offset <= imp->filled); + assert(imp->filled <= imp->size); + assert(imp->buf == NULL || imp->size > 0); + assert(imp->fd >= 0); + + for (;;) { + if (imp->buf) { + size_t start = MAX(imp->scanned, imp->offset); + + c = memchr(imp->buf + start, '\n', + imp->filled - start); + if (c != NULL) + break; + } + + imp->scanned = imp->filled; + if (imp->scanned >= DATA_SIZE_MAX) { + log_error("Entry is bigger than %u bytes.", DATA_SIZE_MAX); + return -E2BIG; + } + + if (imp->passive_fd) + /* we have to wait for some data to come to us */ + return -EAGAIN; + + /* We know that imp->filled is at most DATA_SIZE_MAX, so if + we reallocate it, we'll increase the size at least a bit. */ + assert_cc(DATA_SIZE_MAX < ENTRY_SIZE_MAX); + if (imp->size - imp->filled < LINE_CHUNK && + !realloc_buffer(imp, MIN(imp->filled + LINE_CHUNK, ENTRY_SIZE_MAX))) + return log_oom(); + + assert(imp->buf); + assert(imp->size - imp->filled >= LINE_CHUNK || + imp->size == ENTRY_SIZE_MAX); + + n = read(imp->fd, + imp->buf + imp->filled, + imp->size - imp->filled); + if (n < 0) { + if (errno != EAGAIN) + log_error_errno(errno, "read(%d, ..., %zu): %m", + imp->fd, + imp->size - imp->filled); + return -errno; + } else if (n == 0) + return 0; + + imp->filled += n; + } + + *line = imp->buf + imp->offset; + *size = c + 1 - imp->buf - imp->offset; + imp->offset += *size; + + return 1; +} + +static int fill_fixed_size(JournalImporter *imp, void **data, size_t size) { + + assert(imp); + assert(imp->state == IMPORTER_STATE_DATA_START || + imp->state == IMPORTER_STATE_DATA || + imp->state == IMPORTER_STATE_DATA_FINISH); + assert(size <= DATA_SIZE_MAX); + assert(imp->offset <= imp->filled); + assert(imp->filled <= imp->size); + assert(imp->buf != NULL || imp->size == 0); + assert(imp->buf == NULL || imp->size > 0); + assert(imp->fd >= 0); + assert(data); + + while (imp->filled - imp->offset < size) { + int n; + + if (imp->passive_fd) + /* we have to wait for some data to come to us */ + return -EAGAIN; + + if (!realloc_buffer(imp, imp->offset + size)) + return log_oom(); + + n = read(imp->fd, imp->buf + imp->filled, + imp->size - imp->filled); + if (n < 0) { + if (errno != EAGAIN) + log_error_errno(errno, "read(%d, ..., %zu): %m", imp->fd, + imp->size - imp->filled); + return -errno; + } else if (n == 0) + return 0; + + imp->filled += n; + } + + *data = imp->buf + imp->offset; + imp->offset += size; + + return 1; +} + +static int get_data_size(JournalImporter *imp) { + int r; + void *data; + + assert(imp); + assert(imp->state == IMPORTER_STATE_DATA_START); + assert(imp->data_size == 0); + + r = fill_fixed_size(imp, &data, sizeof(uint64_t)); + if (r <= 0) + return r; + + imp->data_size = le64toh( *(uint64_t *) data ); + if (imp->data_size > DATA_SIZE_MAX) { + log_error("Stream declares field with size %zu > DATA_SIZE_MAX = %u", + imp->data_size, DATA_SIZE_MAX); + return -EINVAL; + } + if (imp->data_size == 0) + log_warning("Binary field with zero length"); + + return 1; +} + +static int get_data_data(JournalImporter *imp, void **data) { + int r; + + assert(imp); + assert(data); + assert(imp->state == IMPORTER_STATE_DATA); + + r = fill_fixed_size(imp, data, imp->data_size); + if (r <= 0) + return r; + + return 1; +} + +static int get_data_newline(JournalImporter *imp) { + int r; + char *data; + + assert(imp); + assert(imp->state == IMPORTER_STATE_DATA_FINISH); + + r = fill_fixed_size(imp, (void**) &data, 1); + if (r <= 0) + return r; + + assert(data); + if (*data != '\n') { + log_error("expected newline, got '%c'", *data); + return -EINVAL; + } + + return 1; +} + +static int process_dunder(JournalImporter *imp, char *line, size_t n) { + const char *timestamp; + int r; + + assert(line); + assert(n > 0); + assert(line[n-1] == '\n'); + + /* XXX: is it worth to support timestamps in extended format? + * We don't produce them, but who knows... */ + + timestamp = startswith(line, "__CURSOR="); + if (timestamp) + /* ignore __CURSOR */ + return 1; + + timestamp = startswith(line, "__REALTIME_TIMESTAMP="); + if (timestamp) { + long long unsigned x; + line[n-1] = '\0'; + r = safe_atollu(timestamp, &x); + if (r < 0) + log_warning("Failed to parse __REALTIME_TIMESTAMP: '%s'", timestamp); + else + imp->ts.realtime = x; + return r < 0 ? r : 1; + } + + timestamp = startswith(line, "__MONOTONIC_TIMESTAMP="); + if (timestamp) { + long long unsigned x; + line[n-1] = '\0'; + r = safe_atollu(timestamp, &x); + if (r < 0) + log_warning("Failed to parse __MONOTONIC_TIMESTAMP: '%s'", timestamp); + else + imp->ts.monotonic = x; + return r < 0 ? r : 1; + } + + timestamp = startswith(line, "__"); + if (timestamp) { + log_notice("Unknown dunder line %s", line); + return 1; + } + + /* no dunder */ + return 0; +} + +int journal_importer_process_data(JournalImporter *imp) { + int r; + + switch(imp->state) { + case IMPORTER_STATE_LINE: { + char *line, *sep; + size_t n = 0; + + assert(imp->data_size == 0); + + r = get_line(imp, &line, &n); + if (r < 0) + return r; + if (r == 0) { + imp->state = IMPORTER_STATE_EOF; + return r; + } + assert(n > 0); + assert(line[n-1] == '\n'); + + if (n == 1) { + log_trace("Received empty line, event is ready"); + return 1; + } + + r = process_dunder(imp, line, n); + if (r != 0) + return r < 0 ? r : 0; + + /* MESSAGE=xxx\n + or + COREDUMP\n + LLLLLLLL0011223344...\n + */ + sep = memchr(line, '=', n); + if (sep) { + /* chomp newline */ + n--; + + r = iovw_put(&imp->iovw, line, n); + if (r < 0) + return r; + } else { + /* replace \n with = */ + line[n-1] = '='; + + imp->field_len = n; + imp->state = IMPORTER_STATE_DATA_START; + + /* we cannot put the field in iovec until we have all data */ + } + + log_trace("Received: %.*s (%s)", (int) n, line, sep ? "text" : "binary"); + + return 0; /* continue */ + } + + case IMPORTER_STATE_DATA_START: + assert(imp->data_size == 0); + + r = get_data_size(imp); + // log_debug("get_data_size() -> %d", r); + if (r < 0) + return r; + if (r == 0) { + imp->state = IMPORTER_STATE_EOF; + return 0; + } + + imp->state = imp->data_size > 0 ? + IMPORTER_STATE_DATA : IMPORTER_STATE_DATA_FINISH; + + return 0; /* continue */ + + case IMPORTER_STATE_DATA: { + void *data; + char *field; + + assert(imp->data_size > 0); + + r = get_data_data(imp, &data); + // log_debug("get_data_data() -> %d", r); + if (r < 0) + return r; + if (r == 0) { + imp->state = IMPORTER_STATE_EOF; + return 0; + } + + assert(data); + + field = (char*) data - sizeof(uint64_t) - imp->field_len; + memmove(field + sizeof(uint64_t), field, imp->field_len); + + r = iovw_put(&imp->iovw, field + sizeof(uint64_t), imp->field_len + imp->data_size); + if (r < 0) + return r; + + imp->state = IMPORTER_STATE_DATA_FINISH; + + return 0; /* continue */ + } + + case IMPORTER_STATE_DATA_FINISH: + r = get_data_newline(imp); + // log_debug("get_data_newline() -> %d", r); + if (r < 0) + return r; + if (r == 0) { + imp->state = IMPORTER_STATE_EOF; + return 0; + } + + imp->data_size = 0; + imp->state = IMPORTER_STATE_LINE; + + return 0; /* continue */ + default: + assert_not_reached("wtf?"); + } +} + +int journal_importer_push_data(JournalImporter *imp, const char *data, size_t size) { + assert(imp); + assert(imp->state != IMPORTER_STATE_EOF); + + if (!realloc_buffer(imp, imp->filled + size)) { + log_error("Failed to store received data of size %zu " + "(in addition to existing %zu bytes with %zu filled): %s", + size, imp->size, imp->filled, strerror(ENOMEM)); + return -ENOMEM; + } + + memcpy(imp->buf + imp->filled, data, size); + imp->filled += size; + + return 0; +} + +void journal_importer_drop_iovw(JournalImporter *imp) { + size_t remain, target; + + /* This function drops processed data that along with the iovw that points at it */ + + iovw_free_contents(&imp->iovw); + + /* possibly reset buffer position */ + remain = imp->filled - imp->offset; + + if (remain == 0) /* no brainer */ + imp->offset = imp->scanned = imp->filled = 0; + else if (imp->offset > imp->size - imp->filled && + imp->offset > remain) { + memcpy(imp->buf, imp->buf + imp->offset, remain); + imp->offset = imp->scanned = 0; + imp->filled = remain; + } + + target = imp->size; + while (target > 16 * LINE_CHUNK && imp->filled < target / 2) + target /= 2; + if (target < imp->size) { + char *tmp; + + tmp = realloc(imp->buf, target); + if (!tmp) + log_warning("Failed to reallocate buffer to (smaller) size %zu", + target); + else { + log_debug("Reallocated buffer from %zu to %zu bytes", + imp->size, target); + imp->buf = tmp; + imp->size = target; + } + } +} + +bool journal_importer_eof(const JournalImporter *imp) { + return imp->state == IMPORTER_STATE_EOF; +} diff --git a/src/basic/journal-importer.h b/src/basic/journal-importer.h new file mode 100644 index 0000000000..b3e308dd6d --- /dev/null +++ b/src/basic/journal-importer.h @@ -0,0 +1,70 @@ +/*** + This file is part of systemd. + + Copyright 2016 Zbigniew Jędrzejewski-Szmek + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#pragma once + +#include <stddef.h> +#include <stdbool.h> +#include <sys/uio.h> + +#include "time-util.h" + +/* Make sure not to make this smaller than the maximum coredump size. + * See COREDUMP_MAX in coredump.c */ +#define ENTRY_SIZE_MAX (1024*1024*770u) +#define DATA_SIZE_MAX (1024*1024*768u) +#define LINE_CHUNK 8*1024u + +struct iovec_wrapper { + struct iovec *iovec; + size_t size_bytes; + size_t count; +}; + +size_t iovw_size(struct iovec_wrapper *iovw); + +typedef struct JournalImporter { + int fd; + bool passive_fd; + char *name; + + char *buf; + size_t size; /* total size of the buffer */ + size_t offset; /* offset to the beginning of live data in the buffer */ + size_t scanned; /* number of bytes since the beginning of data without a newline */ + size_t filled; /* total number of bytes in the buffer */ + + size_t field_len; /* used for binary fields: the field name length */ + size_t data_size; /* and the size of the binary data chunk being processed */ + + struct iovec_wrapper iovw; + + int state; + dual_timestamp ts; +} JournalImporter; + +void journal_importer_cleanup(JournalImporter *); +int journal_importer_process_data(JournalImporter *); +int journal_importer_push_data(JournalImporter *, const char *data, size_t size); +void journal_importer_drop_iovw(JournalImporter *); +bool journal_importer_eof(const JournalImporter *); + +static inline size_t journal_importer_bytes_remaining(const JournalImporter *imp) { + return imp->filled; +} diff --git a/src/basic/khash.c b/src/basic/khash.c new file mode 100644 index 0000000000..84648dc1c9 --- /dev/null +++ b/src/basic/khash.c @@ -0,0 +1,275 @@ +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <linux/if_alg.h> +#include <stdbool.h> +#include <sys/socket.h> + +#include "alloc-util.h" +#include "fd-util.h" +#include "hexdecoct.h" +#include "khash.h" +#include "macro.h" +#include "missing.h" +#include "string-util.h" +#include "util.h" + +/* On current kernels the maximum digest (according to "grep digestsize /proc/crypto | sort -u") is actually 32, but + * let's add some extra room, the few wasted bytes don't really matter... */ +#define LONGEST_DIGEST 128 + +struct khash { + int fd; + char *algorithm; + uint8_t digest[LONGEST_DIGEST+1]; + size_t digest_size; + bool digest_valid; +}; + +int khash_new_with_key(khash **ret, const char *algorithm, const void *key, size_t key_size) { + union { + struct sockaddr sa; + struct sockaddr_alg alg; + } sa = { + .alg.salg_family = AF_ALG, + .alg.salg_type = "hash", + }; + + _cleanup_(khash_unrefp) khash *h = NULL; + _cleanup_close_ int fd = -1; + ssize_t n; + + assert(ret); + assert(key || key_size == 0); + + /* Filter out an empty algorithm early, as we do not support an algorithm by that name. */ + if (isempty(algorithm)) + return -EINVAL; + + /* Overly long hash algorithm names we definitely do not support */ + if (strlen(algorithm) >= sizeof(sa.alg.salg_name)) + return -EOPNOTSUPP; + + fd = socket(AF_ALG, SOCK_SEQPACKET|SOCK_CLOEXEC, 0); + if (fd < 0) + return -errno; + + strcpy((char*) sa.alg.salg_name, algorithm); + if (bind(fd, &sa.sa, sizeof(sa)) < 0) { + if (errno == ENOENT) + return -EOPNOTSUPP; + return -errno; + } + + if (key) { + if (setsockopt(fd, SOL_ALG, ALG_SET_KEY, key, key_size) < 0) + return -errno; + } + + h = new0(khash, 1); + if (!h) + return -ENOMEM; + + h->fd = accept4(fd, NULL, 0, SOCK_CLOEXEC); + if (h->fd < 0) + return -errno; + + h->algorithm = strdup(algorithm); + if (!h->algorithm) + return -ENOMEM; + + /* Temporary fix for rc kernel bug: https://bugzilla.redhat.com/show_bug.cgi?id=1395896 */ + (void) send(h->fd, NULL, 0, 0); + + /* Figure out the digest size */ + n = recv(h->fd, h->digest, sizeof(h->digest), 0); + if (n < 0) + return -errno; + if (n >= LONGEST_DIGEST) /* longer than what we expected? If so, we don't support this */ + return -EOPNOTSUPP; + + h->digest_size = (size_t) n; + h->digest_valid = true; + + /* Temporary fix for rc kernel bug: https://bugzilla.redhat.com/show_bug.cgi?id=1395896 */ + (void) send(h->fd, NULL, 0, 0); + + *ret = h; + h = NULL; + + return 0; +} + +int khash_new(khash **ret, const char *algorithm) { + return khash_new_with_key(ret, algorithm, NULL, 0); +} + +khash* khash_unref(khash *h) { + if (!h) + return NULL; + + safe_close(h->fd); + free(h->algorithm); + free(h); + + return NULL; +} + +int khash_dup(khash *h, khash **ret) { + _cleanup_(khash_unrefp) khash *copy = NULL; + + assert(h); + assert(ret); + + copy = newdup(khash, h, 1); + if (!copy) + return -ENOMEM; + + copy->fd = -1; + copy->algorithm = strdup(h->algorithm); + if (!copy->algorithm) + return -ENOMEM; + + copy->fd = accept4(h->fd, NULL, 0, SOCK_CLOEXEC); + if (copy->fd < 0) + return -errno; + + *ret = copy; + copy = NULL; + + return 0; +} + +const char *khash_get_algorithm(khash *h) { + assert(h); + + return h->algorithm; +} + +size_t khash_get_size(khash *h) { + assert(h); + + return h->digest_size; +} + +int khash_reset(khash *h) { + ssize_t n; + + assert(h); + + n = send(h->fd, NULL, 0, 0); + if (n < 0) + return -errno; + + h->digest_valid = false; + + return 0; +} + +int khash_put(khash *h, const void *buffer, size_t size) { + ssize_t n; + + assert(h); + assert(buffer || size == 0); + + if (size <= 0) + return 0; + + n = send(h->fd, buffer, size, MSG_MORE); + if (n < 0) + return -errno; + + h->digest_valid = false; + + return 0; +} + +int khash_put_iovec(khash *h, const struct iovec *iovec, size_t n) { + struct msghdr mh = { + mh.msg_iov = (struct iovec*) iovec, + mh.msg_iovlen = n, + }; + ssize_t k; + + assert(h); + assert(iovec || n == 0); + + if (n <= 0) + return 0; + + k = sendmsg(h->fd, &mh, MSG_MORE); + if (k < 0) + return -errno; + + h->digest_valid = false; + + return 0; +} + +static int retrieve_digest(khash *h) { + ssize_t n; + + assert(h); + + if (h->digest_valid) + return 0; + + n = recv(h->fd, h->digest, h->digest_size, 0); + if (n < 0) + return n; + if ((size_t) n != h->digest_size) /* digest size changed? */ + return -EIO; + + h->digest_valid = true; + + return 0; +} + +int khash_digest_data(khash *h, const void **ret) { + int r; + + assert(h); + assert(ret); + + r = retrieve_digest(h); + if (r < 0) + return r; + + *ret = h->digest; + return 0; +} + +int khash_digest_string(khash *h, char **ret) { + int r; + char *p; + + assert(h); + assert(ret); + + r = retrieve_digest(h); + if (r < 0) + return r; + + p = hexmem(h->digest, h->digest_size); + if (!p) + return -ENOMEM; + + *ret = p; + return 0; +} diff --git a/src/basic/khash.h b/src/basic/khash.h new file mode 100644 index 0000000000..410f3020e0 --- /dev/null +++ b/src/basic/khash.h @@ -0,0 +1,53 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> +#include <sys/types.h> +#include <sys/uio.h> + +#include "macro.h" + +typedef struct khash khash; + +/* For plain hash functions. Hash functions commonly supported on today's kernels are: crc32c, crct10dif, crc32, + * sha224, sha256, sha512, sha384, sha1, md5, md4, sha3-224, sha3-256, sha3-384, sha3-512, and more. */ +int khash_new(khash **ret, const char *algorithm); + +/* For keyed hash functions. Hash functions commonly supported on today's kernels are: hmac(sha256), cmac(aes), + * cmac(des3_ede), hmac(sha3-512), hmac(sha3-384), hmac(sha3-256), hmac(sha3-224), hmac(rmd160), hmac(rmd128), + * hmac(sha224), hmac(sha512), hmac(sha384), hmac(sha1), hmac(md5), and more. */ +int khash_new_with_key(khash **ret, const char *algorithm, const void *key, size_t key_size); + +int khash_dup(khash *h, khash **ret); +khash* khash_unref(khash *h); + +const char *khash_get_algorithm(khash *h); +size_t khash_get_size(khash *h); + +int khash_reset(khash *h); + +int khash_put(khash *h, const void *buffer, size_t size); +int khash_put_iovec(khash *h, const struct iovec *iovec, size_t n); + +int khash_digest_data(khash *h, const void **ret); +int khash_digest_string(khash *h, char **ret); + +DEFINE_TRIVIAL_CLEANUP_FUNC(khash*, khash_unref); diff --git a/src/basic/log.c b/src/basic/log.c index 4919d175da..36efc9ac7d 100644 --- a/src/basic/log.c +++ b/src/basic/log.c @@ -37,7 +37,7 @@ #include "alloc-util.h" #include "fd-util.h" -#include "formats-util.h" +#include "format-util.h" #include "io-util.h" #include "log.h" #include "macro.h" @@ -72,6 +72,7 @@ static bool show_color = false; static bool show_location = false; static bool upgrade_syslog_to_journal = false; +static bool always_reopen_console = false; /* Akin to glibc's __abort_msg; which is private and we hence cannot * use here. */ @@ -95,7 +96,7 @@ static int log_open_console(void) { if (console_fd >= 0) return 0; - if (getpid() == 1) { + if (always_reopen_console) { console_fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); if (console_fd < 0) return console_fd; @@ -243,13 +244,13 @@ int log_open(void) { return 0; } - if ((log_target != LOG_TARGET_AUTO && log_target != LOG_TARGET_SAFE) || + if (!IN_SET(log_target, LOG_TARGET_AUTO, LOG_TARGET_SAFE) || getpid() == 1 || isatty(STDERR_FILENO) <= 0) { - if (log_target == LOG_TARGET_AUTO || - log_target == LOG_TARGET_JOURNAL_OR_KMSG || - log_target == LOG_TARGET_JOURNAL) { + if (IN_SET(log_target, LOG_TARGET_AUTO, + LOG_TARGET_JOURNAL_OR_KMSG, + LOG_TARGET_JOURNAL)) { r = log_open_journal(); if (r >= 0) { log_close_syslog(); @@ -258,8 +259,8 @@ int log_open(void) { } } - if (log_target == LOG_TARGET_SYSLOG_OR_KMSG || - log_target == LOG_TARGET_SYSLOG) { + if (IN_SET(log_target, LOG_TARGET_SYSLOG_OR_KMSG, + LOG_TARGET_SYSLOG)) { r = log_open_syslog(); if (r >= 0) { log_close_journal(); @@ -268,11 +269,11 @@ int log_open(void) { } } - if (log_target == LOG_TARGET_AUTO || - log_target == LOG_TARGET_SAFE || - log_target == LOG_TARGET_JOURNAL_OR_KMSG || - log_target == LOG_TARGET_SYSLOG_OR_KMSG || - log_target == LOG_TARGET_KMSG) { + if (IN_SET(log_target, LOG_TARGET_AUTO, + LOG_TARGET_SAFE, + LOG_TARGET_JOURNAL_OR_KMSG, + LOG_TARGET_SYSLOG_OR_KMSG, + LOG_TARGET_KMSG)) { r = log_open_kmsg(); if (r >= 0) { log_close_journal(); @@ -500,7 +501,7 @@ static int log_do_header( line ? "CODE_LINE=" : "", line ? 1 : 0, line, /* %.0d means no output too, special case for 0 */ line ? "\n" : "", - isempty(func) ? "" : "CODE_FUNCTION=", + isempty(func) ? "" : "CODE_FUNC=", isempty(func) ? "" : func, isempty(func) ? "" : "\n", error ? "ERRNO=" : "", @@ -588,9 +589,9 @@ static int log_dispatch( if ((e = strpbrk(buffer, NEWLINE))) *(e++) = 0; - if (log_target == LOG_TARGET_AUTO || - log_target == LOG_TARGET_JOURNAL_OR_KMSG || - log_target == LOG_TARGET_JOURNAL) { + if (IN_SET(log_target, LOG_TARGET_AUTO, + LOG_TARGET_JOURNAL_OR_KMSG, + LOG_TARGET_JOURNAL)) { k = write_to_journal(level, error, file, line, func, object_field, object, extra_field, extra, buffer); if (k < 0) { @@ -600,8 +601,8 @@ static int log_dispatch( } } - if (log_target == LOG_TARGET_SYSLOG_OR_KMSG || - log_target == LOG_TARGET_SYSLOG) { + if (IN_SET(log_target, LOG_TARGET_SYSLOG_OR_KMSG, + LOG_TARGET_SYSLOG)) { k = write_to_syslog(level, error, file, line, func, buffer); if (k < 0) { @@ -612,11 +613,11 @@ static int log_dispatch( } if (k <= 0 && - (log_target == LOG_TARGET_AUTO || - log_target == LOG_TARGET_SAFE || - log_target == LOG_TARGET_SYSLOG_OR_KMSG || - log_target == LOG_TARGET_JOURNAL_OR_KMSG || - log_target == LOG_TARGET_KMSG)) { + IN_SET(log_target, LOG_TARGET_AUTO, + LOG_TARGET_SAFE, + LOG_TARGET_SYSLOG_OR_KMSG, + LOG_TARGET_JOURNAL_OR_KMSG, + LOG_TARGET_KMSG)) { k = write_to_kmsg(level, error, file, line, func, buffer); if (k < 0) { @@ -881,9 +882,9 @@ int log_struct_internal( if ((level & LOG_FACMASK) == 0) level = log_facility | LOG_PRI(level); - if ((log_target == LOG_TARGET_AUTO || - log_target == LOG_TARGET_JOURNAL_OR_KMSG || - log_target == LOG_TARGET_JOURNAL) && + if (IN_SET(log_target, LOG_TARGET_AUTO, + LOG_TARGET_JOURNAL_OR_KMSG, + LOG_TARGET_JOURNAL) && journal_fd >= 0) { char header[LINE_MAX]; struct iovec iovec[17] = {}; @@ -981,24 +982,30 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat if (streq(key, "debug") && !value) log_set_max_level(LOG_DEBUG); - else if (streq(key, "systemd.log_target") && value) { + else if (proc_cmdline_key_streq(key, "systemd.log_target")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; if (log_set_target_from_string(value) < 0) log_warning("Failed to parse log target '%s'. Ignoring.", value); - } else if (streq(key, "systemd.log_level") && value) { + } else if (proc_cmdline_key_streq(key, "systemd.log_level")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; if (log_set_max_level_from_string(value) < 0) log_warning("Failed to parse log level '%s'. Ignoring.", value); - } else if (streq(key, "systemd.log_color") && value) { + } else if (proc_cmdline_key_streq(key, "systemd.log_color")) { - if (log_show_color_from_string(value) < 0) + if (log_show_color_from_string(value ?: "1") < 0) log_warning("Failed to parse log color setting '%s'. Ignoring.", value); - } else if (streq(key, "systemd.log_location") && value) { + } else if (proc_cmdline_key_streq(key, "systemd.log_location")) { - if (log_show_location_from_string(value) < 0) + if (log_show_location_from_string(value ?: "1") < 0) log_warning("Failed to parse log location setting '%s'. Ignoring.", value); } @@ -1009,10 +1016,9 @@ void log_parse_environment(void) { const char *e; if (get_ctty_devnr(0, NULL) < 0) - /* 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, NULL, true); + /* Only try to read the command line in daemons. We assume that anything that has a controlling tty is + user stuff. */ + (void) proc_cmdline_parse(parse_proc_cmdline_item, NULL, PROC_CMDLINE_STRIP_RD_PREFIX); e = secure_getenv("SYSTEMD_LOG_TARGET"); if (e && log_set_target_from_string(e) < 0) @@ -1078,8 +1084,8 @@ int log_show_location_from_string(const char *e) { } bool log_on_console(void) { - if (log_target == LOG_TARGET_CONSOLE || - log_target == LOG_TARGET_CONSOLE_PREFIXED) + if (IN_SET(log_target, LOG_TARGET_CONSOLE, + LOG_TARGET_CONSOLE_PREFIXED)) return true; return syslog_fd < 0 && kmsg_fd < 0 && journal_fd < 0; @@ -1134,8 +1140,8 @@ int log_syntax_internal( PROTECT_ERRNO; char buffer[LINE_MAX]; - int r; va_list ap; + const char *unit_fmt = NULL; if (error < 0) error = -error; @@ -1154,24 +1160,19 @@ int log_syntax_internal( va_end(ap); if (unit) - r = log_struct_internal( - level, error, - file, line, func, - getpid() == 1 ? "UNIT=%s" : "USER_UNIT=%s", unit, - LOG_MESSAGE_ID(SD_MESSAGE_INVALID_CONFIGURATION), - "CONFIG_FILE=%s", config_file, - "CONFIG_LINE=%u", config_line, - LOG_MESSAGE("[%s:%u] %s", config_file, config_line, buffer), - NULL); - else - r = log_struct_internal( - level, error, - file, line, func, - LOG_MESSAGE_ID(SD_MESSAGE_INVALID_CONFIGURATION), - "CONFIG_FILE=%s", config_file, - "CONFIG_LINE=%u", config_line, - LOG_MESSAGE("[%s:%u] %s", config_file, config_line, buffer), - NULL); + unit_fmt = getpid() == 1 ? "UNIT=%s" : "USER_UNIT=%s"; + + return log_struct_internal( + level, error, + file, line, func, + "MESSAGE_ID=" SD_MESSAGE_INVALID_CONFIGURATION_STR, + "CONFIG_FILE=%s", config_file, + "CONFIG_LINE=%u", config_line, + LOG_MESSAGE("%s:%u: %s", config_file, config_line, buffer), + unit_fmt, unit, + NULL); +} - return r; +void log_set_always_reopen_console(bool b) { + always_reopen_console = b; } diff --git a/src/basic/log.h b/src/basic/log.h index 2afee20bb5..72714e02e5 100644 --- a/src/basic/log.h +++ b/src/basic/log.h @@ -214,13 +214,13 @@ bool log_on_console(void) _pure_; const char *log_target_to_string(LogTarget target) _const_; LogTarget log_target_from_string(const char *s) _pure_; -/* Helpers to prepare various fields for structured logging */ +/* Helper to prepare various field for structured logging */ #define LOG_MESSAGE(fmt, ...) "MESSAGE=" fmt, ##__VA_ARGS__ -#define LOG_MESSAGE_ID(x) "MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(x) void log_received_signal(int level, const struct signalfd_siginfo *si); void log_set_upgrade_syslog_to_journal(bool b); +void log_set_always_reopen_console(bool b); int log_syntax_internal( const char *unit, diff --git a/src/basic/missing.h b/src/basic/missing.h index 4c013be608..480462357d 100644 --- a/src/basic/missing.h +++ b/src/basic/missing.h @@ -34,6 +34,7 @@ #include <net/ethernet.h> #include <stdlib.h> #include <sys/resource.h> +#include <sys/socket.h> #include <sys/syscall.h> #include <uchar.h> #include <unistd.h> @@ -50,6 +51,23 @@ #include <linux/btrfs.h> #endif +#ifdef HAVE_LINUX_VM_SOCKETS_H +#include <linux/vm_sockets.h> +#else +#define VMADDR_CID_ANY -1U +struct sockaddr_vm { + unsigned short svm_family; + unsigned short svm_reserved1; + unsigned int svm_port; + unsigned int svm_cid; + unsigned char svm_zero[sizeof(struct sockaddr) - + sizeof(unsigned short) - + sizeof(unsigned short) - + sizeof(unsigned int) - + sizeof(unsigned int)]; +}; +#endif /* !HAVE_LINUX_VM_SOCKETS_H */ + #include "macro.h" #ifndef RLIMIT_RTTIME @@ -143,6 +161,10 @@ #define GRND_RANDOM 0x0002 #endif +#ifndef FS_NOCOW_FL +#define FS_NOCOW_FL 0x00800000 +#endif + #ifndef BTRFS_IOCTL_MAGIC #define BTRFS_IOCTL_MAGIC 0x94 #endif @@ -1022,6 +1044,22 @@ struct btrfs_ioctl_quota_ctl_args { typedef int32_t key_serial_t; #endif +#ifndef KEYCTL_JOIN_SESSION_KEYRING +#define KEYCTL_JOIN_SESSION_KEYRING 1 +#endif + +#ifndef KEYCTL_CHOWN +#define KEYCTL_CHOWN 4 +#endif + +#ifndef KEYCTL_SETPERM +#define KEYCTL_SETPERM 5 +#endif + +#ifndef KEYCTL_DESCRIBE +#define KEYCTL_DESCRIBE 6 +#endif + #ifndef KEYCTL_READ #define KEYCTL_READ 11 #endif @@ -1030,10 +1068,44 @@ typedef int32_t key_serial_t; #define KEYCTL_SET_TIMEOUT 15 #endif +#ifndef KEY_POS_VIEW +#define KEY_POS_VIEW 0x01000000 +#define KEY_POS_READ 0x02000000 +#define KEY_POS_WRITE 0x04000000 +#define KEY_POS_SEARCH 0x08000000 +#define KEY_POS_LINK 0x10000000 +#define KEY_POS_SETATTR 0x20000000 + +#define KEY_USR_VIEW 0x00010000 +#define KEY_USR_READ 0x00020000 +#define KEY_USR_WRITE 0x00040000 +#define KEY_USR_SEARCH 0x00080000 +#define KEY_USR_LINK 0x00100000 +#define KEY_USR_SETATTR 0x00200000 + +#define KEY_GRP_VIEW 0x00000100 +#define KEY_GRP_READ 0x00000200 +#define KEY_GRP_WRITE 0x00000400 +#define KEY_GRP_SEARCH 0x00000800 +#define KEY_GRP_LINK 0x00001000 +#define KEY_GRP_SETATTR 0x00002000 + +#define KEY_OTH_VIEW 0x00000001 +#define KEY_OTH_READ 0x00000002 +#define KEY_OTH_WRITE 0x00000004 +#define KEY_OTH_SEARCH 0x00000008 +#define KEY_OTH_LINK 0x00000010 +#define KEY_OTH_SETATTR 0x00000020 +#endif + #ifndef KEY_SPEC_USER_KEYRING #define KEY_SPEC_USER_KEYRING -4 #endif +#ifndef KEY_SPEC_SESSION_KEYRING +#define KEY_SPEC_SESSION_KEYRING -3 +#endif + #ifndef PR_CAP_AMBIENT #define PR_CAP_AMBIENT 47 #endif @@ -1076,6 +1148,41 @@ typedef int32_t key_serial_t; #define IFA_F_MCAUTOJOIN 0x400 #endif +#ifndef HAVE_STRUCT_ETHTOOL_LINK_SETTINGS + +#define ETHTOOL_GLINKSETTINGS 0x0000004c /* Get ethtool_link_settings */ +#define ETHTOOL_SLINKSETTINGS 0x0000004d /* Set ethtool_link_settings */ + +struct ethtool_link_settings { + __u32 cmd; + __u32 speed; + __u8 duplex; + __u8 port; + __u8 phy_address; + __u8 autoneg; + __u8 mdio_support; + __u8 eth_tp_mdix; + __u8 eth_tp_mdix_ctrl; + __s8 link_mode_masks_nwords; + __u32 reserved[8]; + __u32 link_mode_masks[0]; + /* layout of link_mode_masks fields: + * __u32 map_supported[link_mode_masks_nwords]; + * __u32 map_advertising[link_mode_masks_nwords]; + * __u32 map_lp_advertising[link_mode_masks_nwords]; + */ +}; + +#endif + +#endif + +#ifndef SOL_ALG +#define SOL_ALG 279 +#endif + +#ifndef AF_VSOCK +#define AF_VSOCK 40 #endif #include "missing_syscall.h" diff --git a/src/basic/missing_syscall.h b/src/basic/missing_syscall.h index e6fd67cb9d..9fc4156564 100644 --- a/src/basic/missing_syscall.h +++ b/src/basic/missing_syscall.h @@ -194,6 +194,8 @@ static inline pid_t raw_getpid(void) { # define __NR_renameat2 316 # elif defined __arm__ # define __NR_renameat2 382 +# elif defined __aarch64__ +# define __NR_renameat2 276 # elif defined _MIPS_SIM # if _MIPS_SIM == _MIPS_SIM_ABI32 # define __NR_renameat2 4351 diff --git a/src/basic/mount-util.c b/src/basic/mount-util.c index c8f8022578..a8fd63fb45 100644 --- a/src/basic/mount-util.c +++ b/src/basic/mount-util.c @@ -29,6 +29,7 @@ #include "escape.h" #include "fd-util.h" #include "fileio.h" +#include "fs-util.h" #include "hashmap.h" #include "mount-util.h" #include "parse-util.h" @@ -111,9 +112,10 @@ int fd_is_mount_point(int fd, const char *filename, int flags) { r = name_to_handle_at(fd, filename, &h.handle, &mount_id, flags); if (r < 0) { - if (errno == ENOSYS) - /* This kernel does not support name_to_handle_at() - * fall back to simpler logic. */ + if (IN_SET(errno, ENOSYS, EACCES, EPERM)) + /* This kernel does not support name_to_handle_at() at all, or the syscall was blocked (maybe + * through seccomp, because we are running inside of a container?): fall back to simpler + * logic. */ goto fallback_fdinfo; else if (errno == EOPNOTSUPP) /* This kernel or file system does not support @@ -162,7 +164,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 (IN_SET(r, -EOPNOTSUPP, -EACCES)) + if (IN_SET(r, -EOPNOTSUPP, -EACCES, -EPERM)) goto fallback_fstat; if (r < 0) return r; @@ -205,9 +207,10 @@ fallback_fstat: } /* flags can be AT_SYMLINK_FOLLOW or 0 */ -int path_is_mount_point(const char *t, int flags) { - _cleanup_close_ int fd = -1; +int path_is_mount_point(const char *t, const char *root, int flags) { _cleanup_free_ char *canonical = NULL, *parent = NULL; + _cleanup_close_ int fd = -1; + int r; assert(t); @@ -219,9 +222,9 @@ int path_is_mount_point(const char *t, int flags) { * /bin -> /usr/bin/ and /usr is a mount point, then the parent that we * look at needs to be /usr, not /. */ if (flags & AT_SYMLINK_FOLLOW) { - canonical = canonicalize_file_name(t); - if (!canonical) - return -errno; + r = chase_symlinks(t, root, 0, &canonical); + if (r < 0) + return r; t = canonical; } @@ -473,7 +476,7 @@ int bind_remount_recursive(const char *prefix, bool ro, char **blacklist) { return r; /* Deal with mount points that are obstructed by a later mount */ - r = path_is_mount_point(x, 0); + r = path_is_mount_point(x, NULL, 0); if (r == -ENOENT || r == 0) continue; if (r < 0) @@ -642,7 +645,7 @@ static char* mount_flags_to_string(long unsigned flags) { FLAG(MS_I_VERSION), FLAG(MS_STRICTATIME), FLAG(MS_LAZYTIME), - y, NULL); + y); if (!x) return NULL; if (!y) @@ -671,6 +674,9 @@ int mount_verbose( else if ((flags & MS_BIND) && !type) log_debug("Bind-mounting %s on %s (%s \"%s\")...", what, where, strnull(fl), strempty(options)); + else if (flags & MS_MOVE) + log_debug("Moving mount %s → %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)); @@ -687,3 +693,35 @@ int umount_verbose(const char *what) { return log_error_errno(errno, "Failed to unmount %s: %m", what); return 0; } + +const char *mount_propagation_flags_to_string(unsigned long flags) { + + switch (flags & (MS_SHARED|MS_SLAVE|MS_PRIVATE)) { + case 0: + return ""; + case MS_SHARED: + return "shared"; + case MS_SLAVE: + return "slave"; + case MS_PRIVATE: + return "private"; + } + + return NULL; +} + + +int mount_propagation_flags_from_string(const char *name, unsigned long *ret) { + + if (isempty(name)) + *ret = 0; + else if (streq(name, "shared")) + *ret = MS_SHARED; + else if (streq(name, "slave")) + *ret = MS_SLAVE; + else if (streq(name, "private")) + *ret = MS_PRIVATE; + else + return -EINVAL; + return 0; +} diff --git a/src/basic/mount-util.h b/src/basic/mount-util.h index 4f305df19f..1615c94e9a 100644 --- a/src/basic/mount-util.h +++ b/src/basic/mount-util.h @@ -30,7 +30,7 @@ #include "missing.h" int fd_is_mount_point(int fd, const char *filename, int flags); -int path_is_mount_point(const char *path, int flags); +int path_is_mount_point(const char *path, const char *root, int flags); int repeat_unmount(const char *path, int flags); @@ -61,3 +61,6 @@ int mount_verbose( unsigned long flags, const char *options); int umount_verbose(const char *where); + +const char *mount_propagation_flags_to_string(unsigned long flags); +int mount_propagation_flags_from_string(const char *name, unsigned long *ret); diff --git a/src/basic/nss-util.h b/src/basic/nss-util.h index e7844fff96..9d927a8227 100644 --- a/src/basic/nss-util.h +++ b/src/basic/nss-util.h @@ -27,6 +27,10 @@ #define NSS_SIGNALS_BLOCK SIGALRM,SIGVTALRM,SIGPIPE,SIGCHLD,SIGTSTP,SIGIO,SIGHUP,SIGUSR1,SIGUSR2,SIGPROF,SIGURG,SIGWINCH +#ifndef DEPRECATED_RES_USE_INET6 +# define DEPRECATED_RES_USE_INET6 0x00002000 +#endif + #define NSS_GETHOSTBYNAME_PROTOTYPES(module) \ enum nss_status _nss_##module##_gethostbyname4_r( \ const char *name, \ @@ -92,7 +96,7 @@ enum nss_status _nss_##module##_gethostbyname_r( \ int *errnop, int *h_errnop) { \ enum nss_status ret = NSS_STATUS_NOTFOUND; \ \ - if (_res.options & RES_USE_INET6) \ + if (_res.options & DEPRECATED_RES_USE_INET6) \ ret = _nss_##module##_gethostbyname3_r( \ name, \ AF_INET6, \ diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c index c98815b9bc..6e58ced6f5 100644 --- a/src/basic/parse-util.c +++ b/src/basic/parse-util.c @@ -574,3 +574,19 @@ int parse_nice(const char *p, int *ret) { *ret = n; return 0; } + +int parse_ip_port(const char *s, uint16_t *ret) { + uint16_t l; + int r; + + r = safe_atou16(s, &l); + if (r < 0) + return r; + + if (l == 0) + return -EINVAL; + + *ret = (uint16_t) l; + + return 0; +} diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h index 461e1cd4d8..4d132f0de5 100644 --- a/src/basic/parse-util.h +++ b/src/basic/parse-util.h @@ -110,3 +110,5 @@ int parse_percent_unbounded(const char *p); int parse_percent(const char *p); int parse_nice(const char *p, int *ret); + +int parse_ip_port(const char *s, uint16_t *ret); diff --git a/src/basic/path-util.c b/src/basic/path-util.c index fd38f51c4c..1313a52c9c 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -83,7 +83,7 @@ char *path_make_absolute(const char *p, const char *prefix) { if (path_is_absolute(p) || !prefix) return strdup(p); - return strjoin(prefix, "/", p, NULL); + return strjoin(prefix, "/", p); } int path_make_absolute_cwd(const char *p, char **ret) { @@ -104,7 +104,7 @@ int path_make_absolute_cwd(const char *p, char **ret) { if (!cwd) return negative_errno(); - c = strjoin(cwd, "/", p, NULL); + c = strjoin(cwd, "/", p); } if (!c) return -ENOMEM; @@ -220,10 +220,11 @@ int path_strv_make_absolute_cwd(char **l) { return 0; } -char **path_strv_resolve(char **l, const char *prefix) { +char **path_strv_resolve(char **l, const char *root) { char **s; unsigned k = 0; bool enomem = false; + int r; if (strv_isempty(l)) return l; @@ -233,17 +234,17 @@ char **path_strv_resolve(char **l, const char *prefix) { * changes on failure. */ STRV_FOREACH(s, l) { - char *t, *u; _cleanup_free_ char *orig = NULL; + char *t, *u; if (!path_is_absolute(*s)) { free(*s); continue; } - if (prefix) { + if (root) { orig = *s; - t = strappend(prefix, orig); + t = prefix_root(root, orig); if (!t) { enomem = true; continue; @@ -251,28 +252,26 @@ char **path_strv_resolve(char **l, const char *prefix) { } else t = *s; - errno = 0; - u = canonicalize_file_name(t); - if (!u) { - if (errno == ENOENT) { - if (prefix) { - u = orig; - orig = NULL; - free(t); - } else - u = t; - } else { + r = chase_symlinks(t, root, 0, &u); + if (r == -ENOENT) { + if (root) { + u = orig; + orig = NULL; free(t); - if (errno == ENOMEM || errno == 0) - enomem = true; + } else + u = t; + } else if (r < 0) { + free(t); - continue; - } - } else if (prefix) { + if (r == -ENOMEM) + enomem = true; + + continue; + } else if (root) { char *x; free(t); - x = path_startswith(u, prefix); + x = path_startswith(u, root); if (x) { /* restore the slash if it was lost */ if (!startswith(x, "/")) @@ -304,12 +303,12 @@ char **path_strv_resolve(char **l, const char *prefix) { return l; } -char **path_strv_resolve_uniq(char **l, const char *prefix) { +char **path_strv_resolve_uniq(char **l, const char *root) { if (strv_isempty(l)) return l; - if (!path_strv_resolve(l, prefix)) + if (!path_strv_resolve(l, root)) return NULL; return strv_uniq(l); @@ -454,13 +453,11 @@ char* path_join(const char *root, const char *path, const char *rest) { return strjoin(root, endswith(root, "/") ? "" : "/", path[0] == '/' ? path+1 : path, rest ? (endswith(path, "/") ? "" : "/") : NULL, - rest && rest[0] == '/' ? rest+1 : rest, - NULL); + rest && rest[0] == '/' ? rest+1 : rest); else return strjoin(path, rest ? (endswith(path, "/") ? "" : "/") : NULL, - rest && rest[0] == '/' ? rest+1 : rest, - NULL); + rest && rest[0] == '/' ? rest+1 : rest); } int find_binary(const char *name, char **ret) { @@ -504,7 +501,7 @@ int find_binary(const char *name, char **ret) { if (!path_is_absolute(element)) continue; - j = strjoin(element, "/", name, NULL); + j = strjoin(element, "/", name); if (!j) return -ENOMEM; @@ -702,10 +699,7 @@ bool filename_is_valid(const char *p) { if (isempty(p)) return false; - if (streq(p, ".")) - return false; - - if (streq(p, "..")) + if (dot_or_dot_dot(p)) return false; e = strchrnul(p, '/'); @@ -723,14 +717,17 @@ bool path_is_safe(const char *p) { if (isempty(p)) return false; - if (streq(p, "..") || startswith(p, "../") || endswith(p, "/..") || strstr(p, "/../")) + if (dot_or_dot_dot(p)) + return false; + + if (startswith(p, "../") || endswith(p, "/..") || strstr(p, "/../")) return false; if (strlen(p)+1 > PATH_MAX) return false; /* The following two checks are not really dangerous, but hey, they still are confusing */ - if (streq(p, ".") || startswith(p, "./") || endswith(p, "/.") || strstr(p, "/./")) + if (startswith(p, "./") || endswith(p, "/.") || strstr(p, "/./")) return false; if (strstr(p, "//")) @@ -895,3 +892,16 @@ int systemd_installation_has_version(const char *root, unsigned minimal_version) return false; } + +bool dot_or_dot_dot(const char *path) { + if (!path) + return false; + if (path[0] != '.') + return false; + if (path[1] == 0) + return true; + if (path[1] != '.') + return false; + + return path[2] == 0; +} diff --git a/src/basic/path-util.h b/src/basic/path-util.h index 66545f52d9..35aef3adc8 100644 --- a/src/basic/path-util.h +++ b/src/basic/path-util.h @@ -24,6 +24,7 @@ #include <stddef.h> #include "macro.h" +#include "string-util.h" #include "time-util.h" #define DEFAULT_PATH_NORMAL "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin" @@ -65,9 +66,21 @@ static inline bool path_equal_ptr(const char *a, const char *b) { _found; \ }) +#define PATH_STARTSWITH_SET(p, ...) \ + ({ \ + char **s; \ + bool _found = false; \ + STRV_FOREACH(s, STRV_MAKE(__VA_ARGS__)) \ + if (path_startswith(p, *s)) { \ + _found = true; \ + break; \ + } \ + _found; \ + }) + int path_strv_make_absolute_cwd(char **l); -char** path_strv_resolve(char **l, const char *prefix); -char** path_strv_resolve_uniq(char **l, const char *prefix); +char** path_strv_resolve(char **l, const char *root); +char** path_strv_resolve_uniq(char **l, const char *root); int find_binary(const char *name, char **filename); @@ -128,3 +141,5 @@ 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); + +bool dot_or_dot_dot(const char *path); diff --git a/src/basic/proc-cmdline.c b/src/basic/proc-cmdline.c index 8297a222b7..8592a428d5 100644 --- a/src/basic/proc-cmdline.c +++ b/src/basic/proc-cmdline.c @@ -34,17 +34,30 @@ #include "virt.h" int proc_cmdline(char **ret) { + const char *e; assert(ret); + /* For testing purposes it is sometimes useful to be able to override what we consider /proc/cmdline to be */ + e = secure_getenv("SYSTEMD_PROC_CMDLINE"); + if (e) { + char *m; + + m = strdup(e); + if (!m) + return -ENOMEM; + + *ret = m; + return 0; + } + if (detect_container() > 0) return get_process_cmdline(1, 0, false, ret); else return read_one_line_file("/proc/cmdline", ret); } -int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value, void *data), - void *data, - bool strip_prefix) { +int proc_cmdline_parse(proc_cmdline_parse_t parse_item, void *data, unsigned flags) { + _cleanup_free_ char *line = NULL; const char *p; int r; @@ -58,7 +71,7 @@ int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value, voi p = line; for (;;) { _cleanup_free_ char *word = NULL; - char *value = NULL, *unprefixed; + char *value, *key, *q; r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); if (r < 0) @@ -66,17 +79,23 @@ int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value, voi if (r == 0) break; - /* Filter out arguments that are intended only for the - * initrd */ - unprefixed = startswith(word, "rd."); - if (unprefixed && !in_initrd()) - continue; + key = word; + + /* Filter out arguments that are intended only for the initrd */ + q = startswith(word, "rd."); + if (q) { + if (!in_initrd()) + continue; + + if (flags & PROC_CMDLINE_STRIP_RD_PREFIX) + key = q; + } - value = strchr(word, '='); + value = strchr(key, '='); if (value) *(value++) = 0; - r = parse_item(strip_prefix && unprefixed ? unprefixed : word, value, data); + r = parse_item(key, value, data); if (r < 0) return r; } @@ -84,13 +103,64 @@ int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value, voi return 0; } -int get_proc_cmdline_key(const char *key, char **value) { +static bool relaxed_equal_char(char a, char b) { + + return a == b || + (a == '_' && b == '-') || + (a == '-' && b == '_'); +} + +char *proc_cmdline_key_startswith(const char *s, const char *prefix) { + + assert(s); + assert(prefix); + + /* Much like startswith(), but considers "-" and "_" the same */ + + for (; *prefix != 0; s++, prefix++) + if (!relaxed_equal_char(*s, *prefix)) + return NULL; + + return (char*) s; +} + +bool proc_cmdline_key_streq(const char *x, const char *y) { + assert(x); + assert(y); + + /* Much like streq(), but considers "-" and "_" the same */ + + for (; *x != 0 || *y != 0; x++, y++) + if (!relaxed_equal_char(*x, *y)) + return false; + + return true; +} + +int proc_cmdline_get_key(const char *key, unsigned flags, char **value) { _cleanup_free_ char *line = NULL, *ret = NULL; bool found = false; const char *p; int r; - assert(key); + /* Looks for a specific key on the kernel command line. Supports two modes: + * + * a) The "value" parameter is used. In this case a parameter beginning with the "key" string followed by "=" + * is searched, and the value following this is returned in "value". + * + * b) as above, but the PROC_CMDLINE_VALUE_OPTIONAL flag is set. In this case if the key is found as a + * separate word (i.e. not followed by "=" but instead by whitespace or the end of the command line), then + * this is also accepted, and "value" is returned as NULL. + * + * c) The "value" parameter is NULL. In this case a search for the exact "key" parameter is performed. + * + * In all three cases, > 0 is returned if the key is found, 0 if not. */ + + if (isempty(key)) + return -EINVAL; + + if ((flags & PROC_CMDLINE_VALUE_OPTIONAL) && !value) + return -EINVAL; r = proc_cmdline(&line); if (r < 0) @@ -107,21 +177,26 @@ int get_proc_cmdline_key(const char *key, char **value) { if (r == 0) break; - /* Filter out arguments that are intended only for the - * initrd */ + /* Automatically filter out arguments that are intended only for the initrd, if we are not in the + * initrd. */ if (!in_initrd() && startswith(word, "rd.")) continue; if (value) { - e = startswith(word, key); + e = proc_cmdline_key_startswith(word, key); if (!e) continue; - r = free_and_strdup(&ret, e); - if (r < 0) - return r; + if (*e == '=') { + r = free_and_strdup(&ret, e+1); + if (r < 0) + return r; + + found = true; + + } else if (*e == 0 && (flags & PROC_CMDLINE_VALUE_OPTIONAL)) + found = true; - found = true; } else { if (streq(word, key)) found = true; @@ -134,20 +209,42 @@ int get_proc_cmdline_key(const char *key, char **value) { } return found; +} + +int proc_cmdline_get_bool(const char *key, bool *ret) { + _cleanup_free_ char *v = NULL; + int r; + + assert(ret); + + r = proc_cmdline_get_key(key, PROC_CMDLINE_VALUE_OPTIONAL, &v); + if (r < 0) + return r; + if (r == 0) { + *ret = false; + return 0; + } + + if (v) { /* parameter passed */ + r = parse_boolean(v); + if (r < 0) + return r; + *ret = r; + } else /* no parameter passed */ + *ret = true; + return 1; } int shall_restore_state(void) { - _cleanup_free_ char *value = NULL; + bool ret; int r; - r = get_proc_cmdline_key("systemd.restore_state=", &value); + r = proc_cmdline_get_bool("systemd.restore_state", &ret); if (r < 0) return r; - if (r == 0) - return true; - return parse_boolean(value); + return r > 0 ? ret : true; } static const char * const rlmap[] = { diff --git a/src/basic/proc-cmdline.h b/src/basic/proc-cmdline.h index 6d6ee95c11..ebfed355e9 100644 --- a/src/basic/proc-cmdline.h +++ b/src/basic/proc-cmdline.h @@ -19,11 +19,36 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#include <stdbool.h> + +#include "log.h" + +enum { + PROC_CMDLINE_STRIP_RD_PREFIX = 1, + PROC_CMDLINE_VALUE_OPTIONAL = 2, +}; + +typedef int (*proc_cmdline_parse_t)(const char *key, const char *value, void *data); + int proc_cmdline(char **ret); -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 proc_cmdline_parse(const proc_cmdline_parse_t parse, void *userdata, unsigned flags); + +int proc_cmdline_get_key(const char *parameter, unsigned flags, char **value); +int proc_cmdline_get_bool(const char *key, bool *ret); + +char *proc_cmdline_key_startswith(const char *s, const char *prefix); +bool proc_cmdline_key_streq(const char *x, const char *y); int shall_restore_state(void); const char* runlevel_to_target(const char *rl); + +/* A little helper call, to be used in proc_cmdline_parse_t callbacks */ +static inline bool proc_cmdline_value_missing(const char *key, const char *value) { + if (!value) { + log_warning("Missing argument for %s= kernel command line switch, ignoring.", key); + return true; + } + + return false; +} diff --git a/src/basic/process-util.c b/src/basic/process-util.c index 54b644ad56..0df3fed640 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -27,6 +27,7 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/mman.h> #include <sys/personality.h> #include <sys/prctl.h> #include <sys/types.h> @@ -103,7 +104,7 @@ int get_process_comm(pid_t pid, char **name) { int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line) { _cleanup_fclose_ FILE *f = NULL; bool space = false; - char *r = NULL, *k; + char *k, *ans = NULL; const char *p; int c; @@ -117,7 +118,7 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char * * command line that resolves to the empty string will return the "comm" name of the process instead. * * Returns -ESRCH if the process doesn't exist, and -ENOENT if the process has no command line (and - * comm_fallback is false). */ + * comm_fallback is false). Returns 0 and sets *line otherwise. */ p = procfs_file_alloca(pid, "cmdline"); @@ -131,11 +132,11 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char * if (max_length == 1) { /* If there's only room for one byte, return the empty string */ - r = new0(char, 1); - if (!r) + ans = new0(char, 1); + if (!ans) return -ENOMEM; - *line = r; + *line = ans; return 0; } else if (max_length == 0) { @@ -143,36 +144,36 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char * while ((c = getc(f)) != EOF) { - if (!GREEDY_REALLOC(r, allocated, len+3)) { - free(r); + if (!GREEDY_REALLOC(ans, allocated, len+3)) { + free(ans); return -ENOMEM; } if (isprint(c)) { if (space) { - r[len++] = ' '; + ans[len++] = ' '; space = false; } - r[len++] = c; + ans[len++] = c; } else if (len > 0) space = true; } if (len > 0) - r[len] = 0; + ans[len] = '\0'; else - r = mfree(r); + ans = mfree(ans); } else { bool dotdotdot = false; size_t left; - r = new(char, max_length); - if (!r) + ans = new(char, max_length); + if (!ans) return -ENOMEM; - k = r; + k = ans; left = max_length; while ((c = getc(f)) != EOF) { @@ -196,20 +197,20 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char * *(k++) = (char) c; left--; - } else if (k > r) + } else if (k > ans) space = true; } if (dotdotdot) { if (max_length <= 4) { - k = r; + k = ans; left = max_length; } else { - k = r + max_length - 4; + k = ans + max_length - 4; left = 4; /* Eat up final spaces */ - while (k > r && isspace(k[-1])) { + while (k > ans && isspace(k[-1])) { k--; left++; } @@ -222,11 +223,11 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char * } /* Kernel threads have no argv[] */ - if (isempty(r)) { + if (isempty(ans)) { _cleanup_free_ char *t = NULL; int h; - free(r); + free(ans); if (!comm_fallback) return -ENOENT; @@ -236,22 +237,22 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char * return h; if (max_length == 0) - r = strjoin("[", t, "]", NULL); + ans = strjoin("[", t, "]"); else { size_t l; l = strlen(t); if (l + 3 <= max_length) - r = strjoin("[", t, "]", NULL); + ans = strjoin("[", t, "]"); else if (max_length <= 6) { - r = new(char, max_length); - if (!r) + ans = new(char, max_length); + if (!ans) return -ENOMEM; - memcpy(r, "[...]", max_length-1); - r[max_length-1] = 0; + memcpy(ans, "[...]", max_length-1); + ans[max_length-1] = 0; } else { char *e; @@ -263,38 +264,111 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char * e--; *e = 0; - r = strjoin("[", t, "...]", NULL); + ans = strjoin("[", t, "...]"); } } - if (!r) + if (!ans) return -ENOMEM; } - *line = r; + *line = ans; return 0; } -void rename_process(const char name[8]) { - assert(name); +int rename_process(const char name[]) { + static size_t mm_size = 0; + static char *mm = NULL; + bool truncated = false; + size_t l; + + /* This is a like a poor man's setproctitle(). It changes the comm field, argv[0], and also the glibc's + * internally used name of the process. For the first one a limit of 16 chars applies; to the second one in + * many cases one of 10 (i.e. length of "/sbin/init") — however if we have CAP_SYS_RESOURCES it is unbounded; + * to the third one 7 (i.e. the length of "systemd". If you pass a longer string it will likely be + * truncated. + * + * Returns 0 if a name was set but truncated, > 0 if it was set but not truncated. */ + + if (isempty(name)) + return -EINVAL; /* let's not confuse users unnecessarily with an empty name */ - /* This is a like a poor man's setproctitle(). It changes the - * comm field, argv[0], and also the glibc's internally used - * name of the process. For the first one a limit of 16 chars - * applies, to the second one usually one of 10 (i.e. length - * of "/sbin/init"), to the third one one of 7 (i.e. length of - * "systemd"). If you pass a longer string it will be - * truncated */ + l = strlen(name); + /* First step, change the comm field. */ (void) prctl(PR_SET_NAME, name); + if (l > 15) /* Linux process names can be 15 chars at max */ + truncated = true; + + /* Second step, change glibc's ID of the process name. */ + if (program_invocation_name) { + size_t k; + + k = strlen(program_invocation_name); + strncpy(program_invocation_name, name, k); + if (l > k) + truncated = true; + } + + /* Third step, completely replace the argv[] array the kernel maintains for us. This requires privileges, but + * has the advantage that the argv[] array is exactly what we want it to be, and not filled up with zeros at + * the end. This is the best option for changing /proc/self/cmdline. */ + if (mm_size < l+1) { + size_t nn_size; + char *nn; + + /* Let's not bother with this if we don't have euid == 0. Strictly speaking if people do weird stuff + * with capabilities this could work even for euid != 0, but our own code generally doesn't do that, + * hence let's use this as quick bypass check, to avoid calling mmap() if PR_SET_MM_ARG_START fails + * with EPERM later on anyway. After all geteuid() is dead cheap to call, but mmap() is not. */ + if (geteuid() != 0) { + log_debug("Skipping PR_SET_MM_ARG_START, as we don't have privileges."); + goto use_saved_argv; + } + + nn_size = PAGE_ALIGN(l+1); + nn = mmap(NULL, nn_size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + if (nn == MAP_FAILED) { + log_debug_errno(errno, "mmap() failed: %m"); + goto use_saved_argv; + } + + strncpy(nn, name, nn_size); + + /* Now, let's tell the kernel about this new memory */ + if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nn, 0, 0) < 0) { + log_debug_errno(errno, "PR_SET_MM_ARG_START failed, proceeding without: %m"); + (void) munmap(nn, nn_size); + goto use_saved_argv; + } + + /* And update the end pointer to the new end, too. If this fails, we don't really know what to do, it's + * pretty unlikely that we can rollback, hence we'll just accept the failure, and continue. */ + if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) nn + l + 1, 0, 0) < 0) + log_debug_errno(errno, "PR_SET_MM_ARG_END failed, proceeding without: %m"); - if (program_invocation_name) - strncpy(program_invocation_name, name, strlen(program_invocation_name)); + if (mm) + (void) munmap(mm, mm_size); + + mm = nn; + mm_size = nn_size; + } else + strncpy(mm, name, mm_size); + +use_saved_argv: + /* Fourth step: in all cases we'll also update the original argv[], so that our own code gets it right too if + * it still looks here */ if (saved_argc > 0) { int i; - if (saved_argv[0]) - strncpy(saved_argv[0], name, strlen(saved_argv[0])); + if (saved_argv[0]) { + size_t k; + + k = strlen(saved_argv[0]); + strncpy(saved_argv[0], name, k); + if (l > k) + truncated = true; + } for (i = 1; i < saved_argc; i++) { if (!saved_argv[i]) @@ -303,6 +377,8 @@ void rename_process(const char name[8]) { memzero(saved_argv[i], strlen(saved_argv[i])); } } + + return !truncated; } int is_kernel_thread(pid_t pid) { @@ -627,7 +703,7 @@ int kill_and_sigcont(pid_t pid, int sig) { /* If this worked, also send SIGCONT, unless we already just sent a SIGCONT, or SIGKILL was sent which isn't * affected by a process being suspended anyway. */ - if (r >= 0 && !IN_SET(SIGCONT, SIGKILL)) + if (r >= 0 && !IN_SET(sig, SIGCONT, SIGKILL)) (void) kill(pid, SIGCONT); return r; @@ -675,7 +751,7 @@ int getenv_for_pid(pid_t pid, const char *field, char **_value) { } line[i] = 0; - if (memcmp(line, field, l) == 0 && line[l] == '=') { + if (strneq(line, field, l) && line[l] == '=') { value = strdup(line + l + 1); if (!value) return -ENOMEM; diff --git a/src/basic/process-util.h b/src/basic/process-util.h index 2568e3834f..d378901399 100644 --- a/src/basic/process-util.h +++ b/src/basic/process-util.h @@ -28,7 +28,7 @@ #include <sys/types.h> #include <sys/resource.h> -#include "formats-util.h" +#include "format-util.h" #include "macro.h" #define procfs_file_alloca(pid, field) \ @@ -64,7 +64,7 @@ void sigkill_waitp(pid_t *pid); int kill_and_sigcont(pid_t pid, int sig); -void rename_process(const char name[8]); +int rename_process(const char name[]); int is_kernel_thread(pid_t pid); int getenv_for_pid(pid_t pid, const char *field, char **_value); diff --git a/src/basic/raw-clone.h b/src/basic/raw-clone.h index d473828999..c6e531ada4 100644 --- a/src/basic/raw-clone.h +++ b/src/basic/raw-clone.h @@ -47,8 +47,8 @@ static inline int raw_clone(unsigned long flags) { assert((flags & (CLONE_VM|CLONE_PARENT_SETTID|CLONE_CHILD_SETTID| CLONE_CHILD_CLEARTID|CLONE_SETTLS)) == 0); -#if defined(__s390__) || defined(__CRIS__) - /* On s390 and cris the order of the first and second arguments +#if defined(__s390x__) || defined(__s390__) || defined(__CRIS__) + /* On s390/s390x and cris the order of the first and second arguments * of the raw clone() system call is reversed. */ return (int) syscall(__NR_clone, NULL, flags); #elif defined(__sparc__) && defined(__arch64__) diff --git a/src/basic/rlimit-util.c b/src/basic/rlimit-util.c index ee063720ed..ca834df621 100644 --- a/src/basic/rlimit-util.c +++ b/src/basic/rlimit-util.c @@ -22,7 +22,7 @@ #include "alloc-util.h" #include "extract-word.h" -#include "formats-util.h" +#include "format-util.h" #include "macro.h" #include "missing.h" #include "rlimit-util.h" diff --git a/src/basic/rm-rf.c b/src/basic/rm-rf.c index baa70c2c8d..08497af729 100644 --- a/src/basic/rm-rf.c +++ b/src/basic/rm-rf.c @@ -17,7 +17,6 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -#include <dirent.h> #include <errno.h> #include <fcntl.h> #include <stdbool.h> @@ -28,6 +27,7 @@ #include "btrfs-util.h" #include "cgroup-util.h" +#include "dirent-util.h" #include "fd-util.h" #include "log.h" #include "macro.h" @@ -43,6 +43,7 @@ static bool is_physical_fs(const struct statfs *sfs) { int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) { _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; int ret = 0, r; struct statfs sfs; @@ -78,20 +79,11 @@ int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) { return errno == ENOENT ? 0 : -errno; } - for (;;) { - struct dirent *de; + FOREACH_DIRENT_ALL(de, d, return -errno) { bool is_dir; struct stat st; - errno = 0; - de = readdir(d); - if (!de) { - if (errno > 0 && ret == 0) - ret = -errno; - return ret; - } - - if (streq(de->d_name, ".") || streq(de->d_name, "..")) + if (dot_or_dot_dot(de->d_name)) continue; if (de->d_type == DT_UNKNOWN || @@ -178,6 +170,7 @@ int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) { } } } + return ret; } int rm_rf(const char *path, RemoveFlags flags) { diff --git a/src/basic/rm-rf.h b/src/basic/rm-rf.h index f693a5bb7c..e13f7003e3 100644 --- a/src/basic/rm-rf.h +++ b/src/basic/rm-rf.h @@ -33,8 +33,6 @@ int rm_rf(const char *path, RemoveFlags flags); /* Useful for usage with _cleanup_(), destroys a directory and frees the pointer */ static inline void rm_rf_physical_and_free(char *p) { - if (!p) - return; (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL); free(p); } diff --git a/src/basic/siphash24.c b/src/basic/siphash24.c index 8c1cdc3db6..4bb41786c8 100644 --- a/src/basic/siphash24.c +++ b/src/basic/siphash24.c @@ -127,18 +127,25 @@ void siphash24_compress(const void *_in, size_t inlen, struct siphash *state) { switch (left) { case 7: state->padding |= ((uint64_t) in[6]) << 48; + /* fall through */ case 6: state->padding |= ((uint64_t) in[5]) << 40; + /* fall through */ case 5: state->padding |= ((uint64_t) in[4]) << 32; + /* fall through */ case 4: state->padding |= ((uint64_t) in[3]) << 24; + /* fall through */ case 3: state->padding |= ((uint64_t) in[2]) << 16; + /* fall through */ case 2: state->padding |= ((uint64_t) in[1]) << 8; + /* fall through */ case 1: state->padding |= ((uint64_t) in[0]); + /* fall through */ case 0: break; } diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c index 1662c04705..e5847dce00 100644 --- a/src/basic/socket-util.c +++ b/src/basic/socket-util.c @@ -34,7 +34,7 @@ #include "alloc-util.h" #include "fd-util.h" #include "fileio.h" -#include "formats-util.h" +#include "format-util.h" #include "log.h" #include "macro.h" #include "missing.h" @@ -113,6 +113,30 @@ int socket_address_parse(SocketAddress *a, const char *s) { memcpy(a->sockaddr.un.sun_path+1, s+1, l); a->size = offsetof(struct sockaddr_un, sun_path) + 1 + l; + } else if (startswith(s, "vsock:")) { + /* AF_VSOCK socket in vsock:cid:port notation */ + const char *cid_start = s + strlen("vsock:"); + + e = strchr(cid_start, ':'); + if (!e) + return -EINVAL; + + r = safe_atou(e+1, &u); + if (r < 0) + return r; + + n = strndupa(cid_start, e - cid_start); + if (!isempty(n)) { + r = safe_atou(n, &a->sockaddr.vm.svm_cid); + if (r < 0) + return r; + } else + a->sockaddr.vm.svm_cid = VMADDR_CID_ANY; + + a->sockaddr.vm.svm_family = AF_VSOCK; + a->sockaddr.vm.svm_port = u; + a->size = sizeof(struct sockaddr_vm); + } else { e = strchr(s, ':'); if (e) { @@ -289,6 +313,15 @@ int socket_address_verify(const SocketAddress *a) { return 0; + case AF_VSOCK: + if (a->size != sizeof(struct sockaddr_vm)) + return -EINVAL; + + if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM) + return -EINVAL; + + return 0; + default: return -EAFNOSUPPORT; } @@ -394,6 +427,15 @@ bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) { break; + case AF_VSOCK: + if (a->sockaddr.vm.svm_cid != b->sockaddr.vm.svm_cid) + return false; + + if (a->sockaddr.vm.svm_port != b->sockaddr.vm.svm_port) + return false; + + break; + default: /* Cannot compare, so we assume the addresses are different */ return false; @@ -480,15 +522,27 @@ bool socket_address_matches_fd(const SocketAddress *a, int fd) { return socket_address_equal(a, &b); } -int sockaddr_port(const struct sockaddr *_sa) { +int sockaddr_port(const struct sockaddr *_sa, unsigned *port) { union sockaddr_union *sa = (union sockaddr_union*) _sa; assert(sa); - if (!IN_SET(sa->sa.sa_family, AF_INET, AF_INET6)) - return -EAFNOSUPPORT; + switch (sa->sa.sa_family) { + case AF_INET: + *port = be16toh(sa->in.sin_port); + return 0; + + case AF_INET6: + *port = be16toh(sa->in6.sin6_port); + return 0; - return be16toh(sa->sa.sa_family == AF_INET6 ? sa->in6.sin6_port : sa->in.sin_port); + case AF_VSOCK: + *port = sa->vm.svm_port; + return 0; + + default: + return -EAFNOSUPPORT; + } } int sockaddr_pretty(const struct sockaddr *_sa, socklen_t salen, bool translate_ipv6, bool include_port, char **ret) { @@ -591,6 +645,18 @@ int sockaddr_pretty(const struct sockaddr *_sa, socklen_t salen, bool translate_ break; + case AF_VSOCK: + if (include_port) + r = asprintf(&p, + "vsock:%u:%u", + sa->vm.svm_cid, + sa->vm.svm_port); + else + r = asprintf(&p, "vsock:%u", sa->vm.svm_cid); + if (r < 0) + return -ENOMEM; + break; + default: return -EOPNOTSUPP; } @@ -748,6 +814,9 @@ bool sockaddr_equal(const union sockaddr_union *a, const union sockaddr_union *b if (a->sa.sa_family == AF_INET6) return memcmp(&a->in6.sin6_addr, &b->in6.sin6_addr, sizeof(a->in6.sin6_addr)) == 0; + if (a->sa.sa_family == AF_VSOCK) + return a->vm.svm_cid == b->vm.svm_cid; + return false; } @@ -808,7 +877,7 @@ bool ifname_valid(const char *p) { if (strlen(p) >= IFNAMSIZ) return false; - if (STR_IN_SET(p, ".", "..")) + if (dot_or_dot_dot(p)) return false; while (*p) { @@ -831,6 +900,26 @@ bool ifname_valid(const char *p) { return true; } +bool address_label_valid(const char *p) { + + if (isempty(p)) + return false; + + if (strlen(p) >= IFNAMSIZ) + return false; + + while (*p) { + if ((uint8_t) *p >= 127U) + return false; + + if ((uint8_t) *p <= 31U) + return false; + p++; + } + + return true; +} + int getpeercred(int fd, struct ucred *ucred) { socklen_t n = sizeof(struct ucred); struct ucred u; diff --git a/src/basic/socket-util.h b/src/basic/socket-util.h index 2ef572badb..73c3a339fc 100644 --- a/src/basic/socket-util.h +++ b/src/basic/socket-util.h @@ -30,6 +30,7 @@ #include <linux/if_packet.h> #include "macro.h" +#include "missing.h" #include "util.h" union sockaddr_union { @@ -40,6 +41,7 @@ union sockaddr_union { struct sockaddr_nl nl; struct sockaddr_storage storage; struct sockaddr_ll ll; + struct sockaddr_vm vm; }; typedef struct SocketAddress { @@ -100,7 +102,7 @@ const char* socket_address_get_path(const SocketAddress *a); bool socket_ipv6_is_supported(void); -int sockaddr_port(const struct sockaddr *_sa) _pure_; +int sockaddr_port(const struct sockaddr *_sa, unsigned *port); int sockaddr_pretty(const struct sockaddr *_sa, socklen_t salen, bool translate_ipv6, bool include_port, char **ret); int getpeername_pretty(int fd, bool include_port, char **ret); @@ -124,6 +126,7 @@ int ip_tos_to_string_alloc(int i, char **s); int ip_tos_from_string(const char *s); bool ifname_valid(const char *p); +bool address_label_valid(const char *p); int getpeercred(int fd, struct ucred *ucred); int getpeersec(int fd, char **ret); diff --git a/src/basic/sparse-endian.h b/src/basic/sparse-endian.h index c913fda8c5..a3573b84a9 100644 --- a/src/basic/sparse-endian.h +++ b/src/basic/sparse-endian.h @@ -26,19 +26,19 @@ #include <stdint.h> #ifdef __CHECKER__ -#define __bitwise __attribute__((bitwise)) -#define __force __attribute__((force)) +#define __sd_bitwise __attribute__((bitwise)) +#define __sd_force __attribute__((force)) #else -#define __bitwise -#define __force +#define __sd_bitwise +#define __sd_force #endif -typedef uint16_t __bitwise le16_t; -typedef uint16_t __bitwise be16_t; -typedef uint32_t __bitwise le32_t; -typedef uint32_t __bitwise be32_t; -typedef uint64_t __bitwise le64_t; -typedef uint64_t __bitwise be64_t; +typedef uint16_t __sd_bitwise le16_t; +typedef uint16_t __sd_bitwise be16_t; +typedef uint32_t __sd_bitwise le32_t; +typedef uint32_t __sd_bitwise be32_t; +typedef uint64_t __sd_bitwise le64_t; +typedef uint64_t __sd_bitwise be64_t; #undef htobe16 #undef htole16 @@ -69,20 +69,23 @@ typedef uint64_t __bitwise be64_t; #define bswap_64_on_be(x) __bswap_64(x) #endif -static inline le16_t htole16(uint16_t value) { return (le16_t __force) bswap_16_on_be(value); } -static inline le32_t htole32(uint32_t value) { return (le32_t __force) bswap_32_on_be(value); } -static inline le64_t htole64(uint64_t value) { return (le64_t __force) bswap_64_on_be(value); } +static inline le16_t htole16(uint16_t value) { return (le16_t __sd_force) bswap_16_on_be(value); } +static inline le32_t htole32(uint32_t value) { return (le32_t __sd_force) bswap_32_on_be(value); } +static inline le64_t htole64(uint64_t value) { return (le64_t __sd_force) bswap_64_on_be(value); } -static inline be16_t htobe16(uint16_t value) { return (be16_t __force) bswap_16_on_le(value); } -static inline be32_t htobe32(uint32_t value) { return (be32_t __force) bswap_32_on_le(value); } -static inline be64_t htobe64(uint64_t value) { return (be64_t __force) bswap_64_on_le(value); } +static inline be16_t htobe16(uint16_t value) { return (be16_t __sd_force) bswap_16_on_le(value); } +static inline be32_t htobe32(uint32_t value) { return (be32_t __sd_force) bswap_32_on_le(value); } +static inline be64_t htobe64(uint64_t value) { return (be64_t __sd_force) bswap_64_on_le(value); } -static inline uint16_t le16toh(le16_t value) { return bswap_16_on_be((uint16_t __force)value); } -static inline uint32_t le32toh(le32_t value) { return bswap_32_on_be((uint32_t __force)value); } -static inline uint64_t le64toh(le64_t value) { return bswap_64_on_be((uint64_t __force)value); } +static inline uint16_t le16toh(le16_t value) { return bswap_16_on_be((uint16_t __sd_force)value); } +static inline uint32_t le32toh(le32_t value) { return bswap_32_on_be((uint32_t __sd_force)value); } +static inline uint64_t le64toh(le64_t value) { return bswap_64_on_be((uint64_t __sd_force)value); } -static inline uint16_t be16toh(be16_t value) { return bswap_16_on_le((uint16_t __force)value); } -static inline uint32_t be32toh(be32_t value) { return bswap_32_on_le((uint32_t __force)value); } -static inline uint64_t be64toh(be64_t value) { return bswap_64_on_le((uint64_t __force)value); } +static inline uint16_t be16toh(be16_t value) { return bswap_16_on_le((uint16_t __sd_force)value); } +static inline uint32_t be32toh(be32_t value) { return bswap_32_on_le((uint32_t __sd_force)value); } +static inline uint64_t be64toh(be64_t value) { return bswap_64_on_le((uint64_t __sd_force)value); } + +#undef __sd_bitwise +#undef __sd_force #endif /* SPARSE_ENDIAN_H */ diff --git a/src/basic/special.h b/src/basic/special.h index 5276bcf598..feb8e5fe21 100644 --- a/src/basic/special.h +++ b/src/basic/special.h @@ -103,6 +103,7 @@ #define SPECIAL_DBUS_SOCKET "dbus.socket" #define SPECIAL_JOURNALD_SOCKET "systemd-journald.socket" #define SPECIAL_JOURNALD_SERVICE "systemd-journald.service" +#define SPECIAL_TMPFILES_SETUP_SERVICE "systemd-tmpfiles-setup.service" /* Magic init signals */ #define SPECIAL_KBREQUEST_TARGET "kbrequest.target" diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 309e84b93d..7e1914aa14 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -28,6 +28,7 @@ #include "dirent-util.h" #include "fd-util.h" +#include "fs-util.h" #include "macro.h" #include "missing.h" #include "stat-util.h" @@ -143,22 +144,29 @@ int path_is_read_only_fs(const char *path) { } int path_is_os_tree(const char *path) { - char *p; int r; assert(path); - /* We use /usr/lib/os-release as flag file if something is an OS */ - p = strjoina(path, "/usr/lib/os-release"); - r = access(p, F_OK); - if (r >= 0) - return 1; + /* Does the path exist at all? If not, generate an error immediately. This is useful so that a missing root dir + * always results in -ENOENT, and we can properly distuingish the case where the whole root doesn't exist from + * the case where just the os-release file is missing. */ + if (laccess(path, F_OK) < 0) + return -errno; - /* Also check for the old location in /etc, just in case. */ - p = strjoina(path, "/etc/os-release"); - r = access(p, F_OK); + /* We use /usr/lib/os-release as flag file if something is an OS */ + r = chase_symlinks("/usr/lib/os-release", path, CHASE_PREFIX_ROOT, NULL); + if (r == -ENOENT) { + + /* Also check for the old location in /etc, just in case. */ + r = chase_symlinks("/etc/os-release", path, CHASE_PREFIX_ROOT, NULL); + if (r == -ENOENT) + return 0; /* We got nothing */ + } + if (r < 0) + return r; - return r >= 0; + return 1; } int files_same(const char *filea, const char *fileb) { @@ -196,7 +204,7 @@ int fd_check_fstype(int fd, statfs_f_type_t magic_value) { int path_check_fstype(const char *path, statfs_f_type_t magic_value) { _cleanup_close_ int fd = -1; - fd = open(path, O_RDONLY); + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_PATH); if (fd < 0) return -errno; @@ -216,3 +224,13 @@ int fd_is_temporary_fs(int fd) { return is_temporary_fs(&s); } + +int path_is_temporary_fs(const char *path) { + _cleanup_close_ int fd = -1; + + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_PATH); + if (fd < 0) + return -errno; + + return fd_is_temporary_fs(fd); +} diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index 56d28f791e..5d571efe18 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -61,6 +61,7 @@ int path_check_fstype(const char *path, statfs_f_type_t magic_value); bool is_temporary_fs(const struct statfs *s) _pure_; int fd_is_temporary_fs(int fd); +int path_is_temporary_fs(const char *path); /* Because statfs.t_type can be int on some architectures, we have to cast * the const magic to the type, otherwise the compiler warns about diff --git a/src/basic/string-util.c b/src/basic/string-util.c index 6b06e643c9..9d2f4bc8f9 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -218,7 +218,7 @@ char *strappend(const char *s, const char *suffix) { return strnappend(s, suffix, suffix ? strlen(suffix) : 0); } -char *strjoin(const char *x, ...) { +char *strjoin_real(const char *x, ...) { va_list ap; size_t l; char *r, *p; @@ -821,6 +821,7 @@ int free_and_strdup(char **p, const char *s) { return 1; } +#if !HAVE_DECL_EXPLICIT_BZERO /* * Pointer to memset is volatile so that compiler must de-reference * the pointer and can't assume that it points to any function in @@ -831,19 +832,19 @@ typedef void *(*memset_t)(void *,int,size_t); static volatile memset_t memset_func = memset; -void* memory_erase(void *p, size_t l) { - return memset_func(p, 'x', l); +void explicit_bzero(void *p, size_t l) { + memset_func(p, '\0', l); } +#endif char* string_erase(char *x) { - if (!x) return NULL; /* A delicious drop of snake-oil! To be called on memory where * we stored passphrases or so, after we used them. */ - - return memory_erase(x, strlen(x)); + explicit_bzero(x, strlen(x)); + return x; } char *string_free_erase(char *s) { diff --git a/src/basic/string-util.h b/src/basic/string-util.h index d029d538bd..be44dedff4 100644 --- a/src/basic/string-util.h +++ b/src/basic/string-util.h @@ -107,16 +107,14 @@ const char* split(const char **state, size_t *l, const char *separator, bool quo #define FOREACH_WORD_SEPARATOR(word, length, s, separator, state) \ _FOREACH_WORD(word, length, s, separator, false, state) -#define FOREACH_WORD_QUOTED(word, length, s, state) \ - _FOREACH_WORD(word, length, s, WHITESPACE, true, state) - #define _FOREACH_WORD(word, length, s, separator, quoted, state) \ for ((state) = (s), (word) = split(&(state), &(length), (separator), (quoted)); (word); (word) = split(&(state), &(length), (separator), (quoted))) char *strappend(const char *s, const char *suffix); char *strnappend(const char *s, const char *suffix, size_t length); -char *strjoin(const char *x, ...) _sentinel_; +char *strjoin_real(const char *x, ...) _sentinel_; +#define strjoin(a, ...) strjoin_real((a), __VA_ARGS__, NULL) #define strjoina(a, ...) \ ({ \ @@ -191,7 +189,10 @@ static inline void *memmem_safe(const void *haystack, size_t haystacklen, const return memmem(haystack, haystacklen, needle, needlelen); } -void* memory_erase(void *p, size_t l); +#if !HAVE_DECL_EXPLICIT_BZERO +void explicit_bzero(void *p, size_t l); +#endif + char *string_erase(char *x); char *string_free_erase(char *s); diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index eafdea9eb3..9a8ef825c5 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -144,12 +144,14 @@ int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) { return 0; } -int ask_char(char *ret, const char *replies, const char *text, ...) { +#define DEFAULT_ASK_REFRESH_USEC (2*USEC_PER_SEC) + +int ask_char(char *ret, const char *replies, const char *fmt, ...) { int r; assert(ret); assert(replies); - assert(text); + assert(fmt); for (;;) { va_list ap; @@ -159,8 +161,10 @@ int ask_char(char *ret, const char *replies, const char *text, ...) { if (colors_enabled()) fputs(ANSI_HIGHLIGHT, stdout); - va_start(ap, text); - vprintf(text, ap); + putchar('\r'); + + va_start(ap, fmt); + vprintf(fmt, ap); va_end(ap); if (colors_enabled()) @@ -168,9 +172,12 @@ int ask_char(char *ret, const char *replies, const char *text, ...) { fflush(stdout); - r = read_one_char(stdin, &c, USEC_INFINITY, &need_nl); + r = read_one_char(stdin, &c, DEFAULT_ASK_REFRESH_USEC, &need_nl); if (r < 0) { + if (r == -ETIMEDOUT) + continue; + if (r == -EBADMSG) { puts("Bad input, please try again."); continue; @@ -455,7 +462,7 @@ int acquire_terminal( goto fail; } - r = fd_wait_for_event(fd, POLLIN, ts + timeout - n); + r = fd_wait_for_event(notify, POLLIN, ts + timeout - n); if (r < 0) goto fail; diff --git a/src/basic/time-util.c b/src/basic/time-util.c index fedff1362c..a0db97c41a 100644 --- a/src/basic/time-util.c +++ b/src/basic/time-util.c @@ -185,7 +185,7 @@ usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock) { usec_t timespec_load(const struct timespec *ts) { assert(ts); - if (ts->tv_sec == (time_t) -1 && ts->tv_nsec == (long) -1) + if (ts->tv_sec < 0 || ts->tv_nsec < 0) return USEC_INFINITY; if ((usec_t) ts->tv_sec > (UINT64_MAX - (ts->tv_nsec / NSEC_PER_USEC)) / USEC_PER_SEC) @@ -199,7 +199,7 @@ usec_t timespec_load(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) + if (ts->tv_sec < 0 || ts->tv_nsec < 0) return NSEC_INFINITY; if ((nsec_t) ts->tv_sec >= (UINT64_MAX - ts->tv_nsec) / NSEC_PER_SEC) @@ -211,7 +211,8 @@ nsec_t timespec_load_nsec(const struct timespec *ts) { struct timespec *timespec_store(struct timespec *ts, usec_t u) { assert(ts); - if (u == USEC_INFINITY) { + if (u == USEC_INFINITY || + u / USEC_PER_SEC >= TIME_T_MAX) { ts->tv_sec = (time_t) -1; ts->tv_nsec = (long) -1; return ts; @@ -226,8 +227,7 @@ struct timespec *timespec_store(struct timespec *ts, usec_t u) { usec_t timeval_load(const struct timeval *tv) { assert(tv); - if (tv->tv_sec == (time_t) -1 && - tv->tv_usec == (suseconds_t) -1) + if (tv->tv_sec < 0 || tv->tv_usec < 0) return USEC_INFINITY; if ((usec_t) tv->tv_sec > (UINT64_MAX - tv->tv_usec) / USEC_PER_SEC) @@ -241,7 +241,8 @@ usec_t timeval_load(const struct timeval *tv) { struct timeval *timeval_store(struct timeval *tv, usec_t u) { assert(tv); - if (u == USEC_INFINITY) { + if (u == USEC_INFINITY|| + u / USEC_PER_SEC > TIME_T_MAX) { tv->tv_sec = (time_t) -1; tv->tv_usec = (suseconds_t) -1; } else { @@ -288,9 +289,11 @@ static char *format_timestamp_internal( if (t <= 0 || t == USEC_INFINITY) return NULL; /* Timestamp is unset */ + /* Let's not format times with years > 9999 */ + if (t > USEC_TIMESTAMP_FORMATTABLE_MAX) + return NULL; + 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; @@ -309,7 +312,7 @@ static char *format_timestamp_internal( if (n + 8 > l) return NULL; /* Microseconds part doesn't fit. */ - sprintf(buf + n, ".%06llu", (unsigned long long) (t % USEC_PER_SEC)); + sprintf(buf + n, ".%06"PRI_USEC, t % USEC_PER_SEC); } /* Append the timezone */ @@ -499,11 +502,11 @@ char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) { if (j > 0) { k = snprintf(p, l, - "%s"USEC_FMT".%0*llu%s", + "%s"USEC_FMT".%0*"PRI_USEC"%s", p > buf ? " " : "", a, j, - (unsigned long long) b, + b, table[i].suffix); t = 0; @@ -551,12 +554,12 @@ void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t) { } int dual_timestamp_deserialize(const char *value, dual_timestamp *t) { - unsigned long long a, b; + uint64_t a, b; assert(value); assert(t); - if (sscanf(value, "%llu %llu", &a, &b) != 2) { + if (sscanf(value, "%" PRIu64 "%" PRIu64, &a, &b) != 2) { log_debug("Failed to parse dual timestamp value \"%s\": %m", value); return -EINVAL; } @@ -830,16 +833,23 @@ parse_usec: from_tm: x = mktime_or_timegm(&tm, utc); - if (x == (time_t) -1) + if (x < 0) return -EINVAL; if (weekday >= 0 && tm.tm_wday != weekday) return -EINVAL; ret = (usec_t) x * USEC_PER_SEC + x_usec; + if (ret > USEC_TIMESTAMP_FORMATTABLE_MAX) + return -EINVAL; finish: + if (ret + plus < ret) /* overflow? */ + return -EINVAL; ret += plus; + if (ret > USEC_TIMESTAMP_FORMATTABLE_MAX) + return -EINVAL; + if (ret > minus) ret -= minus; else @@ -883,6 +893,7 @@ static char* extract_multiplier(char *p, usec_t *multiplier) { { "y", USEC_PER_YEAR }, { "usec", 1ULL }, { "us", 1ULL }, + { "µs", 1ULL }, }; unsigned i; @@ -1016,6 +1027,7 @@ int parse_nsec(const char *t, nsec_t *nsec) { { "y", NSEC_PER_YEAR }, { "usec", NSEC_PER_USEC }, { "us", NSEC_PER_USEC }, + { "µs", NSEC_PER_USEC }, { "nsec", 1ULL }, { "ns", 1ULL }, { "", 1ULL }, /* default is nsec */ @@ -1269,7 +1281,7 @@ bool clock_supported(clockid_t clock) { if (!clock_boottime_supported()) return false; - /* fall through, after checking the cached value for CLOCK_BOOTTIME. */ + /* fall through */ default: /* For everything else, check properly */ @@ -1320,7 +1332,7 @@ unsigned long usec_to_jiffies(usec_t u) { r = sysconf(_SC_CLK_TCK); assert(r > 0); - hz = (unsigned long) r; + hz = r; } return DIV_ROUND_UP(u , USEC_PER_SEC / hz); diff --git a/src/basic/time-util.h b/src/basic/time-util.h index 558b0b5b7f..7463507f51 100644 --- a/src/basic/time-util.h +++ b/src/basic/time-util.h @@ -29,8 +29,10 @@ typedef uint64_t usec_t; typedef uint64_t nsec_t; -#define NSEC_FMT "%" PRIu64 -#define USEC_FMT "%" PRIu64 +#define PRI_NSEC PRIu64 +#define PRI_USEC PRIu64 +#define NSEC_FMT "%" PRI_NSEC +#define USEC_FMT "%" PRI_USEC #include "macro.h" @@ -179,3 +181,14 @@ static inline usec_t usec_sub(usec_t timestamp, int64_t delta) { return timestamp - delta; } + +#if SIZEOF_TIME_T == 8 +/* The last second we can format is 31. Dec 9999, 1s before midnight, because otherwise we'd enter 5 digit year + * territory. However, since we want to stay away from this in all timezones we take one day off. */ +#define USEC_TIMESTAMP_FORMATTABLE_MAX ((usec_t) 253402214399000000) +#elif SIZEOF_TIME_T == 4 +/* With a 32bit time_t we can't go beyond 2038... */ +#define USEC_TIMESTAMP_FORMATTABLE_MAX ((usec_t) 2147483647000000) +#else +#error "Yuck, time_t is neither 4 not 8 bytes wide?" +#endif diff --git a/src/basic/unit-name.c b/src/basic/unit-name.c index fe883b95c7..0a6efa449a 100644 --- a/src/basic/unit-name.c +++ b/src/basic/unit-name.c @@ -273,7 +273,7 @@ int unit_name_build(const char *prefix, const char *instance, const char *suffix if (!instance) s = strappend(prefix, suffix); else - s = strjoin(prefix, "@", instance, suffix, NULL); + s = strjoin(prefix, "@", instance, suffix); if (!s) return -ENOMEM; @@ -554,7 +554,7 @@ int unit_name_from_path_instance(const char *prefix, const char *path, const cha if (r < 0) return r; - s = strjoin(prefix, "@", p, suffix, NULL); + s = strjoin(prefix, "@", p, suffix); if (!s) return -ENOMEM; diff --git a/src/basic/user-util.c b/src/basic/user-util.c index de6c93056e..c619dad527 100644 --- a/src/basic/user-util.c +++ b/src/basic/user-util.c @@ -34,7 +34,7 @@ #include "alloc-util.h" #include "fd-util.h" #include "fileio.h" -#include "formats-util.h" +#include "format-util.h" #include "macro.h" #include "missing.h" #include "parse-util.h" @@ -46,6 +46,8 @@ bool uid_is_valid(uid_t uid) { + /* Also see POSIX IEEE Std 1003.1-2008, 2016 Edition, 3.436. */ + /* Some libc APIs use UID_INVALID as special placeholder */ if (uid == (uid_t) UINT32_C(0xFFFFFFFF)) return false; @@ -519,7 +521,15 @@ bool valid_user_group_name(const char *u) { const char *i; long sz; - /* Checks if the specified name is a valid user/group name. */ + /* Checks if the specified name is a valid user/group name. Also see POSIX IEEE Std 1003.1-2008, 2016 Edition, + * 3.437. We are a bit stricter here however. Specifically we deviate from POSIX rules: + * + * - We don't allow any dots (this would break chown syntax which permits dots as user/group name separator) + * - We require that names fit into the appropriate utmp field + * - We don't allow empty user names + * + * Note that other systems are even more restrictive, and don't permit underscores or uppercase characters. + */ if (isempty(u)) return false; diff --git a/src/basic/util.c b/src/basic/util.c index ec7939dc83..3dce0ea92e 100644 --- a/src/basic/util.c +++ b/src/basic/util.c @@ -18,7 +18,6 @@ ***/ #include <alloca.h> -#include <dirent.h> #include <errno.h> #include <fcntl.h> #include <sched.h> @@ -41,7 +40,7 @@ #include "dirent-util.h" #include "fd-util.h" #include "fileio.h" -#include "formats-util.h" +#include "format-util.h" #include "hashmap.h" #include "hostname-util.h" #include "log.h" @@ -60,9 +59,6 @@ #include "user-util.h" #include "util.h" -/* Put this test here for a lack of better place */ -assert_cc(EAGAIN == EWOULDBLOCK); - int saved_argc = 0; char **saved_argv = NULL; static int saved_in_initrd = -1; @@ -81,146 +77,6 @@ size_t page_size(void) { return pgsz; } -static int do_execute(char **directories, usec_t timeout, char *argv[]) { - _cleanup_hashmap_free_free_ Hashmap *pids = NULL; - _cleanup_set_free_free_ Set *seen = NULL; - char **directory; - - /* We fork this all off from a child process so that we can - * somewhat cleanly make use of SIGALRM to set a time limit */ - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); - - pids = hashmap_new(NULL); - if (!pids) - return log_oom(); - - seen = set_new(&string_hash_ops); - if (!seen) - return log_oom(); - - STRV_FOREACH(directory, directories) { - _cleanup_closedir_ DIR *d; - struct dirent *de; - - d = opendir(*directory); - if (!d) { - if (errno == ENOENT) - continue; - - return log_error_errno(errno, "Failed to open directory %s: %m", *directory); - } - - FOREACH_DIRENT(de, d, break) { - _cleanup_free_ char *path = NULL; - pid_t pid; - int r; - - if (!dirent_is_file(de)) - continue; - - if (set_contains(seen, de->d_name)) { - log_debug("%1$s/%2$s skipped (%2$s was already seen).", *directory, de->d_name); - continue; - } - - r = set_put_strdup(seen, de->d_name); - if (r < 0) - return log_oom(); - - path = strjoin(*directory, "/", de->d_name, NULL); - if (!path) - return log_oom(); - - if (null_or_empty_path(path)) { - log_debug("%s is empty (a mask).", path); - continue; - } - - pid = fork(); - if (pid < 0) { - log_error_errno(errno, "Failed to fork: %m"); - continue; - } else if (pid == 0) { - char *_argv[2]; - - assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); - - if (!argv) { - _argv[0] = path; - _argv[1] = NULL; - argv = _argv; - } else - argv[0] = path; - - execv(path, argv); - return log_error_errno(errno, "Failed to execute %s: %m", path); - } - - log_debug("Spawned %s as " PID_FMT ".", path, pid); - - r = hashmap_put(pids, PID_TO_PTR(pid), path); - if (r < 0) - return log_oom(); - path = NULL; - } - } - - /* Abort execution of this process after the timout. We simply - * rely on SIGALRM as default action terminating the process, - * and turn on alarm(). */ - - if (timeout != USEC_INFINITY) - alarm((timeout + USEC_PER_SEC - 1) / USEC_PER_SEC); - - while (!hashmap_isempty(pids)) { - _cleanup_free_ char *path = NULL; - pid_t pid; - - pid = PTR_TO_PID(hashmap_first_key(pids)); - assert(pid > 0); - - path = hashmap_remove(pids, PID_TO_PTR(pid)); - assert(path); - - wait_for_terminate_and_warn(path, pid, true); - } - - return 0; -} - -void execute_directories(const char* const* directories, usec_t timeout, char *argv[]) { - pid_t executor_pid; - int r; - char *name; - char **dirs = (char**) directories; - - assert(!strv_isempty(dirs)); - - name = basename(dirs[0]); - assert(!isempty(name)); - - /* Executes all binaries in the directories in parallel and waits - * for them to finish. Optionally a timeout is applied. If a file - * with the same name exists in more than one directory, the - * earliest one wins. */ - - executor_pid = fork(); - if (executor_pid < 0) { - log_error_errno(errno, "Failed to fork: %m"); - return; - - } else if (executor_pid == 0) { - r = do_execute(dirs, timeout, argv); - _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); - } - - wait_for_terminate_and_warn(name, executor_pid, true); -} - bool plymouth_running(void) { return access("/run/plymouth/pid", F_OK) >= 0; } @@ -493,7 +349,7 @@ void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size, u = nmemb; while (l < u) { idx = (l + u) / 2; - p = (void *)(((const char *) base) + (idx * size)); + p = (const char *) base + idx * size; comparison = compar(key, p, arg); if (comparison < 0) u = idx; @@ -508,28 +364,17 @@ void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size, int on_ac_power(void) { bool found_offline = false, found_online = false; _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; d = opendir("/sys/class/power_supply"); if (!d) return errno == ENOENT ? true : -errno; - for (;;) { - struct dirent *de; + FOREACH_DIRENT(de, d, return -errno) { _cleanup_close_ int fd = -1, device = -1; char contents[6]; ssize_t n; - errno = 0; - de = readdir(d); - if (!de && errno > 0) - return -errno; - - if (!de) - break; - - if (hidden_or_backup_file(de->d_name)) - continue; - device = openat(dirfd(d), de->d_name, O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_NOCTTY); if (device < 0) { if (errno == ENOENT || errno == ENOTDIR) diff --git a/src/basic/util.h b/src/basic/util.h index bb2fc318ef..c7da6c39bf 100644 --- a/src/basic/util.h +++ b/src/basic/util.h @@ -41,7 +41,7 @@ #include <time.h> #include <unistd.h> -#include "formats-util.h" +#include "format-util.h" #include "macro.h" #include "missing.h" #include "time-util.h" @@ -65,8 +65,6 @@ 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); bool display_is_local(const char *display) _pure_; diff --git a/src/basic/virt.c b/src/basic/virt.c index 69b0f96183..ff4491d6d6 100644 --- a/src/basic/virt.c +++ b/src/basic/virt.c @@ -25,6 +25,7 @@ #include "alloc-util.h" #include "dirent-util.h" +#include "env-util.h" #include "fd-util.h" #include "fileio.h" #include "macro.h" @@ -33,7 +34,6 @@ #include "string-table.h" #include "string-util.h" #include "virt.h" -#include "env-util.h" static int detect_vm_cpuid(void) { @@ -48,7 +48,7 @@ static int detect_vm_cpuid(void) { { "KVMKVMKVM", VIRTUALIZATION_KVM }, /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */ { "VMwareVMware", VIRTUALIZATION_VMWARE }, - /* http://msdn.microsoft.com/en-us/library/ff542428.aspx */ + /* https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/reference/tlfs */ { "Microsoft Hv", VIRTUALIZATION_MICROSOFT }, /* https://wiki.freebsd.org/bhyve */ { "bhyve bhyve ", VIRTUALIZATION_BHYVE }, @@ -315,25 +315,31 @@ static int detect_vm_zvm(void) { /* Returns a short identifier for the various VM implementations */ int detect_vm(void) { static thread_local int cached_found = _VIRTUALIZATION_INVALID; - int r; + int r, dmi; if (cached_found >= 0) return cached_found; /* We have to use the correct order here: - * Some virtualization technologies do use KVM hypervisor but are - * expected to be detected as something else. So detect DMI first. * - * An example is Virtualbox since version 5.0, which uses KVM backend. - * Detection via DMI works corretly, the CPU ID would find KVM - * only. */ - r = detect_vm_dmi(); + * -> First try to detect Oracle Virtualbox, even if it uses KVM. + * -> Second try to detect from cpuid, this will report KVM for + * whatever software is used even if info in dmi is overwritten. + * -> Third try to detect from dmi. */ + + dmi = detect_vm_dmi(); + if (dmi == VIRTUALIZATION_ORACLE) { + r = dmi; + goto finish; + } + + r = detect_vm_cpuid(); if (r < 0) return r; if (r != VIRTUALIZATION_NONE) goto finish; - r = detect_vm_cpuid(); + r = dmi; if (r < 0) return r; if (r != VIRTUALIZATION_NONE) @@ -408,8 +414,7 @@ int detect_container(void) { if (cached_found >= 0) return cached_found; - /* /proc/vz exists in container and outside of the container, - * /proc/bc only outside of the container. */ + /* /proc/vz exists in container and outside of the container, /proc/bc only outside of the container. */ if (access("/proc/vz", F_OK) >= 0 && access("/proc/bc", F_OK) < 0) { r = VIRTUALIZATION_OPENVZ; @@ -417,50 +422,58 @@ int detect_container(void) { } if (getpid() == 1) { - /* If we are PID 1 we can just check our own - * environment variable */ + /* If we are PID 1 we can just check our own environment variable, and that's authoritative. */ e = getenv("container"); if (isempty(e)) { r = VIRTUALIZATION_NONE; goto finish; } - } else { - - /* Otherwise, PID 1 dropped this information into a - * file in /run. This is better than accessing - * /proc/1/environ, since we don't need CAP_SYS_PTRACE - * for that. */ - - r = read_one_line_file("/run/systemd/container", &m); - if (r == -ENOENT) { - - /* Fallback for cases where PID 1 was not - * systemd (for example, cases where - * init=/bin/sh is used. */ - - r = getenv_for_pid(1, "container", &m); - if (r <= 0) { - - /* If that didn't work, give up, - * assume no container manager. - * - * Note: This means we still cannot - * detect containers if init=/bin/sh - * is passed but privileges dropped, - * as /proc/1/environ is only readable - * with privileges. */ - - r = VIRTUALIZATION_NONE; - goto finish; - } - } - if (r < 0) - return r; + goto translate_name; + } + + /* Otherwise, PID 1 might have dropped this information into a file in /run. This is better than accessing + * /proc/1/environ, since we don't need CAP_SYS_PTRACE for that. */ + r = read_one_line_file("/run/systemd/container", &m); + if (r >= 0) { + e = m; + goto translate_name; + } + if (r != -ENOENT) + return log_debug_errno(r, "Failed to read /run/systemd/container: %m"); + + /* Fallback for cases where PID 1 was not systemd (for example, cases where init=/bin/sh is used. */ + r = getenv_for_pid(1, "container", &m); + if (r > 0) { e = m; + goto translate_name; } + if (r < 0) /* This only works if we have CAP_SYS_PTRACE, hence let's better ignore failures here */ + log_debug_errno(r, "Failed to read $container of PID 1, ignoring: %m"); + + /* Interestingly /proc/1/sched actually shows the host's PID for what we see as PID 1. Hence, if the PID shown + * there is not 1, we know we are in a PID namespace. and hence a container. */ + r = read_one_line_file("/proc/1/sched", &m); + if (r >= 0) { + const char *t; + + t = strrchr(m, '('); + if (!t) + return -EIO; + + if (!startswith(t, "(1,")) { + r = VIRTUALIZATION_CONTAINER_OTHER; + goto finish; + } + } else if (r != -ENOENT) + return r; + + /* If that didn't work, give up, assume no container manager. */ + r = VIRTUALIZATION_NONE; + goto finish; +translate_name: for (j = 0; j < ELEMENTSOF(value_table); j++) if (streq(e, value_table[j].value)) { r = value_table[j].id; @@ -470,7 +483,7 @@ int detect_container(void) { r = VIRTUALIZATION_CONTAINER_OTHER; finish: - log_debug("Found container virtualization %s", virtualization_to_string(r)); + log_debug("Found container virtualization %s.", virtualization_to_string(r)); cached_found = r; return r; } @@ -496,7 +509,7 @@ static int userns_has_mapping(const char *name) { f = fopen(name, "re"); if (!f) { log_debug_errno(errno, "Failed to open %s: %m", name); - return errno == -ENOENT ? false : -errno; + return errno == ENOENT ? false : -errno; } n = getline(&buf, &n_allocated, f); |