X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fshared%2Futil.c;h=5efb9591a60f671ff2b2e1b67948ae05a464202d;hp=8c1cf52c0a695c653b74b8a9cfd869a84e8d9178;hb=f7cf3431c7260635d9d2fa0886af05e56261c5df;hpb=32802361561403cb6441198c82d9c499e0513863 diff --git a/src/shared/util.c b/src/shared/util.c index 8c1cf52c0..5efb9591a 100644 --- a/src/shared/util.c +++ b/src/shared/util.c @@ -19,12 +19,12 @@ along with systemd; If not, see . ***/ -#include #include #include #include #include #include +#include #include #include #include @@ -35,19 +35,13 @@ #include #include #include -#include -#include -#include #include -#include -#include +#include #include #include #include #include #include -#include -#include #include #include #include @@ -60,6 +54,14 @@ #include #include #include +#include +#include +#include +#include + +/* When we include libgen.h because we need dirname() we immediately + * undefine basename() since libgen.h defines it as a macro to the XDG + * version which is really broken. */ #include #undef basename @@ -67,13 +69,13 @@ #include #endif +#include "config.h" #include "macro.h" #include "util.h" #include "ioprio.h" #include "missing.h" #include "log.h" #include "strv.h" -#include "label.h" #include "mkdir.h" #include "path-util.h" #include "exit-status.h" @@ -85,13 +87,19 @@ #include "gunicode.h" #include "virt.h" #include "def.h" +#include "sparse-endian.h" +#include "formats-util.h" +#include "process-util.h" +#include "random-util.h" +#include "terminal-util.h" +#include "hostname-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 volatile unsigned cached_columns = 0; -static volatile unsigned cached_lines = 0; - size_t page_size(void) { static thread_local size_t pgsz = 0; long r; @@ -174,7 +182,7 @@ char* first_word(const char *s, const char *word) { return (char*) p; } -static size_t cescape_char(char c, char *buf) { +size_t cescape_char(char c, char *buf) { char * buf_old = buf; switch (c) { @@ -343,7 +351,6 @@ int parse_uid(const char *s, uid_t* ret_uid) { int r; assert(s); - assert(ret_uid); r = safe_atolu(s, &ul); if (r < 0) @@ -362,7 +369,9 @@ int parse_uid(const char *s, uid_t* ret_uid) { if (uid == (uid_t) 0xFFFF) return -ENXIO; - *ret_uid = uid; + if (ret_uid) + *ret_uid = uid; + return 0; } @@ -503,25 +512,31 @@ int safe_atolli(const char *s, long long int *ret_lli) { int safe_atod(const char *s, double *ret_d) { char *x = NULL; double d = 0; + locale_t loc; assert(s); assert(ret_d); - RUN_WITH_LOCALE(LC_NUMERIC_MASK, "C") { - errno = 0; - d = strtod(s, &x); - } + loc = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0); + if (loc == (locale_t) 0) + return -errno; - if (!x || x == s || *x || errno) + errno = 0; + d = strtod_l(s, &x, loc); + + if (!x || x == s || *x || errno) { + freelocale(loc); return errno ? -errno : -EINVAL; + } + freelocale(loc); *ret_d = (double) d; return 0; } static size_t strcspn_escaped(const char *s, const char *reject) { bool escaped = false; - size_t n; + int n; for (n=0; s[n]; n++) { if (escaped) @@ -531,6 +546,7 @@ static size_t strcspn_escaped(const char *s, const char *reject) { else if (strchr(reject, s[n])) break; } + /* if s ends in \, return index of previous char */ return n - escaped; } @@ -558,7 +574,7 @@ const char* split(const char **state, size_t *l, const char *separator, bool quo *l = strcspn_escaped(current + 1, quotechars); if (current[*l + 1] == '\0' || (current[*l + 2] && !strchr(separator, current[*l + 2]))) { - /* right quote missing or garbage at the end*/ + /* right quote missing or garbage at the end */ *state = current; return NULL; } @@ -566,6 +582,11 @@ const char* split(const char **state, size_t *l, const char *separator, bool quo *state = current++ + *l + 2; } else if (quoted) { *l = strcspn_escaped(current, separator); + if (current[*l] && !strchr(separator, current[*l])) { + /* unfinished escape */ + *state = current; + return NULL; + } *state = current + *l; } else { *l = strcspn(current, separator); @@ -575,49 +596,6 @@ const char* split(const char **state, size_t *l, const char *separator, bool quo return current; } -int get_parent_of_pid(pid_t pid, pid_t *_ppid) { - int r; - _cleanup_free_ char *line = NULL; - long unsigned ppid; - const char *p; - - assert(pid >= 0); - assert(_ppid); - - if (pid == 0) { - *_ppid = getppid(); - return 0; - } - - p = procfs_file_alloca(pid, "stat"); - r = read_one_line_file(p, &line); - if (r < 0) - return r; - - /* Let's skip the pid and comm fields. The latter is enclosed - * in () but does not escape any () in its value, so let's - * skip over it manually */ - - p = strrchr(line, ')'); - if (!p) - return -EIO; - - p++; - - if (sscanf(p, " " - "%*c " /* state */ - "%lu ", /* ppid */ - &ppid) != 1) - return -EIO; - - if ((long unsigned) (pid_t) ppid != ppid) - return -ERANGE; - - *_ppid = (pid_t) ppid; - - return 0; -} - int fchmod_umask(int fd, mode_t m) { mode_t u; int r; @@ -636,308 +614,6 @@ char *truncate_nl(char *s) { return s; } -int get_process_state(pid_t pid) { - const char *p; - char state; - int r; - _cleanup_free_ char *line = NULL; - - assert(pid >= 0); - - p = procfs_file_alloca(pid, "stat"); - r = read_one_line_file(p, &line); - if (r < 0) - return r; - - p = strrchr(line, ')'); - if (!p) - return -EIO; - - p++; - - if (sscanf(p, " %c", &state) != 1) - return -EIO; - - return (unsigned char) state; -} - -int get_process_comm(pid_t pid, char **name) { - const char *p; - int r; - - assert(name); - assert(pid >= 0); - - p = procfs_file_alloca(pid, "comm"); - - r = read_one_line_file(p, name); - if (r == -ENOENT) - return -ESRCH; - - return r; -} - -int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line) { - _cleanup_fclose_ FILE *f = NULL; - char *r = NULL, *k; - const char *p; - int c; - - assert(line); - assert(pid >= 0); - - p = procfs_file_alloca(pid, "cmdline"); - - f = fopen(p, "re"); - if (!f) - return -errno; - - if (max_length == 0) { - size_t len = 0, allocated = 0; - - while ((c = getc(f)) != EOF) { - - if (!GREEDY_REALLOC(r, allocated, len+2)) { - free(r); - return -ENOMEM; - } - - r[len++] = isprint(c) ? c : ' '; - } - - if (len > 0) - r[len-1] = 0; - - } else { - bool space = false; - size_t left; - - r = new(char, max_length); - if (!r) - return -ENOMEM; - - k = r; - left = max_length; - while ((c = getc(f)) != EOF) { - - if (isprint(c)) { - if (space) { - if (left <= 4) - break; - - *(k++) = ' '; - left--; - space = false; - } - - if (left <= 4) - break; - - *(k++) = (char) c; - left--; - } else - space = true; - } - - if (left <= 4) { - size_t n = MIN(left-1, 3U); - memcpy(k, "...", n); - k[n] = 0; - } else - *k = 0; - } - - /* Kernel threads have no argv[] */ - if (isempty(r)) { - _cleanup_free_ char *t = NULL; - int h; - - free(r); - - if (!comm_fallback) - return -ENOENT; - - h = get_process_comm(pid, &t); - if (h < 0) - return h; - - r = strjoin("[", t, "]", NULL); - if (!r) - return -ENOMEM; - } - - *line = r; - return 0; -} - -int is_kernel_thread(pid_t pid) { - const char *p; - size_t count; - char c; - bool eof; - FILE *f; - - if (pid == 0) - return 0; - - assert(pid > 0); - - p = procfs_file_alloca(pid, "cmdline"); - f = fopen(p, "re"); - if (!f) - return -errno; - - count = fread(&c, 1, 1, f); - eof = feof(f); - fclose(f); - - /* Kernel threads have an empty cmdline */ - - if (count <= 0) - return eof ? 1 : -errno; - - return 0; -} - -int get_process_capeff(pid_t pid, char **capeff) { - const char *p; - - assert(capeff); - assert(pid >= 0); - - p = procfs_file_alloca(pid, "status"); - - return get_status_field(p, "\nCapEff:", capeff); -} - -static int get_process_link_contents(const char *proc_file, char **name) { - int r; - - assert(proc_file); - assert(name); - - r = readlink_malloc(proc_file, name); - if (r < 0) - return r == -ENOENT ? -ESRCH : r; - - return 0; -} - -int get_process_exe(pid_t pid, char **name) { - const char *p; - char *d; - int r; - - assert(pid >= 0); - - p = procfs_file_alloca(pid, "exe"); - r = get_process_link_contents(p, name); - if (r < 0) - return r; - - d = endswith(*name, " (deleted)"); - if (d) - *d = '\0'; - - return 0; -} - -static int get_process_id(pid_t pid, const char *field, uid_t *uid) { - _cleanup_fclose_ FILE *f = NULL; - char line[LINE_MAX]; - const char *p; - - assert(field); - assert(uid); - - if (pid == 0) - return getuid(); - - p = procfs_file_alloca(pid, "status"); - f = fopen(p, "re"); - if (!f) - return -errno; - - FOREACH_LINE(line, f, return -errno) { - char *l; - - l = strstrip(line); - - if (startswith(l, field)) { - l += strlen(field); - l += strspn(l, WHITESPACE); - - l[strcspn(l, WHITESPACE)] = 0; - - return parse_uid(l, uid); - } - } - - return -EIO; -} - -int get_process_uid(pid_t pid, uid_t *uid) { - return get_process_id(pid, "Uid:", uid); -} - -int get_process_gid(pid_t pid, gid_t *gid) { - assert_cc(sizeof(uid_t) == sizeof(gid_t)); - return get_process_id(pid, "Gid:", gid); -} - -int get_process_cwd(pid_t pid, char **cwd) { - const char *p; - - assert(pid >= 0); - - p = procfs_file_alloca(pid, "cwd"); - - return get_process_link_contents(p, cwd); -} - -int get_process_root(pid_t pid, char **root) { - const char *p; - - assert(pid >= 0); - - p = procfs_file_alloca(pid, "root"); - - return get_process_link_contents(p, root); -} - -int get_process_environ(pid_t pid, char **env) { - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char *outcome = NULL; - int c; - const char *p; - size_t allocated = 0, sz = 0; - - assert(pid >= 0); - assert(env); - - p = procfs_file_alloca(pid, "environ"); - - f = fopen(p, "re"); - if (!f) - return -errno; - - while ((c = fgetc(f)) != EOF) { - if (!GREEDY_REALLOC(outcome, allocated, sz + 5)) - return -ENOMEM; - - if (c == '\0') - outcome[sz++] = '\n'; - else - sz += cescape_char(c, outcome + sz); - } - - outcome[sz] = '\0'; - *env = outcome; - outcome = NULL; - - return 0; -} - char *strnappend(const char *s, const char *suffix, size_t b) { size_t a; char *r; @@ -1145,7 +821,7 @@ char *delete_chars(char *s, const char *bad) { } char *file_in_same_dir(const char *path, const char *filename) { - char *e, *r; + char *e, *ret; size_t k; assert(path); @@ -1158,17 +834,17 @@ char *file_in_same_dir(const char *path, const char *filename) { if (path_is_absolute(filename)) return strdup(filename); - if (!(e = strrchr(path, '/'))) + e = strrchr(path, '/'); + if (!e) return strdup(filename); k = strlen(filename); - if (!(r = new(char, e-path+1+k+1))) + ret = new(char, (e + 1 - path) + k + 1); + if (!ret) return NULL; - memcpy(r, path, e-path+1); - memcpy(r+(e-path)+1, filename, k+1); - - return r; + memcpy(mempcpy(ret, path, e + 1 - path), filename, k + 1); + return ret; } int rmdir_parents(const char *path, const char *stop) { @@ -1310,7 +986,8 @@ char *cescape(const char *s) { assert(s); - /* Does C style string escaping. */ + /* Does C style string escaping. May be reversed with + * cunescape(). */ r = new(char, strlen(s)*4 + 1); if (!r) @@ -1324,143 +1001,286 @@ char *cescape(const char *s) { return r; } -char *cunescape_length_with_prefix(const char *s, size_t length, const char *prefix) { - char *r, *t; - const char *f; - size_t pl; +static int cunescape_one(const char *p, size_t length, char *ret, uint32_t *ret_unicode) { + int r = 1; - assert(s); + assert(p); + assert(*p); + assert(ret); - /* Undoes C style string escaping, and optionally prefixes it. */ + /* Unescapes C style. Returns the unescaped character in ret, + * unless we encountered a \u sequence in which case the full + * unicode character is returned in ret_unicode, instead. */ - pl = prefix ? strlen(prefix) : 0; + if (length != (size_t) -1 && length < 1) + return -EINVAL; - r = new(char, pl+length+1); - if (!r) - return NULL; + switch (p[0]) { + + case 'a': + *ret = '\a'; + break; + case 'b': + *ret = '\b'; + break; + case 'f': + *ret = '\f'; + break; + case 'n': + *ret = '\n'; + break; + case 'r': + *ret = '\r'; + break; + case 't': + *ret = '\t'; + break; + case 'v': + *ret = '\v'; + break; + case '\\': + *ret = '\\'; + break; + case '"': + *ret = '"'; + break; + case '\'': + *ret = '\''; + break; + + case 's': + /* This is an extension of the XDG syntax files */ + *ret = ' '; + break; + + case 'x': { + /* hexadecimal encoding */ + int a, b; - if (prefix) - memcpy(r, prefix, pl); + if (length != (size_t) -1 && length < 3) + return -EINVAL; - for (f = s, t = r + pl; f < s + length; f++) { + a = unhexchar(p[1]); + if (a < 0) + return -EINVAL; - if (*f != '\\') { - *(t++) = *f; - continue; - } + b = unhexchar(p[2]); + if (b < 0) + return -EINVAL; - f++; + /* Don't allow NUL bytes */ + if (a == 0 && b == 0) + return -EINVAL; - switch (*f) { + *ret = (char) ((a << 4U) | b); + r = 3; + break; + } - case 'a': - *(t++) = '\a'; - break; - case 'b': - *(t++) = '\b'; - break; - case 'f': - *(t++) = '\f'; - break; - case 'n': - *(t++) = '\n'; - break; - case 'r': - *(t++) = '\r'; - break; - case 't': - *(t++) = '\t'; - break; - case 'v': - *(t++) = '\v'; - break; - case '\\': - *(t++) = '\\'; - break; - case '"': - *(t++) = '"'; - break; - case '\'': - *(t++) = '\''; - break; + case 'u': { + /* C++11 style 16bit unicode */ - case 's': - /* This is an extension of the XDG syntax files */ - *(t++) = ' '; - break; + int a[4]; + unsigned i; + uint32_t c; - case 'x': { - /* hexadecimal encoding */ - int a, b; + if (length != (size_t) -1 && length < 5) + return -EINVAL; - a = unhexchar(f[1]); - b = unhexchar(f[2]); + for (i = 0; i < 4; i++) { + a[i] = unhexchar(p[1 + i]); + if (a[i] < 0) + return a[i]; + } - if (a < 0 || b < 0 || (a == 0 && b == 0)) { - /* Invalid escape code, let's take it literal then */ - *(t++) = '\\'; - *(t++) = 'x'; - } else { - *(t++) = (char) ((a << 4) | b); - f += 2; + c = ((uint32_t) a[0] << 12U) | ((uint32_t) a[1] << 8U) | ((uint32_t) a[2] << 4U) | (uint32_t) a[3]; + + /* Don't allow 0 chars */ + if (c == 0) + return -EINVAL; + + if (c < 128) + *ret = c; + else { + if (!ret_unicode) + return -EINVAL; + + *ret = 0; + *ret_unicode = c; + } + + r = 5; + break; + } + + case 'U': { + /* C++11 style 32bit unicode */ + + int a[8]; + unsigned i; + uint32_t c; + + if (length != (size_t) -1 && length < 9) + return -EINVAL; + + for (i = 0; i < 8; i++) { + a[i] = unhexchar(p[1 + i]); + if (a[i] < 0) + return a[i]; + } + + c = ((uint32_t) a[0] << 28U) | ((uint32_t) a[1] << 24U) | ((uint32_t) a[2] << 20U) | ((uint32_t) a[3] << 16U) | + ((uint32_t) a[4] << 12U) | ((uint32_t) a[5] << 8U) | ((uint32_t) a[6] << 4U) | (uint32_t) a[7]; + + /* Don't allow 0 chars */ + if (c == 0) + return -EINVAL; + + /* Don't allow invalid code points */ + if (!unichar_is_valid(c)) + return -EINVAL; + + if (c < 128) + *ret = c; + else { + if (!ret_unicode) + return -EINVAL; + + *ret = 0; + *ret_unicode = c; + } + + r = 9; + break; + } + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': { + /* octal encoding */ + int a, b, c; + uint32_t m; + + if (length != (size_t) -1 && length < 4) + return -EINVAL; + + a = unoctchar(p[0]); + if (a < 0) + return -EINVAL; + + b = unoctchar(p[1]); + if (b < 0) + return -EINVAL; + + c = unoctchar(p[2]); + if (c < 0) + return -EINVAL; + + /* don't allow NUL bytes */ + if (a == 0 && b == 0 && c == 0) + return -EINVAL; + + /* Don't allow bytes above 255 */ + m = ((uint32_t) a << 6U) | ((uint32_t) b << 3U) | (uint32_t) c; + if (m > 255) + return -EINVAL; + + *ret = m; + r = 3; + break; + } + + default: + return -EINVAL; + } + + return r; +} + +int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret) { + char *r, *t; + const char *f; + size_t pl; + + assert(s); + assert(ret); + + /* Undoes C style string escaping, and optionally prefixes it. */ + + pl = prefix ? strlen(prefix) : 0; + + r = new(char, pl+length+1); + if (!r) + return -ENOMEM; + + if (prefix) + memcpy(r, prefix, pl); + + for (f = s, t = r + pl; f < s + length; f++) { + size_t remaining; + uint32_t u; + char c; + int k; + + remaining = s + length - f; + assert(remaining > 0); + + if (*f != '\\') { + /* A literal literal, copy verbatim */ + *(t++) = *f; + continue; + } + + if (remaining == 1) { + if (flags & UNESCAPE_RELAX) { + /* A trailing backslash, copy verbatim */ + *(t++) = *f; + continue; } - break; + free(r); + return -EINVAL; } - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': { - /* octal encoding */ - int a, b, c; - - a = unoctchar(f[0]); - b = unoctchar(f[1]); - c = unoctchar(f[2]); - - if (a < 0 || b < 0 || c < 0 || (a == 0 && b == 0 && c == 0)) { + k = cunescape_one(f + 1, remaining - 1, &c, &u); + if (k < 0) { + if (flags & UNESCAPE_RELAX) { /* Invalid escape code, let's take it literal then */ *(t++) = '\\'; - *(t++) = f[0]; - } else { - *(t++) = (char) ((a << 6) | (b << 3) | c); - f += 2; + continue; } - break; + free(r); + return k; } - case 0: - /* premature end of string.*/ - *(t++) = '\\'; - goto finish; + if (c != 0) + /* Non-Unicode? Let's encode this directly */ + *(t++) = c; + else + /* Unicode? Then let's encode this in UTF-8 */ + t += utf8_encode_unichar(t, u); - default: - /* Invalid escape code, let's take it literal then */ - *(t++) = '\\'; - *(t++) = *f; - break; - } + f += k; } -finish: *t = 0; - return r; -} -char *cunescape_length(const char *s, size_t length) { - return cunescape_length_with_prefix(s, length, NULL); + *ret = r; + return t - r; } -char *cunescape(const char *s) { - assert(s); +int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret) { + return cunescape_length_with_prefix(s, length, NULL, flags, ret); +} - return cunescape_length(s, strlen(s)); +int cunescape(const char *s, UnescapeFlags flags, char **ret) { + return cunescape_length(s, strlen(s), flags, ret); } char *xescape(const char *s, const char *bad) { @@ -1469,7 +1289,7 @@ char *xescape(const char *s, const char *bad) { /* Escapes all chars in bad, in addition to \ and all special * chars, in \xFF style escaping. May be reversed with - * cunescape. */ + * cunescape(). */ r = new(char, strlen(s) * 4 + 1); if (!r) @@ -1504,7 +1324,7 @@ char *ascii_strlower(char *t) { return t; } -_pure_ static bool ignore_file_allow_backup(const char *filename) { +_pure_ static bool hidden_file_allow_backup(const char *filename) { assert(filename); return @@ -1518,16 +1338,20 @@ _pure_ static bool ignore_file_allow_backup(const char *filename) { endswith(filename, ".dpkg-old") || endswith(filename, ".dpkg-new") || endswith(filename, ".dpkg-tmp") || + endswith(filename, ".dpkg-dist") || + endswith(filename, ".dpkg-bak") || + endswith(filename, ".dpkg-backup") || + endswith(filename, ".dpkg-remove") || endswith(filename, ".swp"); } -bool ignore_file(const char *filename) { +bool hidden_file(const char *filename) { assert(filename); if (endswith(filename, "~")) return true; - return ignore_file_allow_backup(filename); + return hidden_file_allow_backup(filename); } int fd_nonblock(int fd, bool nonblock) { @@ -1621,7 +1445,7 @@ int close_all_fds(const int except[], unsigned n_except) { while ((de = readdir(d))) { int fd = -1; - if (ignore_file(de->d_name)) + if (hidden_file(de->d_name)) continue; if (safe_atoi(de->d_name, &fd) < 0) @@ -1660,6 +1484,7 @@ bool chars_intersect(const char *a, const char *b) { bool fstype_is_network(const char *fstype) { static const char table[] = + "afs\0" "cifs\0" "smbfs\0" "sshfs\0" @@ -1680,5678 +1505,4660 @@ bool fstype_is_network(const char *fstype) { return nulstr_contains(table, fstype); } -int chvt(int vt) { - _cleanup_close_ int fd; +int flush_fd(int fd) { + struct pollfd pollfd = { + .fd = fd, + .events = POLLIN, + }; - fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return -errno; + for (;;) { + char buf[LINE_MAX]; + ssize_t l; + int r; - if (vt < 0) { - int tiocl[2] = { - TIOCL_GETKMSGREDIRECT, - 0 - }; + r = poll(&pollfd, 1, 0); + if (r < 0) { + if (errno == EINTR) + continue; - if (ioctl(fd, TIOCLINUX, tiocl) < 0) return -errno; - vt = tiocl[0] <= 0 ? 1 : tiocl[0]; - } - - if (ioctl(fd, VT_ACTIVATE, vt) < 0) - return -errno; + } else if (r == 0) + return 0; - return 0; -} + l = read(fd, buf, sizeof(buf)); + if (l < 0) { -int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) { - struct termios old_termios, new_termios; - char c, line[LINE_MAX]; + if (errno == EINTR) + continue; - assert(f); - assert(ret); + if (errno == EAGAIN) + return 0; - if (tcgetattr(fileno(f), &old_termios) >= 0) { - new_termios = old_termios; + return -errno; + } else if (l == 0) + return 0; + } +} - new_termios.c_lflag &= ~ICANON; - new_termios.c_cc[VMIN] = 1; - new_termios.c_cc[VTIME] = 0; +int sigaction_many(const struct sigaction *sa, ...) { + va_list ap; + int r = 0, sig; - if (tcsetattr(fileno(f), TCSADRAIN, &new_termios) >= 0) { - size_t k; + va_start(ap, sa); + while ((sig = va_arg(ap, int)) > 0) + if (sigaction(sig, sa, NULL) < 0) + r = -errno; + va_end(ap); - if (t != USEC_INFINITY) { - if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0) { - tcsetattr(fileno(f), TCSADRAIN, &old_termios); - return -ETIMEDOUT; - } - } + return r; +} - k = fread(&c, 1, 1, f); +int ignore_signals(int sig, ...) { + struct sigaction sa = { + .sa_handler = SIG_IGN, + .sa_flags = SA_RESTART, + }; + va_list ap; + int r = 0; - tcsetattr(fileno(f), TCSADRAIN, &old_termios); + if (sigaction(sig, &sa, NULL) < 0) + r = -errno; - if (k <= 0) - return -EIO; + va_start(ap, sig); + while ((sig = va_arg(ap, int)) > 0) + if (sigaction(sig, &sa, NULL) < 0) + r = -errno; + va_end(ap); - if (need_nl) - *need_nl = c != '\n'; + return r; +} - *ret = c; - return 0; - } - } +int default_signals(int sig, ...) { + struct sigaction sa = { + .sa_handler = SIG_DFL, + .sa_flags = SA_RESTART, + }; + va_list ap; + int r = 0; - if (t != USEC_INFINITY) { - if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0) - return -ETIMEDOUT; - } + if (sigaction(sig, &sa, NULL) < 0) + r = -errno; - errno = 0; - if (!fgets(line, sizeof(line), f)) - return errno ? -errno : -EIO; + va_start(ap, sig); + while ((sig = va_arg(ap, int)) > 0) + if (sigaction(sig, &sa, NULL) < 0) + r = -errno; + va_end(ap); - truncate_nl(line); + return r; +} - if (strlen(line) != 1) - return -EBADMSG; +void safe_close_pair(int p[]) { + assert(p); - if (need_nl) - *need_nl = false; + if (p[0] == p[1]) { + /* Special case pairs which use the same fd in both + * directions... */ + p[0] = p[1] = safe_close(p[0]); + return; + } - *ret = line[0]; - return 0; + p[0] = safe_close(p[0]); + p[1] = safe_close(p[1]); } -int ask_char(char *ret, const char *replies, const char *text, ...) { - int r; - - assert(ret); - assert(replies); - assert(text); - - for (;;) { - va_list ap; - char c; - bool need_nl = true; +ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) { + uint8_t *p = buf; + ssize_t n = 0; - if (on_tty()) - fputs(ANSI_HIGHLIGHT_ON, stdout); + assert(fd >= 0); + assert(buf); - va_start(ap, text); - vprintf(text, ap); - va_end(ap); + while (nbytes > 0) { + ssize_t k; - if (on_tty()) - fputs(ANSI_HIGHLIGHT_OFF, stdout); + k = read(fd, p, nbytes); + if (k < 0) { + if (errno == EINTR) + continue; - fflush(stdout); + if (errno == EAGAIN && do_poll) { - r = read_one_char(stdin, &c, USEC_INFINITY, &need_nl); - if (r < 0) { + /* We knowingly ignore any return value here, + * and expect that any error/EOF is reported + * via read() */ - if (r == -EBADMSG) { - puts("Bad input, please try again."); + fd_wait_for_event(fd, POLLIN, USEC_INFINITY); continue; } - putchar('\n'); - return r; + return n > 0 ? n : -errno; } - if (need_nl) - putchar('\n'); - - if (strchr(replies, c)) { - *ret = c; - return 0; - } + if (k == 0) + return n; - puts("Read unexpected character, please try again."); + p += k; + nbytes -= k; + n += k; } + + return n; } -int ask_string(char **ret, const char *text, ...) { - assert(ret); - assert(text); +int loop_read_exact(int fd, void *buf, size_t nbytes, bool do_poll) { + ssize_t n; - for (;;) { - char line[LINE_MAX]; - va_list ap; + n = loop_read(fd, buf, nbytes, do_poll); + if (n < 0) + return n; + if ((size_t) n != nbytes) + return -EIO; + return 0; +} - if (on_tty()) - fputs(ANSI_HIGHLIGHT_ON, stdout); +int loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) { + const uint8_t *p = buf; - va_start(ap, text); - vprintf(text, ap); - va_end(ap); + assert(fd >= 0); + assert(buf); - if (on_tty()) - fputs(ANSI_HIGHLIGHT_OFF, stdout); + errno = 0; - fflush(stdout); + do { + ssize_t k; - errno = 0; - if (!fgets(line, sizeof(line), stdin)) - return errno ? -errno : -EIO; + k = write(fd, p, nbytes); + if (k < 0) { + if (errno == EINTR) + continue; - if (!endswith(line, "\n")) - putchar('\n'); - else { - char *s; + if (errno == EAGAIN && do_poll) { + /* We knowingly ignore any return value here, + * and expect that any error/EOF is reported + * via write() */ - if (isempty(line)) + fd_wait_for_event(fd, POLLOUT, USEC_INFINITY); continue; + } - truncate_nl(line); - s = strdup(line); - if (!s) - return -ENOMEM; - - *ret = s; - return 0; + return -errno; } - } -} -int reset_terminal_fd(int fd, bool switch_to_text) { - struct termios termios; - int r = 0; - - /* Set terminal to some sane defaults */ - - assert(fd >= 0); - - /* We leave locked terminal attributes untouched, so that - * Plymouth may set whatever it wants to set, and we don't - * interfere with that. */ - - /* Disable exclusive mode, just in case */ - ioctl(fd, TIOCNXCL); - - /* Switch to text mode */ - if (switch_to_text) - ioctl(fd, KDSETMODE, KD_TEXT); - - /* Enable console unicode mode */ - ioctl(fd, KDSKBMODE, K_UNICODE); - - if (tcgetattr(fd, &termios) < 0) { - r = -errno; - goto finish; - } - - /* We only reset the stuff that matters to the software. How - * hardware is set up we don't touch assuming that somebody - * else will do that for us */ - - termios.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | IUCLC); - termios.c_iflag |= ICRNL | IMAXBEL | IUTF8; - termios.c_oflag |= ONLCR; - termios.c_cflag |= CREAD; - termios.c_lflag = ISIG | ICANON | IEXTEN | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOPRT | ECHOKE; - - termios.c_cc[VINTR] = 03; /* ^C */ - termios.c_cc[VQUIT] = 034; /* ^\ */ - termios.c_cc[VERASE] = 0177; - termios.c_cc[VKILL] = 025; /* ^X */ - termios.c_cc[VEOF] = 04; /* ^D */ - termios.c_cc[VSTART] = 021; /* ^Q */ - termios.c_cc[VSTOP] = 023; /* ^S */ - termios.c_cc[VSUSP] = 032; /* ^Z */ - termios.c_cc[VLNEXT] = 026; /* ^V */ - termios.c_cc[VWERASE] = 027; /* ^W */ - termios.c_cc[VREPRINT] = 022; /* ^R */ - termios.c_cc[VEOL] = 0; - termios.c_cc[VEOL2] = 0; - - termios.c_cc[VTIME] = 0; - termios.c_cc[VMIN] = 1; - - if (tcsetattr(fd, TCSANOW, &termios) < 0) - r = -errno; - -finish: - /* Just in case, flush all crap out */ - tcflush(fd, TCIOFLUSH); - - return r; -} - -int reset_terminal(const char *name) { - _cleanup_close_ int fd = -1; + if (nbytes > 0 && k == 0) /* Can't really happen */ + return -EIO; - fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return fd; + p += k; + nbytes -= k; + } while (nbytes > 0); - return reset_terminal_fd(fd, true); + return 0; } -int open_terminal(const char *name, int mode) { - int fd, r; - unsigned c = 0; +int parse_size(const char *t, off_t base, off_t *size) { - /* - * If a TTY is in the process of being closed opening it might - * cause EIO. This is horribly awful, but unlikely to be - * changed in the kernel. Hence we work around this problem by - * retrying a couple of times. + /* Soo, sometimes we want to parse IEC binary suffixes, and + * sometimes SI decimal suffixes. This function can parse + * both. Which one is the right way depends on the + * context. Wikipedia suggests that SI is customary for + * hardware metrics and network speeds, while IEC is + * customary for most data sizes used by software and volatile + * (RAM) memory. Hence be careful which one you pick! * - * https://bugs.launchpad.net/ubuntu/+source/linux/+bug/554172/comments/245 - */ - - assert(!(mode & O_CREAT)); + * In either case we use just K, M, G as suffix, and not Ki, + * Mi, Gi or so (as IEC would suggest). That's because that's + * frickin' ugly. But this means you really need to make sure + * to document which base you are parsing when you use this + * call. */ - for (;;) { - fd = open(name, mode, 0); - if (fd >= 0) - break; + struct table { + const char *suffix; + unsigned long long factor; + }; - if (errno != EIO) - return -errno; + static const struct table iec[] = { + { "E", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, + { "P", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, + { "T", 1024ULL*1024ULL*1024ULL*1024ULL }, + { "G", 1024ULL*1024ULL*1024ULL }, + { "M", 1024ULL*1024ULL }, + { "K", 1024ULL }, + { "B", 1 }, + { "", 1 }, + }; - /* Max 1s in total */ - if (c >= 20) - return -errno; + static const struct table si[] = { + { "E", 1000ULL*1000ULL*1000ULL*1000ULL*1000ULL*1000ULL }, + { "P", 1000ULL*1000ULL*1000ULL*1000ULL*1000ULL }, + { "T", 1000ULL*1000ULL*1000ULL*1000ULL }, + { "G", 1000ULL*1000ULL*1000ULL }, + { "M", 1000ULL*1000ULL }, + { "K", 1000ULL }, + { "B", 1 }, + { "", 1 }, + }; - usleep(50 * USEC_PER_MSEC); - c++; - } + const struct table *table; + const char *p; + unsigned long long r = 0; + unsigned n_entries, start_pos = 0; - r = isatty(fd); - if (r < 0) { - safe_close(fd); - return -errno; - } + assert(t); + assert(base == 1000 || base == 1024); + assert(size); - if (!r) { - safe_close(fd); - return -ENOTTY; + if (base == 1000) { + table = si; + n_entries = ELEMENTSOF(si); + } else { + table = iec; + n_entries = ELEMENTSOF(iec); } - return fd; -} - -int flush_fd(int fd) { - struct pollfd pollfd = { - .fd = fd, - .events = POLLIN, - }; - - for (;;) { - char buf[LINE_MAX]; - ssize_t l; - int r; + p = t; + do { + long long l; + unsigned long long l2; + double frac = 0; + char *e; + unsigned i; - r = poll(&pollfd, 1, 0); - if (r < 0) { - if (errno == EINTR) - continue; + errno = 0; + l = strtoll(p, &e, 10); + if (errno > 0) return -errno; - } else if (r == 0) - return 0; + if (l < 0) + return -ERANGE; - l = read(fd, buf, sizeof(buf)); - if (l < 0) { + if (e == p) + return -EINVAL; - if (errno == EINTR) - continue; + if (*e == '.') { + e++; + if (*e >= '0' && *e <= '9') { + char *e2; - if (errno == EAGAIN) - return 0; + /* strotoull itself would accept space/+/- */ + l2 = strtoull(e, &e2, 10); - return -errno; - } else if (l == 0) - return 0; - } -} + if (errno == ERANGE) + return -errno; -int acquire_terminal( - const char *name, - bool fail, - bool force, - bool ignore_tiocstty_eperm, - usec_t timeout) { + /* Ignore failure. E.g. 10.M is valid */ + frac = l2; + for (; e < e2; e++) + frac /= 10; + } + } - int fd = -1, notify = -1, r = 0, wd = -1; - usec_t ts = 0; + e += strspn(e, WHITESPACE); - assert(name); + for (i = start_pos; i < n_entries; i++) + if (startswith(e, table[i].suffix)) { + unsigned long long tmp; + if ((unsigned long long) l + (frac > 0) > ULLONG_MAX / table[i].factor) + return -ERANGE; + tmp = l * table[i].factor + (unsigned long long) (frac * table[i].factor); + if (tmp > ULLONG_MAX - r) + return -ERANGE; - /* We use inotify to be notified when the tty is closed. We - * create the watch before checking if we can actually acquire - * it, so that we don't lose any event. - * - * Note: strictly speaking this actually watches for the - * device being closed, it does *not* really watch whether a - * tty loses its controlling process. However, unless some - * rogue process uses TIOCNOTTY on /dev/tty *after* closing - * its tty otherwise this will not become a problem. As long - * as the administrator makes sure not configure any service - * on the same tty as an untrusted user this should not be a - * problem. (Which he probably should not do anyway.) */ + r += tmp; + if ((unsigned long long) (off_t) r != r) + return -ERANGE; - if (timeout != USEC_INFINITY) - ts = now(CLOCK_MONOTONIC); + p = e + strlen(table[i].suffix); - if (!fail && !force) { - notify = inotify_init1(IN_CLOEXEC | (timeout != USEC_INFINITY ? IN_NONBLOCK : 0)); - if (notify < 0) { - r = -errno; - goto fail; - } + start_pos = i + 1; + break; + } - wd = inotify_add_watch(notify, name, IN_CLOSE); - if (wd < 0) { - r = -errno; - goto fail; - } - } + if (i >= n_entries) + return -EINVAL; - for (;;) { - struct sigaction sa_old, sa_new = { - .sa_handler = SIG_IGN, - .sa_flags = SA_RESTART, - }; + } while (*p); - if (notify >= 0) { - r = flush_fd(notify); - if (r < 0) - goto fail; - } + *size = r; - /* We pass here O_NOCTTY only so that we can check the return - * value TIOCSCTTY and have a reliable way to figure out if we - * successfully became the controlling process of the tty */ - fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return fd; + return 0; +} - /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed - * if we already own the tty. */ - assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0); +bool is_device_path(const char *path) { - /* First, try to get the tty */ - if (ioctl(fd, TIOCSCTTY, force) < 0) - r = -errno; + /* Returns true on paths that refer to a device, either in + * sysfs or in /dev */ - assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0); + return + path_startswith(path, "/dev/") || + path_startswith(path, "/sys/"); +} - /* Sometimes it makes sense to ignore TIOCSCTTY - * returning EPERM, i.e. when very likely we already - * are have this controlling terminal. */ - if (r < 0 && r == -EPERM && ignore_tiocstty_eperm) - r = 0; +int dir_is_empty(const char *path) { + _cleanup_closedir_ DIR *d; - if (r < 0 && (force || fail || r != -EPERM)) { - goto fail; - } + d = opendir(path); + if (!d) + return -errno; - if (r >= 0) - break; + for (;;) { + struct dirent *de; - assert(!fail); - assert(!force); - assert(notify >= 0); + errno = 0; + de = readdir(d); + if (!de && errno != 0) + return -errno; - for (;;) { - uint8_t inotify_buffer[sizeof(struct inotify_event) + FILENAME_MAX]; - ssize_t l; - struct inotify_event *e; + if (!de) + return 1; - if (timeout != USEC_INFINITY) { - usec_t n; + if (!hidden_file(de->d_name)) + return 0; + } +} - n = now(CLOCK_MONOTONIC); - if (ts + timeout < n) { - r = -ETIMEDOUT; - goto fail; - } +char* dirname_malloc(const char *path) { + char *d, *dir, *dir2; - r = fd_wait_for_event(fd, POLLIN, ts + timeout - n); - if (r < 0) - goto fail; + d = strdup(path); + if (!d) + return NULL; + dir = dirname(d); + assert(dir); - if (r == 0) { - r = -ETIMEDOUT; - goto fail; - } - } + if (dir != d) { + dir2 = strdup(dir); + free(d); + return dir2; + } - l = read(notify, inotify_buffer, sizeof(inotify_buffer)); - if (l < 0) { + return dir; +} - if (errno == EINTR || errno == EAGAIN) - continue; +void rename_process(const char name[8]) { + assert(name); - r = -errno; - goto fail; - } + /* 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 */ - e = (struct inotify_event*) inotify_buffer; + prctl(PR_SET_NAME, name); - while (l > 0) { - size_t step; + if (program_invocation_name) + strncpy(program_invocation_name, name, strlen(program_invocation_name)); - if (e->wd != wd || !(e->mask & IN_CLOSE)) { - r = -EIO; - goto fail; - } + if (saved_argc > 0) { + int i; - step = sizeof(struct inotify_event) + e->len; - assert(step <= (size_t) l); + if (saved_argv[0]) + strncpy(saved_argv[0], name, strlen(saved_argv[0])); - e = (struct inotify_event*) ((uint8_t*) e + step); - l -= step; - } + for (i = 1; i < saved_argc; i++) { + if (!saved_argv[i]) + break; - break; + memzero(saved_argv[i], strlen(saved_argv[i])); } - - /* We close the tty fd here since if the old session - * ended our handle will be dead. It's important that - * we do this after sleeping, so that we don't enter - * an endless loop. */ - fd = safe_close(fd); } +} - safe_close(notify); +void sigset_add_many(sigset_t *ss, ...) { + va_list ap; + int sig; - r = reset_terminal_fd(fd, true); - if (r < 0) - log_warning_errno(r, "Failed to reset terminal: %m"); - - return fd; - -fail: - safe_close(fd); - safe_close(notify); + assert(ss); - return r; + va_start(ap, ss); + while ((sig = va_arg(ap, int)) > 0) + assert_se(sigaddset(ss, sig) == 0); + va_end(ap); } -int release_terminal(void) { - static const struct sigaction sa_new = { - .sa_handler = SIG_IGN, - .sa_flags = SA_RESTART, - }; +int sigprocmask_many(int how, ...) { + va_list ap; + sigset_t ss; + int sig; - _cleanup_close_ int fd = -1; - struct sigaction sa_old; - int r = 0; + assert_se(sigemptyset(&ss) == 0); - fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_NDELAY|O_CLOEXEC); - if (fd < 0) + va_start(ap, how); + while ((sig = va_arg(ap, int)) > 0) + assert_se(sigaddset(&ss, sig) == 0); + va_end(ap); + + if (sigprocmask(how, &ss, NULL) < 0) return -errno; - /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed - * by our own TIOCNOTTY */ - assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0); + return 0; +} +char *lookup_uid(uid_t uid) { + long bufsize; + char *name; + _cleanup_free_ char *buf = NULL; + struct passwd pwbuf, *pw = NULL; - if (ioctl(fd, TIOCNOTTY) < 0) - r = -errno; + /* Shortcut things to avoid NSS lookups */ + if (uid == 0) + return strdup("root"); - assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0); + bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); + if (bufsize <= 0) + bufsize = 4096; - return r; -} + buf = malloc(bufsize); + if (!buf) + return NULL; -int sigaction_many(const struct sigaction *sa, ...) { - va_list ap; - int r = 0, sig; + if (getpwuid_r(uid, &pwbuf, buf, bufsize, &pw) == 0 && pw) + return strdup(pw->pw_name); - va_start(ap, sa); - while ((sig = va_arg(ap, int)) > 0) - if (sigaction(sig, sa, NULL) < 0) - r = -errno; - va_end(ap); + if (asprintf(&name, UID_FMT, uid) < 0) + return NULL; - return r; + return name; } -int ignore_signals(int sig, ...) { - struct sigaction sa = { - .sa_handler = SIG_IGN, - .sa_flags = SA_RESTART, - }; - va_list ap; - int r = 0; - - if (sigaction(sig, &sa, NULL) < 0) - r = -errno; +char* getlogname_malloc(void) { + uid_t uid; + struct stat st; - va_start(ap, sig); - while ((sig = va_arg(ap, int)) > 0) - if (sigaction(sig, &sa, NULL) < 0) - r = -errno; - va_end(ap); + if (isatty(STDIN_FILENO) && fstat(STDIN_FILENO, &st) >= 0) + uid = st.st_uid; + else + uid = getuid(); - return r; + return lookup_uid(uid); } -int default_signals(int sig, ...) { - struct sigaction sa = { - .sa_handler = SIG_DFL, - .sa_flags = SA_RESTART, - }; - va_list ap; - int r = 0; +char *getusername_malloc(void) { + const char *e; - if (sigaction(sig, &sa, NULL) < 0) - r = -errno; + e = getenv("USER"); + if (e) + return strdup(e); - va_start(ap, sig); - while ((sig = va_arg(ap, int)) > 0) - if (sigaction(sig, &sa, NULL) < 0) - r = -errno; - va_end(ap); + return lookup_uid(getuid()); +} - return r; +bool is_temporary_fs(const struct statfs *s) { + assert(s); + + return F_TYPE_EQUAL(s->f_type, TMPFS_MAGIC) || + F_TYPE_EQUAL(s->f_type, RAMFS_MAGIC); } -void safe_close_pair(int p[]) { - assert(p); +int fd_is_temporary_fs(int fd) { + struct statfs s; - if (p[0] == p[1]) { - /* Special case pairs which use the same fd in both - * directions... */ - p[0] = p[1] = safe_close(p[0]); - return; - } + if (fstatfs(fd, &s) < 0) + return -errno; - p[0] = safe_close(p[0]); - p[1] = safe_close(p[1]); + return is_temporary_fs(&s); } -ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) { - uint8_t *p = buf; - ssize_t n = 0; +int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) { + assert(path); - assert(fd >= 0); - assert(buf); + /* Under the assumption that we are running privileged we + * first change the access mode and only then hand out + * ownership to avoid a window where access is too open. */ - while (nbytes > 0) { - ssize_t k; + if (mode != MODE_INVALID) + if (chmod(path, mode) < 0) + return -errno; - k = read(fd, p, nbytes); - if (k < 0 && errno == EINTR) - continue; + if (uid != UID_INVALID || gid != GID_INVALID) + if (chown(path, uid, gid) < 0) + return -errno; - if (k < 0 && errno == EAGAIN && do_poll) { + return 0; +} - /* We knowingly ignore any return value here, - * and expect that any error/EOF is reported - * via read() */ +int fchmod_and_fchown(int fd, mode_t mode, uid_t uid, gid_t gid) { + assert(fd >= 0); - fd_wait_for_event(fd, POLLIN, USEC_INFINITY); - continue; - } + /* Under the assumption that we are running privileged we + * first change the access mode and only then hand out + * ownership to avoid a window where access is too open. */ - if (k <= 0) - return n > 0 ? n : (k < 0 ? -errno : 0); + if (mode != MODE_INVALID) + if (fchmod(fd, mode) < 0) + return -errno; - p += k; - nbytes -= k; - n += k; - } + if (uid != UID_INVALID || gid != GID_INVALID) + if (fchown(fd, uid, gid) < 0) + return -errno; - return n; + return 0; } -ssize_t loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) { - const uint8_t *p = buf; - ssize_t n = 0; - - assert(fd >= 0); - assert(buf); +cpu_set_t* cpu_set_malloc(unsigned *ncpus) { + cpu_set_t *r; + unsigned n = 1024; - while (nbytes > 0) { - ssize_t k; + /* Allocates the cpuset in the right size */ - k = write(fd, p, nbytes); - if (k < 0 && errno == EINTR) - continue; + for (;;) { + if (!(r = CPU_ALLOC(n))) + return NULL; - if (k < 0 && errno == EAGAIN && do_poll) { + if (sched_getaffinity(0, CPU_ALLOC_SIZE(n), r) >= 0) { + CPU_ZERO_S(CPU_ALLOC_SIZE(n), r); - /* We knowingly ignore any return value here, - * and expect that any error/EOF is reported - * via write() */ + if (ncpus) + *ncpus = n; - fd_wait_for_event(fd, POLLOUT, USEC_INFINITY); - continue; + return r; } - if (k <= 0) - return n > 0 ? n : (k < 0 ? -errno : 0); + CPU_FREE(r); - p += k; - nbytes -= k; - n += k; - } + if (errno != EINVAL) + return NULL; - return n; + n *= 2; + } } -int parse_size(const char *t, off_t base, off_t *size) { +int files_same(const char *filea, const char *fileb) { + struct stat a, b; - /* Soo, sometimes we want to parse IEC binary suffxies, and - * sometimes SI decimal suffixes. This function can parse - * both. Which one is the right way depends on the - * context. Wikipedia suggests that SI is customary for - * hardrware metrics and network speeds, while IEC is - * customary for most data sizes used by software and volatile - * (RAM) memory. Hence be careful which one you pick! - * - * In either case we use just K, M, G as suffix, and not Ki, - * Mi, Gi or so (as IEC would suggest). That's because that's - * frickin' ugly. But this means you really need to make sure - * to document which base you are parsing when you use this - * call. */ + if (stat(filea, &a) < 0) + return -errno; - struct table { - const char *suffix; - unsigned long long factor; - }; + if (stat(fileb, &b) < 0) + return -errno; - static const struct table iec[] = { - { "E", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, - { "P", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, - { "T", 1024ULL*1024ULL*1024ULL*1024ULL }, - { "G", 1024ULL*1024ULL*1024ULL }, - { "M", 1024ULL*1024ULL }, - { "K", 1024ULL }, - { "B", 1 }, - { "", 1 }, - }; + return a.st_dev == b.st_dev && + a.st_ino == b.st_ino; +} - static const struct table si[] = { - { "E", 1000ULL*1000ULL*1000ULL*1000ULL*1000ULL*1000ULL }, - { "P", 1000ULL*1000ULL*1000ULL*1000ULL*1000ULL }, - { "T", 1000ULL*1000ULL*1000ULL*1000ULL }, - { "G", 1000ULL*1000ULL*1000ULL }, - { "M", 1000ULL*1000ULL }, - { "K", 1000ULL }, - { "B", 1 }, - { "", 1 }, - }; +int running_in_chroot(void) { + int ret; - const struct table *table; - const char *p; - unsigned long long r = 0; - unsigned n_entries, start_pos = 0; + ret = files_same("/proc/1/root", "/"); + if (ret < 0) + return ret; - assert(t); - assert(base == 1000 || base == 1024); - assert(size); + return ret == 0; +} - if (base == 1000) { - table = si; - n_entries = ELEMENTSOF(si); - } else { - table = iec; - n_entries = ELEMENTSOF(iec); - } +static char *ascii_ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) { + size_t x; + char *r; - p = t; - do { - long long l; - unsigned long long l2; - double frac = 0; - char *e; - unsigned i; + assert(s); + assert(percent <= 100); + assert(new_length >= 3); - errno = 0; - l = strtoll(p, &e, 10); + if (old_length <= 3 || old_length <= new_length) + return strndup(s, old_length); - if (errno > 0) - return -errno; + r = new0(char, new_length+1); + if (!r) + return NULL; - if (l < 0) - return -ERANGE; + x = (new_length * percent) / 100; - if (e == p) - return -EINVAL; + if (x > new_length - 3) + x = new_length - 3; - if (*e == '.') { - e++; - if (*e >= '0' && *e <= '9') { - char *e2; + memcpy(r, s, x); + r[x] = '.'; + r[x+1] = '.'; + r[x+2] = '.'; + memcpy(r + x + 3, + s + old_length - (new_length - x - 3), + new_length - x - 3); - /* strotoull itself would accept space/+/- */ - l2 = strtoull(e, &e2, 10); + return r; +} - if (errno == ERANGE) - return -errno; +char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) { + size_t x; + char *e; + const char *i, *j; + unsigned k, len, len2; - /* Ignore failure. E.g. 10.M is valid */ - frac = l2; - for (; e < e2; e++) - frac /= 10; - } - } + assert(s); + assert(percent <= 100); + assert(new_length >= 3); - e += strspn(e, WHITESPACE); + /* if no multibyte characters use ascii_ellipsize_mem for speed */ + if (ascii_is_valid(s)) + return ascii_ellipsize_mem(s, old_length, new_length, percent); - for (i = start_pos; i < n_entries; i++) - if (startswith(e, table[i].suffix)) { - unsigned long long tmp; - if ((unsigned long long) l + (frac > 0) > ULLONG_MAX / table[i].factor) - return -ERANGE; - tmp = l * table[i].factor + (unsigned long long) (frac * table[i].factor); - if (tmp > ULLONG_MAX - r) - return -ERANGE; + if (old_length <= 3 || old_length <= new_length) + return strndup(s, old_length); - r += tmp; - if ((unsigned long long) (off_t) r != r) - return -ERANGE; + x = (new_length * percent) / 100; - p = e + strlen(table[i].suffix); + if (x > new_length - 3) + x = new_length - 3; - start_pos = i + 1; - break; - } + k = 0; + for (i = s; k < x && i < s + old_length; i = utf8_next_char(i)) { + int c; - if (i >= n_entries) - return -EINVAL; + c = utf8_encoded_to_unichar(i); + if (c < 0) + return NULL; + k += unichar_iswide(c) ? 2 : 1; + } - } while (*p); + if (k > x) /* last character was wide and went over quota */ + x ++; - *size = r; + for (j = s + old_length; k < new_length && j > i; ) { + int c; - return 0; -} + j = utf8_prev_char(j); + c = utf8_encoded_to_unichar(j); + if (c < 0) + return NULL; + k += unichar_iswide(c) ? 2 : 1; + } + assert(i <= j); -int make_stdio(int fd) { - int r, s, t; + /* we don't actually need to ellipsize */ + if (i == j) + return memdup(s, old_length + 1); - assert(fd >= 0); + /* make space for ellipsis */ + j = utf8_next_char(j); - r = dup3(fd, STDIN_FILENO, 0); - s = dup3(fd, STDOUT_FILENO, 0); - t = dup3(fd, STDERR_FILENO, 0); + len = i - s; + len2 = s + old_length - j; + e = new(char, len + 3 + len2 + 1); + if (!e) + return NULL; - if (fd >= 3) - safe_close(fd); + /* + printf("old_length=%zu new_length=%zu x=%zu len=%u len2=%u k=%u\n", + old_length, new_length, x, len, len2, k); + */ - if (r < 0 || s < 0 || t < 0) - return -errno; + memcpy(e, s, len); + e[len] = 0xe2; /* tri-dot ellipsis: … */ + e[len + 1] = 0x80; + e[len + 2] = 0xa6; - /* We rely here that the new fd has O_CLOEXEC not set */ + memcpy(e + len + 3, j, len2 + 1); - return 0; + return e; } -int make_null_stdio(void) { - int null_fd; - - null_fd = open("/dev/null", O_RDWR|O_NOCTTY); - if (null_fd < 0) - return -errno; - - return make_stdio(null_fd); +char *ellipsize(const char *s, size_t length, unsigned percent) { + return ellipsize_mem(s, strlen(s), length, percent); } -bool is_device_path(const char *path) { - - /* Returns true on paths that refer to a device, either in - * sysfs or in /dev */ +int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode) { + _cleanup_close_ int fd; + int r; - return - path_startswith(path, "/dev/") || - path_startswith(path, "/sys/"); -} + assert(path); -int dir_is_empty(const char *path) { - _cleanup_closedir_ DIR *d; + if (parents) + mkdir_parents(path, 0755); - d = opendir(path); - if (!d) + fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, mode > 0 ? mode : 0644); + if (fd < 0) return -errno; - for (;;) { - struct dirent *de; - - errno = 0; - de = readdir(d); - if (!de && errno != 0) + if (mode > 0) { + r = fchmod(fd, mode); + if (r < 0) return -errno; - - if (!de) - return 1; - - if (!ignore_file(de->d_name)) - return 0; - } -} - -char* dirname_malloc(const char *path) { - char *d, *dir, *dir2; - - d = strdup(path); - if (!d) - return NULL; - dir = dirname(d); - assert(dir); - - if (dir != d) { - dir2 = strdup(dir); - free(d); - return dir2; } - return dir; -} - -int dev_urandom(void *p, size_t n) { - static int have_syscall = -1; - int r, fd; - ssize_t k; - - /* Gathers some randomness from the kernel. This call will - * never block, and will always return some data from the - * kernel, regardless if the random pool is fully initialized - * or not. It thus makes no guarantee for the quality of the - * returned entropy, but is good enough for or usual usecases - * of seeding the hash functions for hashtable */ - - /* Use the getrandom() syscall unless we know we don't have - * it, or when the requested size is too large for it. */ - if (have_syscall != 0 || (size_t) (int) n != n) { - r = getrandom(p, n, GRND_NONBLOCK); - if (r == (int) n) { - have_syscall = true; - return 0; - } - - if (r < 0) { - if (errno == ENOSYS) - /* we lack the syscall, continue with - * reading from /dev/urandom */ - have_syscall = false; - else if (errno == EAGAIN) - /* not enough entropy for now. Let's - * remember to use the syscall the - * next time, again, but also read - * from /dev/urandom for now, which - * doesn't care about the current - * amount of entropy. */ - have_syscall = true; - else - return -errno; - } else - /* too short read? */ - return -EIO; + if (uid != UID_INVALID || gid != GID_INVALID) { + r = fchown(fd, uid, gid); + if (r < 0) + return -errno; } - fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - return errno == ENOENT ? -ENOSYS : -errno; - - k = loop_read(fd, p, n, true); - safe_close(fd); + if (stamp != USEC_INFINITY) { + struct timespec ts[2]; - if (k < 0) - return (int) k; - if ((size_t) k != n) - return -EIO; + timespec_store(&ts[0], stamp); + ts[1] = ts[0]; + r = futimens(fd, ts); + } else + r = futimens(fd, NULL); + if (r < 0) + return -errno; return 0; } -void initialize_srand(void) { - static bool srand_called = false; - unsigned x; -#ifdef HAVE_SYS_AUXV_H - void *auxv; -#endif - - if (srand_called) - return; +int touch(const char *path) { + return touch_file(path, false, USEC_INFINITY, UID_INVALID, GID_INVALID, 0); +} - x = 0; +static char *unquote(const char *s, const char* quotes) { + size_t l; + assert(s); -#ifdef HAVE_SYS_AUXV_H - /* The kernel provides us with a bit of entropy in auxv, so - * let's try to make use of that to seed the pseudo-random - * generator. It's better than nothing... */ + /* This is rather stupid, simply removes the heading and + * trailing quotes if there is one. Doesn't care about + * escaping or anything. + * + * DON'T USE THIS FOR NEW CODE ANYMORE!*/ - auxv = (void*) getauxval(AT_RANDOM); - if (auxv) - x ^= *(unsigned*) auxv; -#endif + l = strlen(s); + if (l < 2) + return strdup(s); - x ^= (unsigned) now(CLOCK_REALTIME); - x ^= (unsigned) gettid(); + if (strchr(quotes, s[0]) && s[l-1] == s[0]) + return strndup(s+1, l-2); - srand(x); - srand_called = true; + return strdup(s); } -void random_bytes(void *p, size_t n) { - uint8_t *q; - int r; - - r = dev_urandom(p, n); - if (r >= 0) - return; +noreturn void freeze(void) { - /* If some idiot made /dev/urandom unavailable to us, he'll - * get a PRNG instead. */ + /* Make sure nobody waits for us on a socket anymore */ + close_all_fds(NULL, 0); - initialize_srand(); + sync(); - for (q = p; q < (uint8_t*) p + n; q ++) - *q = rand(); + for (;;) + pause(); } -void rename_process(const char name[8]) { - assert(name); +bool null_or_empty(struct stat *st) { + assert(st); - /* 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 */ - - prctl(PR_SET_NAME, name); - - if (program_invocation_name) - strncpy(program_invocation_name, name, strlen(program_invocation_name)); - - if (saved_argc > 0) { - int i; - - if (saved_argv[0]) - strncpy(saved_argv[0], name, strlen(saved_argv[0])); + if (S_ISREG(st->st_mode) && st->st_size <= 0) + return true; - for (i = 1; i < saved_argc; i++) { - if (!saved_argv[i]) - break; + if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)) + return true; - memzero(saved_argv[i], strlen(saved_argv[i])); - } - } + return false; } -void sigset_add_many(sigset_t *ss, ...) { - va_list ap; - int sig; +int null_or_empty_path(const char *fn) { + struct stat st; - assert(ss); + assert(fn); - va_start(ap, ss); - while ((sig = va_arg(ap, int)) > 0) - assert_se(sigaddset(ss, sig) == 0); - va_end(ap); -} + if (stat(fn, &st) < 0) + return -errno; -int sigprocmask_many(int how, ...) { - va_list ap; - sigset_t ss; - int sig; + return null_or_empty(&st); +} - assert_se(sigemptyset(&ss) == 0); +int null_or_empty_fd(int fd) { + struct stat st; - va_start(ap, how); - while ((sig = va_arg(ap, int)) > 0) - assert_se(sigaddset(&ss, sig) == 0); - va_end(ap); + assert(fd >= 0); - if (sigprocmask(how, &ss, NULL) < 0) + if (fstat(fd, &st) < 0) return -errno; - return 0; + return null_or_empty(&st); } -char* gethostname_malloc(void) { - struct utsname u; +DIR *xopendirat(int fd, const char *name, int flags) { + int nfd; + DIR *d; + + assert(!(flags & O_CREAT)); - assert_se(uname(&u) >= 0); + nfd = openat(fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|flags, 0); + if (nfd < 0) + return NULL; - if (!isempty(u.nodename) && !streq(u.nodename, "(none)")) - return strdup(u.nodename); + d = fdopendir(nfd); + if (!d) { + safe_close(nfd); + return NULL; + } - return strdup(u.sysname); + return d; } -bool hostname_is_set(void) { - struct utsname u; +int signal_from_string_try_harder(const char *s) { + int signo; + assert(s); - assert_se(uname(&u) >= 0); + signo = signal_from_string(s); + if (signo <= 0) + if (startswith(s, "SIG")) + return signal_from_string(s+3); - return !isempty(u.nodename) && !streq(u.nodename, "(none)"); + return signo; } -char *lookup_uid(uid_t uid) { - long bufsize; - char *name; - _cleanup_free_ char *buf = NULL; - struct passwd pwbuf, *pw = NULL; - - /* Shortcut things to avoid NSS lookups */ - if (uid == 0) - return strdup("root"); - - bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); - if (bufsize <= 0) - bufsize = 4096; +static char *tag_to_udev_node(const char *tagvalue, const char *by) { + _cleanup_free_ char *t = NULL, *u = NULL; + size_t enc_len; - buf = malloc(bufsize); - if (!buf) + u = unquote(tagvalue, QUOTES); + if (!u) return NULL; - if (getpwuid_r(uid, &pwbuf, buf, bufsize, &pw) == 0 && pw) - return strdup(pw->pw_name); + enc_len = strlen(u) * 4 + 1; + t = new(char, enc_len); + if (!t) + return NULL; - if (asprintf(&name, UID_FMT, uid) < 0) + if (encode_devnode_name(u, t, enc_len) < 0) return NULL; - return name; + return strjoin("/dev/disk/by-", by, "/", t, NULL); } -char* getlogname_malloc(void) { - uid_t uid; - struct stat st; +char *fstab_node_to_udev_node(const char *p) { + assert(p); - if (isatty(STDIN_FILENO) && fstat(STDIN_FILENO, &st) >= 0) - uid = st.st_uid; - else - uid = getuid(); + if (startswith(p, "LABEL=")) + return tag_to_udev_node(p+6, "label"); - return lookup_uid(uid); -} + if (startswith(p, "UUID=")) + return tag_to_udev_node(p+5, "uuid"); -char *getusername_malloc(void) { - const char *e; + if (startswith(p, "PARTUUID=")) + return tag_to_udev_node(p+9, "partuuid"); - e = getenv("USER"); - if (e) - return strdup(e); + if (startswith(p, "PARTLABEL=")) + return tag_to_udev_node(p+10, "partlabel"); - return lookup_uid(getuid()); + return strdup(p); } -int getttyname_malloc(int fd, char **r) { - char path[PATH_MAX], *c; - int k; +bool dirent_is_file(const struct dirent *de) { + assert(de); - assert(r); + if (hidden_file(de->d_name)) + return false; + + if (de->d_type != DT_REG && + de->d_type != DT_LNK && + de->d_type != DT_UNKNOWN) + return false; - k = ttyname_r(fd, path, sizeof(path)); - if (k > 0) - return -k; + return true; +} + +bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) { + assert(de); - char_array_0(path); + if (de->d_type != DT_REG && + de->d_type != DT_LNK && + de->d_type != DT_UNKNOWN) + return false; - c = strdup(startswith(path, "/dev/") ? path + 5 : path); - if (!c) - return -ENOMEM; + if (hidden_file_allow_backup(de->d_name)) + return false; - *r = c; - return 0; + return endswith(de->d_name, suffix); } -int getttyname_harder(int fd, char **r) { - int k; - char *s; +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; - k = getttyname_malloc(fd, &s); - if (k < 0) - return k; + /* 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 (streq(s, "tty")) { - free(s); - return get_ctty(0, NULL, r); - } + reset_all_signal_handlers(); + reset_signal_mask(); - *r = s; - return 0; -} + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); -int get_ctty_devnr(pid_t pid, dev_t *d) { - int r; - _cleanup_free_ char *line = NULL; - const char *p; - unsigned long ttynr; + pids = hashmap_new(NULL); + if (!pids) + return log_oom(); - assert(pid >= 0); + seen = set_new(&string_hash_ops); + if (!seen) + return log_oom(); - p = procfs_file_alloca(pid, "stat"); - r = read_one_line_file(p, &line); - if (r < 0) - return r; + STRV_FOREACH(directory, directories) { + _cleanup_closedir_ DIR *d; + struct dirent *de; - p = strrchr(line, ')'); - if (!p) - return -EIO; + d = opendir(*directory); + if (!d) { + if (errno == ENOENT) + continue; + + return log_error_errno(errno, "Failed to open directory %s: %m", *directory); + } - p++; + FOREACH_DIRENT(de, d, break) { + _cleanup_free_ char *path = NULL; + pid_t pid; + int r; - if (sscanf(p, " " - "%*c " /* state */ - "%*d " /* ppid */ - "%*d " /* pgrp */ - "%*d " /* session */ - "%lu ", /* ttynr */ - &ttynr) != 1) - return -EIO; + if (!dirent_is_file(de)) + continue; - if (major(ttynr) == 0 && minor(ttynr) == 0) - return -ENOENT; + if (set_contains(seen, de->d_name)) { + log_debug("%1$s/%2$s skipped (%2$s was already seen).", *directory, de->d_name); + continue; + } - if (d) - *d = (dev_t) ttynr; + r = set_put_strdup(seen, de->d_name); + if (r < 0) + return log_oom(); - return 0; -} + path = strjoin(*directory, "/", de->d_name, NULL); + if (!path) + return log_oom(); -int get_ctty(pid_t pid, dev_t *_devnr, char **r) { - char fn[sizeof("/dev/char/")-1 + 2*DECIMAL_STR_MAX(unsigned) + 1 + 1], *b = NULL; - _cleanup_free_ char *s = NULL; - const char *p; - dev_t devnr; - int k; + if (null_or_empty_path(path)) { + log_debug("%s is empty (a mask).", path); + continue; + } - assert(r); + pid = fork(); + if (pid < 0) { + log_error_errno(errno, "Failed to fork: %m"); + continue; + } else if (pid == 0) { + char *_argv[2]; - k = get_ctty_devnr(pid, &devnr); - if (k < 0) - return k; + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); - sprintf(fn, "/dev/char/%u:%u", major(devnr), minor(devnr)); + if (!argv) { + _argv[0] = path; + _argv[1] = NULL; + argv = _argv; + } else + argv[0] = path; - k = readlink_malloc(fn, &s); - if (k < 0) { + execv(path, argv); + return log_error_errno(errno, "Failed to execute %s: %m", path); + } - if (k != -ENOENT) - return k; + log_debug("Spawned %s as " PID_FMT ".", path, pid); - /* This is an ugly hack */ - if (major(devnr) == 136) { - asprintf(&b, "pts/%u", minor(devnr)); - goto finish; + r = hashmap_put(pids, UINT_TO_PTR(pid), path); + if (r < 0) + return log_oom(); + path = NULL; } + } - /* Probably something like the ptys which have no - * symlink in /dev/char. Let's return something - * vaguely useful. */ + /* Abort execution of this process after the timout. We simply + * rely on SIGALRM as default action terminating the process, + * and turn on alarm(). */ - b = strdup(fn + 5); - goto finish; - } + if (timeout != USEC_INFINITY) + alarm((timeout + USEC_PER_SEC - 1) / USEC_PER_SEC); - if (startswith(s, "/dev/")) - p = s + 5; - else if (startswith(s, "../")) - p = s + 3; - else - p = s; + while (!hashmap_isempty(pids)) { + _cleanup_free_ char *path = NULL; + pid_t pid; - b = strdup(p); + pid = PTR_TO_UINT(hashmap_first_key(pids)); + assert(pid > 0); -finish: - if (!b) - return -ENOMEM; + path = hashmap_remove(pids, UINT_TO_PTR(pid)); + assert(path); - *r = b; - if (_devnr) - *_devnr = devnr; + wait_for_terminate_and_warn(path, pid, true); + } return 0; } -int rm_rf_children_dangerous(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev) { - _cleanup_closedir_ DIR *d = NULL; - int ret = 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(fd >= 0); + assert(!strv_isempty(dirs)); - /* This returns the first error we run into, but nevertheless - * tries to go on. This closes the passed fd. */ + name = basename(dirs[0]); + assert(!isempty(name)); - d = fdopendir(fd); - if (!d) { - safe_close(fd); + /* 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. */ - return errno == ENOENT ? 0 : -errno; - } + executor_pid = fork(); + if (executor_pid < 0) { + log_error_errno(errno, "Failed to fork: %m"); + return; - for (;;) { - struct dirent *de; - bool is_dir, keep_around; - struct stat st; - int r; + } else if (executor_pid == 0) { + r = do_execute(dirs, timeout, argv); + _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); + } - errno = 0; - de = readdir(d); - if (!de) { - if (errno != 0 && ret == 0) - ret = -errno; - return ret; - } + wait_for_terminate_and_warn(name, executor_pid, true); +} - if (streq(de->d_name, ".") || streq(de->d_name, "..")) - continue; +bool nulstr_contains(const char*nulstr, const char *needle) { + const char *i; - if (de->d_type == DT_UNKNOWN || - honour_sticky || - (de->d_type == DT_DIR && root_dev)) { - if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { - if (ret == 0 && errno != ENOENT) - ret = -errno; - continue; - } - - is_dir = S_ISDIR(st.st_mode); - keep_around = - honour_sticky && - (st.st_uid == 0 || st.st_uid == getuid()) && - (st.st_mode & S_ISVTX); - } else { - is_dir = de->d_type == DT_DIR; - keep_around = false; - } - - if (is_dir) { - int subdir_fd; + if (!nulstr) + return false; - /* if root_dev is set, remove subdirectories only, if device is same as dir */ - if (root_dev && st.st_dev != root_dev->st_dev) - continue; + NULSTR_FOREACH(i, nulstr) + if (streq(i, needle)) + return true; - subdir_fd = openat(fd, de->d_name, - O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); - if (subdir_fd < 0) { - if (ret == 0 && errno != ENOENT) - ret = -errno; - continue; - } + return false; +} - r = rm_rf_children_dangerous(subdir_fd, only_dirs, honour_sticky, root_dev); - if (r < 0 && ret == 0) - ret = r; +bool plymouth_running(void) { + return access("/run/plymouth/pid", F_OK) >= 0; +} - if (!keep_around) - if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) { - if (ret == 0 && errno != ENOENT) - ret = -errno; - } +char* strshorten(char *s, size_t l) { + assert(s); - } else if (!only_dirs && !keep_around) { + if (l < strlen(s)) + s[l] = 0; - if (unlinkat(fd, de->d_name, 0) < 0) { - if (ret == 0 && errno != ENOENT) - ret = -errno; - } - } - } + return s; } -_pure_ static int is_temporary_fs(struct statfs *s) { - assert(s); +bool machine_name_is_valid(const char *s) { - return F_TYPE_EQUAL(s->f_type, TMPFS_MAGIC) || - F_TYPE_EQUAL(s->f_type, RAMFS_MAGIC); -} + if (!hostname_is_valid(s)) + return false; -int is_fd_on_temporary_fs(int fd) { - struct statfs s; + /* Machine names should be useful hostnames, but also be + * useful in unit names, hence we enforce a stricter length + * limitation. */ - if (fstatfs(fd, &s) < 0) - return -errno; + if (strlen(s) > 64) + return false; - return is_temporary_fs(&s); + return true; } -int rm_rf_children(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev) { - struct statfs s; +int pipe_eof(int fd) { + struct pollfd pollfd = { + .fd = fd, + .events = POLLIN|POLLHUP, + }; - assert(fd >= 0); + int r; - if (fstatfs(fd, &s) < 0) { - safe_close(fd); + r = poll(&pollfd, 1, 0); + if (r < 0) return -errno; - } - /* We refuse to clean disk file systems with this call. This - * is extra paranoia just to be sure we never ever remove - * non-state data */ - if (!is_temporary_fs(&s)) { - log_error("Attempted to remove disk file system, and we can't allow that."); - safe_close(fd); - return -EPERM; - } + if (r == 0) + return 0; - return rm_rf_children_dangerous(fd, only_dirs, honour_sticky, root_dev); + return pollfd.revents & POLLHUP; } -static int file_is_priv_sticky(const char *p) { - struct stat st; +int fd_wait_for_event(int fd, int event, usec_t t) { - assert(p); + struct pollfd pollfd = { + .fd = fd, + .events = event, + }; - if (lstat(p, &st) < 0) + struct timespec ts; + int r; + + r = ppoll(&pollfd, 1, t == USEC_INFINITY ? NULL : timespec_store(&ts, t), NULL); + if (r < 0) return -errno; - return - (st.st_uid == 0 || st.st_uid == getuid()) && - (st.st_mode & S_ISVTX); + if (r == 0) + return 0; + + return pollfd.revents; } -static int rm_rf_internal(const char *path, bool only_dirs, bool delete_root, bool honour_sticky, bool dangerous) { - int fd, r; - struct statfs s; +int fopen_temporary(const char *path, FILE **_f, char **_temp_path) { + FILE *f; + char *t; + int r, fd; assert(path); + assert(_f); + assert(_temp_path); - /* We refuse to clean the root file system with this - * call. This is extra paranoia to never cause a really - * seriously broken system. */ - if (path_equal(path, "/")) { - log_error("Attempted to remove entire root file system, and we can't allow that."); - return -EPERM; - } + r = tempfn_xxxxxx(path, &t); + if (r < 0) + return r; - fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); + fd = mkostemp_safe(t, O_WRONLY|O_CLOEXEC); if (fd < 0) { + free(t); + return -errno; + } - if (errno != ENOTDIR) - return -errno; + f = fdopen(fd, "we"); + if (!f) { + unlink(t); + free(t); + return -errno; + } - if (!dangerous) { - if (statfs(path, &s) < 0) - return -errno; + *_f = f; + *_temp_path = t; - if (!is_temporary_fs(&s)) { - log_error("Attempted to remove disk file system, and we can't allow that."); - return -EPERM; - } - } + return 0; +} - if (delete_root && !only_dirs) - if (unlink(path) < 0 && errno != ENOENT) - return -errno; +int symlink_atomic(const char *from, const char *to) { + _cleanup_free_ char *t = NULL; + int r; - return 0; - } + assert(from); + assert(to); - if (!dangerous) { - if (fstatfs(fd, &s) < 0) { - safe_close(fd); - return -errno; - } + r = tempfn_random(to, &t); + if (r < 0) + return r; - if (!is_temporary_fs(&s)) { - log_error("Attempted to remove disk file system, and we can't allow that."); - safe_close(fd); - return -EPERM; - } + if (symlink(from, t) < 0) + return -errno; + + if (rename(t, to) < 0) { + unlink_noerrno(t); + return -errno; } - r = rm_rf_children_dangerous(fd, only_dirs, honour_sticky, NULL); - if (delete_root) { + return 0; +} + +int symlink_idempotent(const char *from, const char *to) { + _cleanup_free_ char *p = NULL; + int r; + + assert(from); + assert(to); + + if (symlink(from, to) < 0) { + if (errno != EEXIST) + return -errno; - if (honour_sticky && file_is_priv_sticky(path) > 0) + r = readlink_malloc(to, &p); + if (r < 0) return r; - if (rmdir(path) < 0 && errno != ENOENT) { - if (r == 0) - r = -errno; - } + if (!streq(p, from)) + return -EINVAL; } - return r; -} - -int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky) { - return rm_rf_internal(path, only_dirs, delete_root, honour_sticky, false); + return 0; } -int rm_rf_dangerous(const char *path, bool only_dirs, bool delete_root, bool honour_sticky) { - return rm_rf_internal(path, only_dirs, delete_root, honour_sticky, true); -} +int mknod_atomic(const char *path, mode_t mode, dev_t dev) { + _cleanup_free_ char *t = NULL; + int r; -int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) { assert(path); - /* Under the assumption that we are running privileged we - * first change the access mode and only then hand out - * ownership to avoid a window where access is too open. */ + r = tempfn_random(path, &t); + if (r < 0) + return r; - if (mode != MODE_INVALID) - if (chmod(path, mode) < 0) - return -errno; + if (mknod(t, mode, dev) < 0) + return -errno; - if (uid != UID_INVALID || gid != GID_INVALID) - if (chown(path, uid, gid) < 0) - return -errno; + if (rename(t, path) < 0) { + unlink_noerrno(t); + return -errno; + } return 0; } -int fchmod_and_fchown(int fd, mode_t mode, uid_t uid, gid_t gid) { - assert(fd >= 0); +int mkfifo_atomic(const char *path, mode_t mode) { + _cleanup_free_ char *t = NULL; + int r; - /* Under the assumption that we are running privileged we - * first change the access mode and only then hand out - * ownership to avoid a window where access is too open. */ + assert(path); - if (mode != MODE_INVALID) - if (fchmod(fd, mode) < 0) - return -errno; + r = tempfn_random(path, &t); + if (r < 0) + return r; - if (uid != UID_INVALID || gid != GID_INVALID) - if (fchown(fd, uid, gid) < 0) - return -errno; + if (mkfifo(t, mode) < 0) + return -errno; + + if (rename(t, path) < 0) { + unlink_noerrno(t); + return -errno; + } return 0; } -cpu_set_t* cpu_set_malloc(unsigned *ncpus) { - cpu_set_t *r; - unsigned n = 1024; +bool display_is_local(const char *display) { + assert(display); - /* Allocates the cpuset in the right size */ + return + display[0] == ':' && + display[1] >= '0' && + display[1] <= '9'; +} - for (;;) { - if (!(r = CPU_ALLOC(n))) - return NULL; +int socket_from_display(const char *display, char **path) { + size_t k; + char *f, *c; - if (sched_getaffinity(0, CPU_ALLOC_SIZE(n), r) >= 0) { - CPU_ZERO_S(CPU_ALLOC_SIZE(n), r); + assert(display); + assert(path); - if (ncpus) - *ncpus = n; + if (!display_is_local(display)) + return -EINVAL; - return r; - } + k = strspn(display+1, "0123456789"); - CPU_FREE(r); + f = new(char, strlen("/tmp/.X11-unix/X") + k + 1); + if (!f) + return -ENOMEM; - if (errno != EINVAL) - return NULL; + c = stpcpy(f, "/tmp/.X11-unix/X"); + memcpy(c, display+1, k); + c[k] = 0; - n *= 2; - } -} + *path = f; -int status_vprintf(const char *status, bool ellipse, bool ephemeral, const char *format, va_list ap) { - static const char status_indent[] = " "; /* "[" STATUS "] " */ - _cleanup_free_ char *s = NULL; - _cleanup_close_ int fd = -1; - struct iovec iovec[6] = {}; - int n = 0; - static bool prev_ephemeral; + return 0; +} - assert(format); +int get_user_creds( + const char **username, + uid_t *uid, gid_t *gid, + const char **home, + const char **shell) { - /* This is independent of logging, as status messages are - * optional and go exclusively to the console. */ + struct passwd *p; + uid_t u; - if (vasprintf(&s, format, ap) < 0) - return log_oom(); + assert(username); + assert(*username); - fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return fd; + /* We enforce some special rules for uid=0: in order to avoid + * NSS lookups for root we hardcode its data. */ - if (ellipse) { - char *e; - size_t emax, sl; - int c; + if (streq(*username, "root") || streq(*username, "0")) { + *username = "root"; - c = fd_columns(fd); - if (c <= 0) - c = 80; + if (uid) + *uid = 0; - sl = status ? sizeof(status_indent)-1 : 0; + if (gid) + *gid = 0; - emax = c - sl - 1; - if (emax < 3) - emax = 3; + if (home) + *home = "/root"; - e = ellipsize(s, emax, 50); - if (e) { - free(s); - s = e; - } + if (shell) + *shell = "/bin/sh"; + + return 0; } - if (prev_ephemeral) - IOVEC_SET_STRING(iovec[n++], "\r" ANSI_ERASE_TO_END_OF_LINE); - prev_ephemeral = ephemeral; + if (parse_uid(*username, &u) >= 0) { + errno = 0; + p = getpwuid(u); - if (status) { - if (!isempty(status)) { - IOVEC_SET_STRING(iovec[n++], "["); - IOVEC_SET_STRING(iovec[n++], status); - IOVEC_SET_STRING(iovec[n++], "] "); - } else - IOVEC_SET_STRING(iovec[n++], status_indent); + /* If there are multiple users with the same id, make + * sure to leave $USER to the configured value instead + * of the first occurrence in the database. However if + * the uid was configured by a numeric uid, then let's + * pick the real username from /etc/passwd. */ + if (p) + *username = p->pw_name; + } else { + errno = 0; + p = getpwnam(*username); } - IOVEC_SET_STRING(iovec[n++], s); - if (!ephemeral) - IOVEC_SET_STRING(iovec[n++], "\n"); - - if (writev(fd, iovec, n) < 0) - return -errno; + if (!p) + return errno > 0 ? -errno : -ESRCH; - return 0; -} + if (uid) + *uid = p->pw_uid; -int status_printf(const char *status, bool ellipse, bool ephemeral, const char *format, ...) { - va_list ap; - int r; + if (gid) + *gid = p->pw_gid; - assert(format); + if (home) + *home = p->pw_dir; - va_start(ap, format); - r = status_vprintf(status, ellipse, ephemeral, format, ap); - va_end(ap); + if (shell) + *shell = p->pw_shell; - return r; + return 0; } -char *replace_env(const char *format, char **env) { - enum { - WORD, - CURLY, - VARIABLE - } state = WORD; - - const char *e, *word = format; - char *r = NULL, *k; +char* uid_to_name(uid_t uid) { + struct passwd *p; + char *r; - assert(format); + if (uid == 0) + return strdup("root"); - for (e = format; *e; e ++) { + p = getpwuid(uid); + if (p) + return strdup(p->pw_name); - switch (state) { + if (asprintf(&r, UID_FMT, uid) < 0) + return NULL; - case WORD: - if (*e == '$') - state = CURLY; - break; + return r; +} - case CURLY: - if (*e == '{') { - k = strnappend(r, word, e-word-1); - if (!k) - goto fail; +char* gid_to_name(gid_t gid) { + struct group *p; + char *r; - free(r); - r = k; + if (gid == 0) + return strdup("root"); - word = e-1; - state = VARIABLE; + p = getgrgid(gid); + if (p) + return strdup(p->gr_name); - } else if (*e == '$') { - k = strnappend(r, word, e-word); - if (!k) - goto fail; + if (asprintf(&r, GID_FMT, gid) < 0) + return NULL; - free(r); - r = k; + return r; +} - word = e+1; - state = WORD; - } else - state = WORD; - break; +int get_group_creds(const char **groupname, gid_t *gid) { + struct group *g; + gid_t id; - case VARIABLE: - if (*e == '}') { - const char *t; + assert(groupname); - t = strempty(strv_env_get_n(env, word+2, e-word-2)); + /* We enforce some special rules for gid=0: in order to avoid + * NSS lookups for root we hardcode its data. */ - k = strappend(r, t); - if (!k) - goto fail; + if (streq(*groupname, "root") || streq(*groupname, "0")) { + *groupname = "root"; - free(r); - r = k; + if (gid) + *gid = 0; - word = e+1; - state = WORD; - } - break; - } + return 0; } - k = strnappend(r, word, e-word); - if (!k) - goto fail; + if (parse_gid(*groupname, &id) >= 0) { + errno = 0; + g = getgrgid(id); - free(r); - return k; + if (g) + *groupname = g->gr_name; + } else { + errno = 0; + g = getgrnam(*groupname); + } -fail: - free(r); - return NULL; -} + if (!g) + return errno > 0 ? -errno : -ESRCH; -char **replace_env_argv(char **argv, char **env) { - char **ret, **i; - unsigned k = 0, l = 0; + if (gid) + *gid = g->gr_gid; - l = strv_length(argv); + return 0; +} - ret = new(char*, l+1); - if (!ret) - return NULL; +int in_gid(gid_t gid) { + gid_t *gids; + int ngroups_max, r, i; - STRV_FOREACH(i, argv) { + if (getgid() == gid) + return 1; - /* If $FOO appears as single word, replace it by the split up variable */ - if ((*i)[0] == '$' && (*i)[1] != '{') { - char *e; - char **w, **m; - unsigned q; + if (getegid() == gid) + return 1; - e = strv_env_get(env, *i+1); - if (e) { - int r; + ngroups_max = sysconf(_SC_NGROUPS_MAX); + assert(ngroups_max > 0); - r = strv_split_quoted(&m, e, true); - if (r < 0) { - ret[k] = NULL; - strv_free(ret); - return NULL; - } - } else - m = NULL; + gids = alloca(sizeof(gid_t) * ngroups_max); - q = strv_length(m); - l = l + q - 1; + r = getgroups(ngroups_max, gids); + if (r < 0) + return -errno; - w = realloc(ret, sizeof(char*) * (l+1)); - if (!w) { - ret[k] = NULL; - strv_free(ret); - strv_free(m); - return NULL; - } + for (i = 0; i < r; i++) + if (gids[i] == gid) + return 1; - ret = w; - if (m) { - memcpy(ret + k, m, q * sizeof(char*)); - free(m); - } + return 0; +} - k += q; - continue; - } +int in_group(const char *name) { + int r; + gid_t gid; - /* If ${FOO} appears as part of a word, replace it by the variable as-is */ - ret[k] = replace_env(*i, env); - if (!ret[k]) { - strv_free(ret); - return NULL; - } - k++; - } + r = get_group_creds(&name, &gid); + if (r < 0) + return r; - ret[k] = NULL; - return ret; + return in_gid(gid); } -int fd_columns(int fd) { - struct winsize ws = {}; +int glob_exists(const char *path) { + _cleanup_globfree_ glob_t g = {}; + int k; - if (ioctl(fd, TIOCGWINSZ, &ws) < 0) - return -errno; + assert(path); - if (ws.ws_col <= 0) - return -EIO; + errno = 0; + k = glob(path, GLOB_NOSORT|GLOB_BRACE, NULL, &g); - return ws.ws_col; + if (k == GLOB_NOMATCH) + return 0; + else if (k == GLOB_NOSPACE) + return -ENOMEM; + else if (k == 0) + return !strv_isempty(g.gl_pathv); + else + return errno ? -errno : -EIO; } -unsigned columns(void) { - const char *e; - int c; - - if (_likely_(cached_columns > 0)) - return cached_columns; +int glob_extend(char ***strv, const char *path) { + _cleanup_globfree_ glob_t g = {}; + int k; + char **p; - c = 0; - e = getenv("COLUMNS"); - if (e) - (void) safe_atoi(e, &c); + errno = 0; + k = glob(path, GLOB_NOSORT|GLOB_BRACE, NULL, &g); - if (c <= 0) - c = fd_columns(STDOUT_FILENO); + if (k == GLOB_NOMATCH) + return -ENOENT; + else if (k == GLOB_NOSPACE) + return -ENOMEM; + else if (k != 0 || strv_isempty(g.gl_pathv)) + return errno ? -errno : -EIO; - if (c <= 0) - c = 80; + STRV_FOREACH(p, g.gl_pathv) { + k = strv_extend(strv, *p); + if (k < 0) + break; + } - cached_columns = c; - return c; + return k; } -int fd_lines(int fd) { - struct winsize ws = {}; +int dirent_ensure_type(DIR *d, struct dirent *de) { + struct stat st; + + assert(d); + assert(de); + + if (de->d_type != DT_UNKNOWN) + return 0; - if (ioctl(fd, TIOCGWINSZ, &ws) < 0) + if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) return -errno; - if (ws.ws_row <= 0) - return -EIO; + de->d_type = + S_ISREG(st.st_mode) ? DT_REG : + S_ISDIR(st.st_mode) ? DT_DIR : + S_ISLNK(st.st_mode) ? DT_LNK : + S_ISFIFO(st.st_mode) ? DT_FIFO : + S_ISSOCK(st.st_mode) ? DT_SOCK : + S_ISCHR(st.st_mode) ? DT_CHR : + S_ISBLK(st.st_mode) ? DT_BLK : + DT_UNKNOWN; - return ws.ws_row; + return 0; } -unsigned lines(void) { - const char *e; - unsigned l; - - if (_likely_(cached_lines > 0)) - return cached_lines; +int get_files_in_directory(const char *path, char ***list) { + _cleanup_closedir_ DIR *d = NULL; + size_t bufsize = 0, n = 0; + _cleanup_strv_free_ char **l = NULL; - l = 0; - e = getenv("LINES"); - if (e) - (void) safe_atou(e, &l); + assert(path); - if (l <= 0) - l = fd_lines(STDOUT_FILENO); + /* Returns all files in a directory in *list, and the number + * of files as return value. If list is NULL returns only the + * number. */ - if (l <= 0) - l = 24; + d = opendir(path); + if (!d) + return -errno; - cached_lines = l; - return cached_lines; -} + for (;;) { + struct dirent *de; -/* intended to be used as a SIGWINCH sighandler */ -void columns_lines_cache_reset(int signum) { - cached_columns = 0; - cached_lines = 0; -} + errno = 0; + de = readdir(d); + if (!de && errno != 0) + return -errno; + if (!de) + break; -bool on_tty(void) { - static int cached_on_tty = -1; + dirent_ensure_type(d, de); - if (_unlikely_(cached_on_tty < 0)) - cached_on_tty = isatty(STDOUT_FILENO) > 0; + if (!dirent_is_file(de)) + continue; - return cached_on_tty; -} + if (list) { + /* one extra slot is needed for the terminating NULL */ + if (!GREEDY_REALLOC(l, bufsize, n + 2)) + return -ENOMEM; -int files_same(const char *filea, const char *fileb) { - struct stat a, b; + l[n] = strdup(de->d_name); + if (!l[n]) + return -ENOMEM; - if (stat(filea, &a) < 0) - return -errno; + l[++n] = NULL; + } else + n++; + } - if (stat(fileb, &b) < 0) - return -errno; + if (list) { + *list = l; + l = NULL; /* avoid freeing */ + } - return a.st_dev == b.st_dev && - a.st_ino == b.st_ino; + return n; } -int running_in_chroot(void) { - int ret; - - ret = files_same("/proc/1/root", "/"); - if (ret < 0) - return ret; - - return ret == 0; -} - -static char *ascii_ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) { - size_t x; - char *r; - - assert(s); - assert(percent <= 100); - assert(new_length >= 3); - - if (old_length <= 3 || old_length <= new_length) - return strndup(s, old_length); - - r = new0(char, new_length+1); - if (!r) - return NULL; - - x = (new_length * percent) / 100; - - if (x > new_length - 3) - x = new_length - 3; - - memcpy(r, s, x); - r[x] = '.'; - r[x+1] = '.'; - r[x+2] = '.'; - memcpy(r + x + 3, - s + old_length - (new_length - x - 3), - new_length - x - 3); - - return r; -} - -char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) { - size_t x; - char *e; - const char *i, *j; - unsigned k, len, len2; +char *strjoin(const char *x, ...) { + va_list ap; + size_t l; + char *r, *p; - assert(s); - assert(percent <= 100); - assert(new_length >= 3); + va_start(ap, x); - /* if no multibyte characters use ascii_ellipsize_mem for speed */ - if (ascii_is_valid(s)) - return ascii_ellipsize_mem(s, old_length, new_length, percent); + if (x) { + l = strlen(x); - if (old_length <= 3 || old_length <= new_length) - return strndup(s, old_length); + for (;;) { + const char *t; + size_t n; - x = (new_length * percent) / 100; + t = va_arg(ap, const char *); + if (!t) + break; - if (x > new_length - 3) - x = new_length - 3; + n = strlen(t); + if (n > ((size_t) -1) - l) { + va_end(ap); + return NULL; + } - k = 0; - for (i = s; k < x && i < s + old_length; i = utf8_next_char(i)) { - int c; + l += n; + } + } else + l = 0; - c = utf8_encoded_to_unichar(i); - if (c < 0) - return NULL; - k += unichar_iswide(c) ? 2 : 1; - } + va_end(ap); - if (k > x) /* last character was wide and went over quota */ - x ++; + r = new(char, l+1); + if (!r) + return NULL; - for (j = s + old_length; k < new_length && j > i; ) { - int c; + if (x) { + p = stpcpy(r, x); - j = utf8_prev_char(j); - c = utf8_encoded_to_unichar(j); - if (c < 0) - return NULL; - k += unichar_iswide(c) ? 2 : 1; - } - assert(i <= j); + va_start(ap, x); - /* we don't actually need to ellipsize */ - if (i == j) - return memdup(s, old_length + 1); + for (;;) { + const char *t; - /* make space for ellipsis */ - j = utf8_next_char(j); + t = va_arg(ap, const char *); + if (!t) + break; - len = i - s; - len2 = s + old_length - j; - e = new(char, len + 3 + len2 + 1); - if (!e) - return NULL; + p = stpcpy(p, t); + } - /* - printf("old_length=%zu new_length=%zu x=%zu len=%u len2=%u k=%u\n", - old_length, new_length, x, len, len2, k); - */ + va_end(ap); + } else + r[0] = 0; - memcpy(e, s, len); - e[len] = 0xe2; /* tri-dot ellipsis: … */ - e[len + 1] = 0x80; - e[len + 2] = 0xa6; + return r; +} - memcpy(e + len + 3, j, len2 + 1); +bool is_main_thread(void) { + static thread_local int cached = 0; - return e; -} + if (_unlikely_(cached == 0)) + cached = getpid() == gettid() ? 1 : -1; -char *ellipsize(const char *s, size_t length, unsigned percent) { - return ellipsize_mem(s, strlen(s), length, percent); + return cached > 0; } -int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode) { - _cleanup_close_ int fd; +int block_get_whole_disk(dev_t d, dev_t *ret) { + char *p, *s; int r; + unsigned n, m; - assert(path); + assert(ret); - if (parents) - mkdir_parents(path, 0755); + /* If it has a queue this is good enough for us */ + if (asprintf(&p, "/sys/dev/block/%u:%u/queue", major(d), minor(d)) < 0) + return -ENOMEM; - fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, mode > 0 ? mode : 0644); - if (fd < 0) - return -errno; + r = access(p, F_OK); + free(p); - if (mode > 0) { - r = fchmod(fd, mode); - if (r < 0) - return -errno; + if (r >= 0) { + *ret = d; + return 0; } - if (uid != UID_INVALID || gid != GID_INVALID) { - r = fchown(fd, uid, gid); - if (r < 0) - return -errno; - } + /* If it is a partition find the originating device */ + if (asprintf(&p, "/sys/dev/block/%u:%u/partition", major(d), minor(d)) < 0) + return -ENOMEM; - if (stamp != USEC_INFINITY) { - struct timespec ts[2]; + r = access(p, F_OK); + free(p); - timespec_store(&ts[0], stamp); - ts[1] = ts[0]; - r = futimens(fd, ts); - } else - r = futimens(fd, NULL); if (r < 0) - return -errno; - - return 0; -} - -int touch(const char *path) { - return touch_file(path, false, USEC_INFINITY, UID_INVALID, GID_INVALID, 0); -} - -char *unquote(const char *s, const char* quotes) { - size_t l; - assert(s); - - /* This is rather stupid, simply removes the heading and - * trailing quotes if there is one. Doesn't care about - * escaping or anything. We should make this smarter one - * day...*/ + return -ENOENT; - l = strlen(s); - if (l < 2) - return strdup(s); + /* Get parent dev_t */ + if (asprintf(&p, "/sys/dev/block/%u:%u/../dev", major(d), minor(d)) < 0) + return -ENOMEM; - if (strchr(quotes, s[0]) && s[l-1] == s[0]) - return strndup(s+1, l-2); + r = read_one_line_file(p, &s); + free(p); - return strdup(s); -} + if (r < 0) + return r; -char *normalize_env_assignment(const char *s) { - _cleanup_free_ char *value = NULL; - const char *eq; - char *p, *name; + r = sscanf(s, "%u:%u", &m, &n); + free(s); - eq = strchr(s, '='); - if (!eq) { - char *r, *t; + if (r != 2) + return -EINVAL; - r = strdup(s); - if (!r) - return NULL; + /* Only return this if it is really good enough for us. */ + if (asprintf(&p, "/sys/dev/block/%u:%u/queue", m, n) < 0) + return -ENOMEM; - t = strstrip(r); - if (t != r) - memmove(r, t, strlen(t) + 1); + r = access(p, F_OK); + free(p); - return r; + if (r >= 0) { + *ret = makedev(m, n); + return 0; } - name = strndupa(s, eq - s); - p = strdupa(eq + 1); - - value = unquote(strstrip(p), QUOTES); - if (!value) - return NULL; - - return strjoin(strstrip(name), "=", value, NULL); + return -ENOENT; } -int wait_for_terminate(pid_t pid, siginfo_t *status) { - siginfo_t dummy; - - assert(pid >= 1); - - if (!status) - status = &dummy; +static const char *const ioprio_class_table[] = { + [IOPRIO_CLASS_NONE] = "none", + [IOPRIO_CLASS_RT] = "realtime", + [IOPRIO_CLASS_BE] = "best-effort", + [IOPRIO_CLASS_IDLE] = "idle" +}; - for (;;) { - zero(*status); +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ioprio_class, int, INT_MAX); - if (waitid(P_PID, pid, status, WEXITED) < 0) { +static const char *const sigchld_code_table[] = { + [CLD_EXITED] = "exited", + [CLD_KILLED] = "killed", + [CLD_DUMPED] = "dumped", + [CLD_TRAPPED] = "trapped", + [CLD_STOPPED] = "stopped", + [CLD_CONTINUED] = "continued", +}; - if (errno == EINTR) - continue; +DEFINE_STRING_TABLE_LOOKUP(sigchld_code, int); - return -errno; - } - - return 0; - } -} +static const char *const log_facility_unshifted_table[LOG_NFACILITIES] = { + [LOG_FAC(LOG_KERN)] = "kern", + [LOG_FAC(LOG_USER)] = "user", + [LOG_FAC(LOG_MAIL)] = "mail", + [LOG_FAC(LOG_DAEMON)] = "daemon", + [LOG_FAC(LOG_AUTH)] = "auth", + [LOG_FAC(LOG_SYSLOG)] = "syslog", + [LOG_FAC(LOG_LPR)] = "lpr", + [LOG_FAC(LOG_NEWS)] = "news", + [LOG_FAC(LOG_UUCP)] = "uucp", + [LOG_FAC(LOG_CRON)] = "cron", + [LOG_FAC(LOG_AUTHPRIV)] = "authpriv", + [LOG_FAC(LOG_FTP)] = "ftp", + [LOG_FAC(LOG_LOCAL0)] = "local0", + [LOG_FAC(LOG_LOCAL1)] = "local1", + [LOG_FAC(LOG_LOCAL2)] = "local2", + [LOG_FAC(LOG_LOCAL3)] = "local3", + [LOG_FAC(LOG_LOCAL4)] = "local4", + [LOG_FAC(LOG_LOCAL5)] = "local5", + [LOG_FAC(LOG_LOCAL6)] = "local6", + [LOG_FAC(LOG_LOCAL7)] = "local7" +}; -/* - * Return values: - * < 0 : wait_for_terminate() failed to get the state of the - * process, the process was terminated by a signal, or - * failed for an unknown reason. - * >=0 : The process terminated normally, and its exit code is - * returned. - * - * That is, success is indicated by a return value of zero, and an - * error is indicated by a non-zero value. - * - * A warning is emitted if the process terminates abnormally, - * and also if it returns non-zero unless check_exit_code is true. - */ -int wait_for_terminate_and_warn(const char *name, pid_t pid, bool check_exit_code) { - int r; - siginfo_t status; +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(log_facility_unshifted, int, LOG_FAC(~0)); - assert(name); - assert(pid > 1); +static const char *const log_level_table[] = { + [LOG_EMERG] = "emerg", + [LOG_ALERT] = "alert", + [LOG_CRIT] = "crit", + [LOG_ERR] = "err", + [LOG_WARNING] = "warning", + [LOG_NOTICE] = "notice", + [LOG_INFO] = "info", + [LOG_DEBUG] = "debug" +}; - r = wait_for_terminate(pid, &status); - if (r < 0) - return log_warning_errno(r, "Failed to wait for %s: %m", name); +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(log_level, int, LOG_DEBUG); - if (status.si_code == CLD_EXITED) { - if (status.si_status != 0) - log_full(check_exit_code ? LOG_WARNING : LOG_DEBUG, - "%s failed with error code %i.", name, status.si_status); - else - log_debug("%s succeeded.", name); +static const char* const sched_policy_table[] = { + [SCHED_OTHER] = "other", + [SCHED_BATCH] = "batch", + [SCHED_IDLE] = "idle", + [SCHED_FIFO] = "fifo", + [SCHED_RR] = "rr" +}; - return status.si_status; - } else if (status.si_code == CLD_KILLED || - status.si_code == CLD_DUMPED) { +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(sched_policy, int, INT_MAX); - log_warning("%s terminated by signal %s.", name, signal_to_string(status.si_status)); - return -EPROTO; - } +static const char* const rlimit_table[_RLIMIT_MAX] = { + [RLIMIT_CPU] = "LimitCPU", + [RLIMIT_FSIZE] = "LimitFSIZE", + [RLIMIT_DATA] = "LimitDATA", + [RLIMIT_STACK] = "LimitSTACK", + [RLIMIT_CORE] = "LimitCORE", + [RLIMIT_RSS] = "LimitRSS", + [RLIMIT_NOFILE] = "LimitNOFILE", + [RLIMIT_AS] = "LimitAS", + [RLIMIT_NPROC] = "LimitNPROC", + [RLIMIT_MEMLOCK] = "LimitMEMLOCK", + [RLIMIT_LOCKS] = "LimitLOCKS", + [RLIMIT_SIGPENDING] = "LimitSIGPENDING", + [RLIMIT_MSGQUEUE] = "LimitMSGQUEUE", + [RLIMIT_NICE] = "LimitNICE", + [RLIMIT_RTPRIO] = "LimitRTPRIO", + [RLIMIT_RTTIME] = "LimitRTTIME" +}; - log_warning("%s failed due to unknown reason.", name); - return -EPROTO; -} +DEFINE_STRING_TABLE_LOOKUP(rlimit, int); -noreturn void freeze(void) { +static const char* const ip_tos_table[] = { + [IPTOS_LOWDELAY] = "low-delay", + [IPTOS_THROUGHPUT] = "throughput", + [IPTOS_RELIABILITY] = "reliability", + [IPTOS_LOWCOST] = "low-cost", +}; - /* Make sure nobody waits for us on a socket anymore */ - close_all_fds(NULL, 0); +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ip_tos, int, 0xff); - sync(); +static const char *const __signal_table[] = { + [SIGHUP] = "HUP", + [SIGINT] = "INT", + [SIGQUIT] = "QUIT", + [SIGILL] = "ILL", + [SIGTRAP] = "TRAP", + [SIGABRT] = "ABRT", + [SIGBUS] = "BUS", + [SIGFPE] = "FPE", + [SIGKILL] = "KILL", + [SIGUSR1] = "USR1", + [SIGSEGV] = "SEGV", + [SIGUSR2] = "USR2", + [SIGPIPE] = "PIPE", + [SIGALRM] = "ALRM", + [SIGTERM] = "TERM", +#ifdef SIGSTKFLT + [SIGSTKFLT] = "STKFLT", /* Linux on SPARC doesn't know SIGSTKFLT */ +#endif + [SIGCHLD] = "CHLD", + [SIGCONT] = "CONT", + [SIGSTOP] = "STOP", + [SIGTSTP] = "TSTP", + [SIGTTIN] = "TTIN", + [SIGTTOU] = "TTOU", + [SIGURG] = "URG", + [SIGXCPU] = "XCPU", + [SIGXFSZ] = "XFSZ", + [SIGVTALRM] = "VTALRM", + [SIGPROF] = "PROF", + [SIGWINCH] = "WINCH", + [SIGIO] = "IO", + [SIGPWR] = "PWR", + [SIGSYS] = "SYS" +}; - for (;;) - pause(); -} +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(__signal, int); -bool null_or_empty(struct stat *st) { - assert(st); +const char *signal_to_string(int signo) { + static thread_local char buf[sizeof("RTMIN+")-1 + DECIMAL_STR_MAX(int) + 1]; + const char *name; - if (S_ISREG(st->st_mode) && st->st_size <= 0) - return true; + name = __signal_to_string(signo); + if (name) + return name; - if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)) - return true; + if (signo >= SIGRTMIN && signo <= SIGRTMAX) + snprintf(buf, sizeof(buf), "RTMIN+%d", signo - SIGRTMIN); + else + snprintf(buf, sizeof(buf), "%d", signo); - return false; + return buf; } -int null_or_empty_path(const char *fn) { - struct stat st; +int signal_from_string(const char *s) { + int signo; + int offset = 0; + unsigned u; - assert(fn); + signo = __signal_from_string(s); + if (signo > 0) + return signo; - if (stat(fn, &st) < 0) - return -errno; + if (startswith(s, "RTMIN+")) { + s += 6; + offset = SIGRTMIN; + } + if (safe_atou(s, &u) >= 0) { + signo = (int) u + offset; + if (signo > 0 && signo < _NSIG) + return signo; + } + return -EINVAL; +} - return null_or_empty(&st); +bool kexec_loaded(void) { + bool loaded = false; + char *s; + + if (read_one_line_file("/sys/kernel/kexec_loaded", &s) >= 0) { + if (s[0] == '1') + loaded = true; + free(s); + } + return loaded; } -int null_or_empty_fd(int fd) { - struct stat st; +int prot_from_flags(int flags) { - assert(fd >= 0); + switch (flags & O_ACCMODE) { - if (fstat(fd, &st) < 0) - return -errno; + case O_RDONLY: + return PROT_READ; - return null_or_empty(&st); + case O_WRONLY: + return PROT_WRITE; + + case O_RDWR: + return PROT_READ|PROT_WRITE; + + default: + return -EINVAL; + } } -DIR *xopendirat(int fd, const char *name, int flags) { - int nfd; - DIR *d; +char *format_bytes(char *buf, size_t l, off_t t) { + unsigned i; - assert(!(flags & O_CREAT)); + static const struct { + const char *suffix; + off_t factor; + } table[] = { + { "E", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, + { "P", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, + { "T", 1024ULL*1024ULL*1024ULL*1024ULL }, + { "G", 1024ULL*1024ULL*1024ULL }, + { "M", 1024ULL*1024ULL }, + { "K", 1024ULL }, + }; - nfd = openat(fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|flags, 0); - if (nfd < 0) + if (t == (off_t) -1) return NULL; - d = fdopendir(nfd); - if (!d) { - safe_close(nfd); - return NULL; - } + for (i = 0; i < ELEMENTSOF(table); i++) { - return d; -} + if (t >= table[i].factor) { + snprintf(buf, l, + "%llu.%llu%s", + (unsigned long long) (t / table[i].factor), + (unsigned long long) (((t*10ULL) / table[i].factor) % 10ULL), + table[i].suffix); -int signal_from_string_try_harder(const char *s) { - int signo; - assert(s); + goto finish; + } + } - signo = signal_from_string(s); - if (signo <= 0) - if (startswith(s, "SIG")) - return signal_from_string(s+3); + snprintf(buf, l, "%lluB", (unsigned long long) t); + +finish: + buf[l-1] = 0; + return buf; - return signo; } -static char *tag_to_udev_node(const char *tagvalue, const char *by) { - _cleanup_free_ char *t = NULL, *u = NULL; - size_t enc_len; +void* memdup(const void *p, size_t l) { + void *r; - u = unquote(tagvalue, "\"\'"); - if (!u) - return NULL; + assert(p); - enc_len = strlen(u) * 4 + 1; - t = new(char, enc_len); - if (!t) + r = malloc(l); + if (!r) return NULL; - if (encode_devnode_name(u, t, enc_len) < 0) - return NULL; - - return strjoin("/dev/disk/by-", by, "/", t, NULL); + memcpy(r, p, l); + return r; } -char *fstab_node_to_udev_node(const char *p) { - assert(p); - - if (startswith(p, "LABEL=")) - return tag_to_udev_node(p+6, "label"); +int fd_inc_sndbuf(int fd, size_t n) { + int r, value; + socklen_t l = sizeof(value); - if (startswith(p, "UUID=")) - return tag_to_udev_node(p+5, "uuid"); + r = getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, &l); + if (r >= 0 && l == sizeof(value) && (size_t) value >= n*2) + return 0; - if (startswith(p, "PARTUUID=")) - return tag_to_udev_node(p+9, "partuuid"); + /* If we have the privileges we will ignore the kernel limit. */ - if (startswith(p, "PARTLABEL=")) - return tag_to_udev_node(p+10, "partlabel"); + value = (int) n; + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUFFORCE, &value, sizeof(value)) < 0) + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, sizeof(value)) < 0) + return -errno; - return strdup(p); + return 1; } -bool tty_is_vc(const char *tty) { - assert(tty); - - return vtnr_from_tty(tty) >= 0; -} +int fd_inc_rcvbuf(int fd, size_t n) { + int r, value; + socklen_t l = sizeof(value); -bool tty_is_console(const char *tty) { - assert(tty); + r = getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, &l); + if (r >= 0 && l == sizeof(value) && (size_t) value >= n*2) + return 0; - if (startswith(tty, "/dev/")) - tty += 5; + /* If we have the privileges we will ignore the kernel limit. */ - return streq(tty, "console"); + value = (int) n; + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, &value, sizeof(value)) < 0) + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, sizeof(value)) < 0) + return -errno; + return 1; } -int vtnr_from_tty(const char *tty) { - int i, r; - - assert(tty); +int fork_agent(pid_t *pid, const int except[], unsigned n_except, const char *path, ...) { + bool stdout_is_tty, stderr_is_tty; + pid_t parent_pid, agent_pid; + sigset_t ss, saved_ss; + unsigned n, i; + va_list ap; + char **l; - if (startswith(tty, "/dev/")) - tty += 5; + assert(pid); + assert(path); - if (!startswith(tty, "tty") ) - return -EINVAL; + /* Spawns a temporary TTY agent, making sure it goes away when + * we go away */ - if (tty[3] < '0' || tty[3] > '9') - return -EINVAL; + parent_pid = getpid(); - r = safe_atoi(tty+3, &i); - if (r < 0) - return r; + /* First we temporarily block all signals, so that the new + * child has them blocked initially. This way, we can be sure + * that SIGTERMs are not lost we might send to the agent. */ + assert_se(sigfillset(&ss) >= 0); + assert_se(sigprocmask(SIG_SETMASK, &ss, &saved_ss) >= 0); - if (i < 0 || i > 63) - return -EINVAL; + agent_pid = fork(); + if (agent_pid < 0) { + assert_se(sigprocmask(SIG_SETMASK, &saved_ss, NULL) >= 0); + return -errno; + } - return i; -} + if (agent_pid != 0) { + assert_se(sigprocmask(SIG_SETMASK, &saved_ss, NULL) >= 0); + *pid = agent_pid; + return 0; + } -char *resolve_dev_console(char **active) { - char *tty; + /* In the child: + * + * Make sure the agent goes away when the parent dies */ + if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) + _exit(EXIT_FAILURE); - /* Resolve where /dev/console is pointing to, if /sys is actually ours - * (i.e. not read-only-mounted which is a sign for container setups) */ + /* Make sure we actually can kill the agent, if we need to, in + * case somebody invoked us from a shell script that trapped + * SIGTERM or so... */ + reset_all_signal_handlers(); + reset_signal_mask(); - if (path_is_read_only_fs("/sys") > 0) - return NULL; + /* Check whether our parent died before we were able + * to set the death signal and unblock the signals */ + if (getppid() != parent_pid) + _exit(EXIT_SUCCESS); - if (read_one_line_file("/sys/class/tty/console/active", active) < 0) - return NULL; + /* Don't leak fds to the agent */ + close_all_fds(except, n_except); - /* If multiple log outputs are configured the last one is what - * /dev/console points to */ - tty = strrchr(*active, ' '); - if (tty) - tty++; - else - tty = *active; + stdout_is_tty = isatty(STDOUT_FILENO); + stderr_is_tty = isatty(STDERR_FILENO); - if (streq(tty, "tty0")) { - char *tmp; + if (!stdout_is_tty || !stderr_is_tty) { + int fd; - /* Get the active VC (e.g. tty1) */ - if (read_one_line_file("/sys/class/tty/tty0/active", &tmp) >= 0) { - free(*active); - tty = *active = tmp; + /* Detach from stdout/stderr. and reopen + * /dev/tty for them. This is important to + * ensure that when systemctl is started via + * popen() or a similar call that expects to + * read EOF we actually do generate EOF and + * not delay this indefinitely by because we + * keep an unused copy of stdin around. */ + fd = open("/dev/tty", O_WRONLY); + if (fd < 0) { + log_error_errno(errno, "Failed to open /dev/tty: %m"); + _exit(EXIT_FAILURE); } - } - return tty; -} - -bool tty_is_vc_resolve(const char *tty) { - _cleanup_free_ char *active = NULL; - - assert(tty); + if (!stdout_is_tty) + dup2(fd, STDOUT_FILENO); - if (startswith(tty, "/dev/")) - tty += 5; + if (!stderr_is_tty) + dup2(fd, STDERR_FILENO); - if (streq(tty, "console")) { - tty = resolve_dev_console(&active); - if (!tty) - return false; + if (fd > 2) + close(fd); } - return tty_is_vc(tty); -} - -const char *default_term_for_tty(const char *tty) { - assert(tty); - - return tty_is_vc_resolve(tty) ? "TERM=linux" : "TERM=vt102"; -} - -bool dirent_is_file(const struct dirent *de) { - assert(de); - - if (ignore_file(de->d_name)) - return false; - - if (de->d_type != DT_REG && - de->d_type != DT_LNK && - de->d_type != DT_UNKNOWN) - return false; - - return true; -} - -bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) { - assert(de); + /* Count arguments */ + va_start(ap, path); + for (n = 0; va_arg(ap, char*); n++) + ; + va_end(ap); - if (de->d_type != DT_REG && - de->d_type != DT_LNK && - de->d_type != DT_UNKNOWN) - return false; + /* Allocate strv */ + l = alloca(sizeof(char *) * (n + 1)); - if (ignore_file_allow_backup(de->d_name)) - return false; + /* Fill in arguments */ + va_start(ap, path); + for (i = 0; i <= n; i++) + l[i] = va_arg(ap, char*); + va_end(ap); - return endswith(de->d_name, suffix); + execv(path, l); + _exit(EXIT_FAILURE); } -void execute_directory(const char *directory, DIR *d, usec_t timeout, char *argv[]) { - pid_t executor_pid; - int r; - - assert(directory); - - /* Executes all binaries in a directory in parallel and waits - * for them to finish. Optionally a timeout is applied. */ - - executor_pid = fork(); - if (executor_pid < 0) { - log_error_errno(errno, "Failed to fork: %m"); - return; - - } else if (executor_pid == 0) { - _cleanup_hashmap_free_free_ Hashmap *pids = NULL; - _cleanup_closedir_ DIR *_d = NULL; - struct dirent *de; - - /* We fork this all off from a child process so that - * we can somewhat cleanly make use of SIGALRM to set - * a time limit */ - - reset_all_signal_handlers(); - reset_signal_mask(); - - assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); - - if (!d) { - d = _d = opendir(directory); - if (!d) { - if (errno == ENOENT) - _exit(EXIT_SUCCESS); - - log_error_errno(errno, "Failed to enumerate directory %s: %m", directory); - _exit(EXIT_FAILURE); - } - } - - pids = hashmap_new(NULL); - if (!pids) { - log_oom(); - _exit(EXIT_FAILURE); - } - - FOREACH_DIRENT(de, d, break) { - _cleanup_free_ char *path = NULL; - pid_t pid; - - if (!dirent_is_file(de)) - continue; +int setrlimit_closest(int resource, const struct rlimit *rlim) { + struct rlimit highest, fixed; - path = strjoin(directory, "/", de->d_name, NULL); - if (!path) { - log_oom(); - _exit(EXIT_FAILURE); - } + assert(rlim); - pid = fork(); - if (pid < 0) { - log_error_errno(errno, "Failed to fork: %m"); - continue; - } else if (pid == 0) { - char *_argv[2]; + if (setrlimit(resource, rlim) >= 0) + return 0; - assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); + if (errno != EPERM) + return -errno; - if (!argv) { - _argv[0] = path; - _argv[1] = NULL; - argv = _argv; - } else - argv[0] = path; + /* So we failed to set the desired setrlimit, then let's try + * to get as close as we can */ + assert_se(getrlimit(resource, &highest) == 0); - execv(path, argv); - log_error_errno(errno, "Failed to execute %s: %m", path); - _exit(EXIT_FAILURE); - } + fixed.rlim_cur = MIN(rlim->rlim_cur, highest.rlim_max); + fixed.rlim_max = MIN(rlim->rlim_max, highest.rlim_max); - log_debug("Spawned %s as " PID_FMT ".", path, pid); + if (setrlimit(resource, &fixed) < 0) + return -errno; - r = hashmap_put(pids, UINT_TO_PTR(pid), path); - if (r < 0) { - log_oom(); - _exit(EXIT_FAILURE); - } + return 0; +} - path = NULL; - } +bool http_etag_is_valid(const char *etag) { + if (isempty(etag)) + return false; - /* Abort execution of this process after the - * timout. We simply rely on SIGALRM as default action - * terminating the process, and turn on alarm(). */ + if (!endswith(etag, "\"")) + return false; - if (timeout != USEC_INFINITY) - alarm((timeout + USEC_PER_SEC - 1) / USEC_PER_SEC); + if (!startswith(etag, "\"") && !startswith(etag, "W/\"")) + return false; - while (!hashmap_isempty(pids)) { - _cleanup_free_ char *path = NULL; - pid_t pid; + return true; +} - pid = PTR_TO_UINT(hashmap_first_key(pids)); - assert(pid > 0); +bool http_url_is_valid(const char *url) { + const char *p; - path = hashmap_remove(pids, UINT_TO_PTR(pid)); - assert(path); + if (isempty(url)) + return false; - wait_for_terminate_and_warn(path, pid, true); - } + p = startswith(url, "http://"); + if (!p) + p = startswith(url, "https://"); + if (!p) + return false; - _exit(EXIT_SUCCESS); - } + if (isempty(p)) + return false; - wait_for_terminate_and_warn(directory, executor_pid, true); + return ascii_is_valid(p); } -int kill_and_sigcont(pid_t pid, int sig) { - int r; - - r = kill(pid, sig) < 0 ? -errno : 0; - - if (r >= 0) - kill(pid, SIGCONT); - - return r; -} +bool documentation_url_is_valid(const char *url) { + const char *p; -bool nulstr_contains(const char*nulstr, const char *needle) { - const char *i; + if (isempty(url)) + return false; - if (!nulstr) - return false; - - NULSTR_FOREACH(i, nulstr) - if (streq(i, needle)) - return true; - - return false; -} - -bool plymouth_running(void) { - return access("/run/plymouth/pid", F_OK) >= 0; -} - -char* strshorten(char *s, size_t l) { - assert(s); - - if (l < strlen(s)) - s[l] = 0; - - return s; -} - -static bool hostname_valid_char(char c) { - return - (c >= 'a' && c <= 'z') || - (c >= 'A' && c <= 'Z') || - (c >= '0' && c <= '9') || - c == '-' || - c == '_' || - c == '.'; -} - -bool hostname_is_valid(const char *s) { - const char *p; - bool dot; - - if (isempty(s)) - return false; - - for (p = s, dot = true; *p; p++) { - if (*p == '.') { - if (dot) - return false; - - dot = true; - } else { - if (!hostname_valid_char(*p)) - return false; - - dot = false; - } - } - - if (dot) - return false; - - if (p-s > HOST_NAME_MAX) - return false; - - return true; -} - -char* hostname_cleanup(char *s, bool lowercase) { - char *p, *d; - bool dot; - - for (p = s, d = s, dot = true; *p; p++) { - if (*p == '.') { - if (dot) - continue; - - *(d++) = '.'; - dot = true; - } else if (hostname_valid_char(*p)) { - *(d++) = lowercase ? tolower(*p) : *p; - dot = false; - } - - } - - if (dot && d > s) - d[-1] = 0; - else - *d = 0; - - strshorten(s, HOST_NAME_MAX); - - return s; -} - -bool machine_name_is_valid(const char *s) { - - if (!hostname_is_valid(s)) - return false; - - /* Machine names should be useful hostnames, but also be - * useful in unit names, hence we enforce a stricter length - * limitation. */ - - if (strlen(s) > 64) - return false; - - return true; -} - -int pipe_eof(int fd) { - struct pollfd pollfd = { - .fd = fd, - .events = POLLIN|POLLHUP, - }; - - int r; - - r = poll(&pollfd, 1, 0); - if (r < 0) - return -errno; - - if (r == 0) - return 0; - - return pollfd.revents & POLLHUP; -} - -int fd_wait_for_event(int fd, int event, usec_t t) { - - struct pollfd pollfd = { - .fd = fd, - .events = event, - }; - - struct timespec ts; - int r; - - r = ppoll(&pollfd, 1, t == USEC_INFINITY ? NULL : timespec_store(&ts, t), NULL); - if (r < 0) - return -errno; - - if (r == 0) - return 0; - - return pollfd.revents; -} - -int fopen_temporary(const char *path, FILE **_f, char **_temp_path) { - FILE *f; - char *t; - int fd; - - assert(path); - assert(_f); - assert(_temp_path); - - t = tempfn_xxxxxx(path); - if (!t) - return -ENOMEM; - - fd = mkostemp_safe(t, O_WRONLY|O_CLOEXEC); - if (fd < 0) { - free(t); - return -errno; - } - - f = fdopen(fd, "we"); - if (!f) { - unlink(t); - free(t); - return -errno; - } - - *_f = f; - *_temp_path = t; - - return 0; -} - -int terminal_vhangup_fd(int fd) { - assert(fd >= 0); - - if (ioctl(fd, TIOCVHANGUP) < 0) - return -errno; - - return 0; -} - -int terminal_vhangup(const char *name) { - _cleanup_close_ int fd; - - fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return fd; - - return terminal_vhangup_fd(fd); -} - -int vt_disallocate(const char *name) { - int fd, r; - unsigned u; - - /* Deallocate the VT if possible. If not possible - * (i.e. because it is the active one), at least clear it - * entirely (including the scrollback buffer) */ - - if (!startswith(name, "/dev/")) - return -EINVAL; - - if (!tty_is_vc(name)) { - /* So this is not a VT. I guess we cannot deallocate - * it then. But let's at least clear the screen */ - - fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return fd; - - loop_write(fd, - "\033[r" /* clear scrolling region */ - "\033[H" /* move home */ - "\033[2J", /* clear screen */ - 10, false); - safe_close(fd); - - return 0; - } - - if (!startswith(name, "/dev/tty")) - return -EINVAL; - - r = safe_atou(name+8, &u); - if (r < 0) - return r; - - if (u <= 0) - return -EINVAL; - - /* Try to deallocate */ - fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return fd; - - r = ioctl(fd, VT_DISALLOCATE, u); - safe_close(fd); - - if (r >= 0) - return 0; - - if (errno != EBUSY) - return -errno; - - /* Couldn't deallocate, so let's clear it fully with - * scrollback */ - fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return fd; - - loop_write(fd, - "\033[r" /* clear scrolling region */ - "\033[H" /* move home */ - "\033[3J", /* clear screen including scrollback, requires Linux 2.6.40 */ - 10, false); - safe_close(fd); - - return 0; -} - -int symlink_atomic(const char *from, const char *to) { - _cleanup_free_ char *t = NULL; - - assert(from); - assert(to); - - t = tempfn_random(to); - if (!t) - return -ENOMEM; - - if (symlink(from, t) < 0) - return -errno; - - if (rename(t, to) < 0) { - unlink_noerrno(t); - return -errno; - } - - return 0; -} - -int mknod_atomic(const char *path, mode_t mode, dev_t dev) { - _cleanup_free_ char *t = NULL; - - assert(path); - - t = tempfn_random(path); - if (!t) - return -ENOMEM; - - if (mknod(t, mode, dev) < 0) - return -errno; - - if (rename(t, path) < 0) { - unlink_noerrno(t); - return -errno; - } - - return 0; -} - -int mkfifo_atomic(const char *path, mode_t mode) { - _cleanup_free_ char *t = NULL; - - assert(path); - - t = tempfn_random(path); - if (!t) - return -ENOMEM; - - if (mkfifo(t, mode) < 0) - return -errno; - - if (rename(t, path) < 0) { - unlink_noerrno(t); - return -errno; - } - - return 0; -} - -bool display_is_local(const char *display) { - assert(display); - - return - display[0] == ':' && - display[1] >= '0' && - display[1] <= '9'; -} - -int socket_from_display(const char *display, char **path) { - size_t k; - char *f, *c; - - assert(display); - assert(path); - - if (!display_is_local(display)) - return -EINVAL; - - k = strspn(display+1, "0123456789"); - - f = new(char, strlen("/tmp/.X11-unix/X") + k + 1); - if (!f) - return -ENOMEM; - - c = stpcpy(f, "/tmp/.X11-unix/X"); - memcpy(c, display+1, k); - c[k] = 0; - - *path = f; - - return 0; -} - -int get_user_creds( - const char **username, - uid_t *uid, gid_t *gid, - const char **home, - const char **shell) { - - struct passwd *p; - uid_t u; - - assert(username); - assert(*username); - - /* We enforce some special rules for uid=0: in order to avoid - * NSS lookups for root we hardcode its data. */ - - if (streq(*username, "root") || streq(*username, "0")) { - *username = "root"; - - if (uid) - *uid = 0; - - if (gid) - *gid = 0; - - if (home) - *home = "/root"; - - if (shell) - *shell = "/bin/sh"; - - return 0; - } - - if (parse_uid(*username, &u) >= 0) { - errno = 0; - p = getpwuid(u); - - /* If there are multiple users with the same id, make - * sure to leave $USER to the configured value instead - * of the first occurrence in the database. However if - * the uid was configured by a numeric uid, then let's - * pick the real username from /etc/passwd. */ - if (p) - *username = p->pw_name; - } else { - errno = 0; - p = getpwnam(*username); - } - - if (!p) - return errno > 0 ? -errno : -ESRCH; - - if (uid) - *uid = p->pw_uid; - - if (gid) - *gid = p->pw_gid; - - if (home) - *home = p->pw_dir; - - if (shell) - *shell = p->pw_shell; - - return 0; -} - -char* uid_to_name(uid_t uid) { - struct passwd *p; - char *r; - - if (uid == 0) - return strdup("root"); - - p = getpwuid(uid); - if (p) - return strdup(p->pw_name); - - if (asprintf(&r, UID_FMT, uid) < 0) - return NULL; - - return r; -} - -char* gid_to_name(gid_t gid) { - struct group *p; - char *r; - - if (gid == 0) - return strdup("root"); - - p = getgrgid(gid); - if (p) - return strdup(p->gr_name); - - if (asprintf(&r, GID_FMT, gid) < 0) - return NULL; - - return r; -} - -int get_group_creds(const char **groupname, gid_t *gid) { - struct group *g; - gid_t id; - - assert(groupname); - - /* We enforce some special rules for gid=0: in order to avoid - * NSS lookups for root we hardcode its data. */ - - if (streq(*groupname, "root") || streq(*groupname, "0")) { - *groupname = "root"; - - if (gid) - *gid = 0; - - return 0; - } - - if (parse_gid(*groupname, &id) >= 0) { - errno = 0; - g = getgrgid(id); - - if (g) - *groupname = g->gr_name; - } else { - errno = 0; - g = getgrnam(*groupname); - } - - if (!g) - return errno > 0 ? -errno : -ESRCH; - - if (gid) - *gid = g->gr_gid; - - return 0; -} - -int in_gid(gid_t gid) { - gid_t *gids; - int ngroups_max, r, i; - - if (getgid() == gid) - return 1; - - if (getegid() == gid) - return 1; - - ngroups_max = sysconf(_SC_NGROUPS_MAX); - assert(ngroups_max > 0); - - gids = alloca(sizeof(gid_t) * ngroups_max); - - r = getgroups(ngroups_max, gids); - if (r < 0) - return -errno; - - for (i = 0; i < r; i++) - if (gids[i] == gid) - return 1; - - return 0; -} - -int in_group(const char *name) { - int r; - gid_t gid; - - r = get_group_creds(&name, &gid); - if (r < 0) - return r; - - return in_gid(gid); -} - -int glob_exists(const char *path) { - _cleanup_globfree_ glob_t g = {}; - int k; - - assert(path); - - errno = 0; - k = glob(path, GLOB_NOSORT|GLOB_BRACE, NULL, &g); - - if (k == GLOB_NOMATCH) - return 0; - else if (k == GLOB_NOSPACE) - return -ENOMEM; - else if (k == 0) - return !strv_isempty(g.gl_pathv); - else - return errno ? -errno : -EIO; -} - -int glob_extend(char ***strv, const char *path) { - _cleanup_globfree_ glob_t g = {}; - int k; - char **p; - - errno = 0; - k = glob(path, GLOB_NOSORT|GLOB_BRACE, NULL, &g); - - if (k == GLOB_NOMATCH) - return -ENOENT; - else if (k == GLOB_NOSPACE) - return -ENOMEM; - else if (k != 0 || strv_isempty(g.gl_pathv)) - return errno ? -errno : -EIO; - - STRV_FOREACH(p, g.gl_pathv) { - k = strv_extend(strv, *p); - if (k < 0) - break; - } - - return k; -} - -int dirent_ensure_type(DIR *d, struct dirent *de) { - struct stat st; - - assert(d); - assert(de); - - if (de->d_type != DT_UNKNOWN) - return 0; - - if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) - return -errno; - - de->d_type = - S_ISREG(st.st_mode) ? DT_REG : - S_ISDIR(st.st_mode) ? DT_DIR : - S_ISLNK(st.st_mode) ? DT_LNK : - S_ISFIFO(st.st_mode) ? DT_FIFO : - S_ISSOCK(st.st_mode) ? DT_SOCK : - S_ISCHR(st.st_mode) ? DT_CHR : - S_ISBLK(st.st_mode) ? DT_BLK : - DT_UNKNOWN; - - return 0; -} - -int get_files_in_directory(const char *path, char ***list) { - _cleanup_closedir_ DIR *d = NULL; - size_t bufsize = 0, n = 0; - _cleanup_strv_free_ char **l = NULL; - - assert(path); - - /* Returns all files in a directory in *list, and the number - * of files as return value. If list is NULL returns only the - * number. */ - - d = opendir(path); - if (!d) - return -errno; - - for (;;) { - struct dirent *de; - - errno = 0; - de = readdir(d); - if (!de && errno != 0) - return -errno; - if (!de) - break; - - dirent_ensure_type(d, de); - - if (!dirent_is_file(de)) - continue; - - if (list) { - /* one extra slot is needed for the terminating NULL */ - if (!GREEDY_REALLOC(l, bufsize, n + 2)) - return -ENOMEM; - - l[n] = strdup(de->d_name); - if (!l[n]) - return -ENOMEM; - - l[++n] = NULL; - } else - n++; - } - - if (list) { - *list = l; - l = NULL; /* avoid freeing */ - } - - return n; -} - -char *strjoin(const char *x, ...) { - va_list ap; - size_t l; - char *r, *p; - - va_start(ap, x); - - if (x) { - l = strlen(x); - - for (;;) { - const char *t; - size_t n; - - t = va_arg(ap, const char *); - if (!t) - break; - - n = strlen(t); - if (n > ((size_t) -1) - l) { - va_end(ap); - return NULL; - } - - l += n; - } - } else - l = 0; - - va_end(ap); - - r = new(char, l+1); - if (!r) - return NULL; - - if (x) { - p = stpcpy(r, x); - - va_start(ap, x); - - for (;;) { - const char *t; - - t = va_arg(ap, const char *); - if (!t) - break; - - p = stpcpy(p, t); - } - - va_end(ap); - } else - r[0] = 0; - - return r; -} - -bool is_main_thread(void) { - static thread_local int cached = 0; - - if (_unlikely_(cached == 0)) - cached = getpid() == gettid() ? 1 : -1; - - return cached > 0; -} - -int block_get_whole_disk(dev_t d, dev_t *ret) { - char *p, *s; - int r; - unsigned n, m; - - assert(ret); - - /* If it has a queue this is good enough for us */ - if (asprintf(&p, "/sys/dev/block/%u:%u/queue", major(d), minor(d)) < 0) - return -ENOMEM; - - r = access(p, F_OK); - free(p); - - if (r >= 0) { - *ret = d; - return 0; - } - - /* If it is a partition find the originating device */ - if (asprintf(&p, "/sys/dev/block/%u:%u/partition", major(d), minor(d)) < 0) - return -ENOMEM; - - r = access(p, F_OK); - free(p); - - if (r < 0) - return -ENOENT; - - /* Get parent dev_t */ - if (asprintf(&p, "/sys/dev/block/%u:%u/../dev", major(d), minor(d)) < 0) - return -ENOMEM; - - r = read_one_line_file(p, &s); - free(p); - - if (r < 0) - return r; - - r = sscanf(s, "%u:%u", &m, &n); - free(s); - - if (r != 2) - return -EINVAL; - - /* Only return this if it is really good enough for us. */ - if (asprintf(&p, "/sys/dev/block/%u:%u/queue", m, n) < 0) - return -ENOMEM; - - r = access(p, F_OK); - free(p); - - if (r >= 0) { - *ret = makedev(m, n); - return 0; - } - - return -ENOENT; -} - -static const char *const ioprio_class_table[] = { - [IOPRIO_CLASS_NONE] = "none", - [IOPRIO_CLASS_RT] = "realtime", - [IOPRIO_CLASS_BE] = "best-effort", - [IOPRIO_CLASS_IDLE] = "idle" -}; - -DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ioprio_class, int, INT_MAX); - -static const char *const sigchld_code_table[] = { - [CLD_EXITED] = "exited", - [CLD_KILLED] = "killed", - [CLD_DUMPED] = "dumped", - [CLD_TRAPPED] = "trapped", - [CLD_STOPPED] = "stopped", - [CLD_CONTINUED] = "continued", -}; - -DEFINE_STRING_TABLE_LOOKUP(sigchld_code, int); - -static const char *const log_facility_unshifted_table[LOG_NFACILITIES] = { - [LOG_FAC(LOG_KERN)] = "kern", - [LOG_FAC(LOG_USER)] = "user", - [LOG_FAC(LOG_MAIL)] = "mail", - [LOG_FAC(LOG_DAEMON)] = "daemon", - [LOG_FAC(LOG_AUTH)] = "auth", - [LOG_FAC(LOG_SYSLOG)] = "syslog", - [LOG_FAC(LOG_LPR)] = "lpr", - [LOG_FAC(LOG_NEWS)] = "news", - [LOG_FAC(LOG_UUCP)] = "uucp", - [LOG_FAC(LOG_CRON)] = "cron", - [LOG_FAC(LOG_AUTHPRIV)] = "authpriv", - [LOG_FAC(LOG_FTP)] = "ftp", - [LOG_FAC(LOG_LOCAL0)] = "local0", - [LOG_FAC(LOG_LOCAL1)] = "local1", - [LOG_FAC(LOG_LOCAL2)] = "local2", - [LOG_FAC(LOG_LOCAL3)] = "local3", - [LOG_FAC(LOG_LOCAL4)] = "local4", - [LOG_FAC(LOG_LOCAL5)] = "local5", - [LOG_FAC(LOG_LOCAL6)] = "local6", - [LOG_FAC(LOG_LOCAL7)] = "local7" -}; - -DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(log_facility_unshifted, int, LOG_FAC(~0)); - -static const char *const log_level_table[] = { - [LOG_EMERG] = "emerg", - [LOG_ALERT] = "alert", - [LOG_CRIT] = "crit", - [LOG_ERR] = "err", - [LOG_WARNING] = "warning", - [LOG_NOTICE] = "notice", - [LOG_INFO] = "info", - [LOG_DEBUG] = "debug" -}; - -DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(log_level, int, LOG_DEBUG); - -static const char* const sched_policy_table[] = { - [SCHED_OTHER] = "other", - [SCHED_BATCH] = "batch", - [SCHED_IDLE] = "idle", - [SCHED_FIFO] = "fifo", - [SCHED_RR] = "rr" -}; - -DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(sched_policy, int, INT_MAX); - -static const char* const rlimit_table[_RLIMIT_MAX] = { - [RLIMIT_CPU] = "LimitCPU", - [RLIMIT_FSIZE] = "LimitFSIZE", - [RLIMIT_DATA] = "LimitDATA", - [RLIMIT_STACK] = "LimitSTACK", - [RLIMIT_CORE] = "LimitCORE", - [RLIMIT_RSS] = "LimitRSS", - [RLIMIT_NOFILE] = "LimitNOFILE", - [RLIMIT_AS] = "LimitAS", - [RLIMIT_NPROC] = "LimitNPROC", - [RLIMIT_MEMLOCK] = "LimitMEMLOCK", - [RLIMIT_LOCKS] = "LimitLOCKS", - [RLIMIT_SIGPENDING] = "LimitSIGPENDING", - [RLIMIT_MSGQUEUE] = "LimitMSGQUEUE", - [RLIMIT_NICE] = "LimitNICE", - [RLIMIT_RTPRIO] = "LimitRTPRIO", - [RLIMIT_RTTIME] = "LimitRTTIME" -}; - -DEFINE_STRING_TABLE_LOOKUP(rlimit, int); - -static const char* const ip_tos_table[] = { - [IPTOS_LOWDELAY] = "low-delay", - [IPTOS_THROUGHPUT] = "throughput", - [IPTOS_RELIABILITY] = "reliability", - [IPTOS_LOWCOST] = "low-cost", -}; - -DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ip_tos, int, 0xff); - -static const char *const __signal_table[] = { - [SIGHUP] = "HUP", - [SIGINT] = "INT", - [SIGQUIT] = "QUIT", - [SIGILL] = "ILL", - [SIGTRAP] = "TRAP", - [SIGABRT] = "ABRT", - [SIGBUS] = "BUS", - [SIGFPE] = "FPE", - [SIGKILL] = "KILL", - [SIGUSR1] = "USR1", - [SIGSEGV] = "SEGV", - [SIGUSR2] = "USR2", - [SIGPIPE] = "PIPE", - [SIGALRM] = "ALRM", - [SIGTERM] = "TERM", -#ifdef SIGSTKFLT - [SIGSTKFLT] = "STKFLT", /* Linux on SPARC doesn't know SIGSTKFLT */ -#endif - [SIGCHLD] = "CHLD", - [SIGCONT] = "CONT", - [SIGSTOP] = "STOP", - [SIGTSTP] = "TSTP", - [SIGTTIN] = "TTIN", - [SIGTTOU] = "TTOU", - [SIGURG] = "URG", - [SIGXCPU] = "XCPU", - [SIGXFSZ] = "XFSZ", - [SIGVTALRM] = "VTALRM", - [SIGPROF] = "PROF", - [SIGWINCH] = "WINCH", - [SIGIO] = "IO", - [SIGPWR] = "PWR", - [SIGSYS] = "SYS" -}; + if (http_url_is_valid(url)) + return true; -DEFINE_PRIVATE_STRING_TABLE_LOOKUP(__signal, int); + p = startswith(url, "file:/"); + if (!p) + p = startswith(url, "info:"); + if (!p) + p = startswith(url, "man:"); -const char *signal_to_string(int signo) { - static thread_local char buf[sizeof("RTMIN+")-1 + DECIMAL_STR_MAX(int) + 1]; - const char *name; + if (isempty(p)) + return false; - name = __signal_to_string(signo); - if (name) - return name; + return ascii_is_valid(p); +} - if (signo >= SIGRTMIN && signo <= SIGRTMAX) - snprintf(buf, sizeof(buf), "RTMIN+%d", signo - SIGRTMIN); - else - snprintf(buf, sizeof(buf), "%d", signo); +bool in_initrd(void) { + static int saved = -1; + struct statfs s; - return buf; + if (saved >= 0) + return saved; + + /* We make two checks here: + * + * 1. the flag file /etc/initrd-release must exist + * 2. the root file system must be a memory file system + * + * The second check is extra paranoia, since misdetecting an + * initrd can have bad bad consequences due the initrd + * emptying when transititioning to the main systemd. + */ + + saved = access("/etc/initrd-release", F_OK) >= 0 && + statfs("/", &s) >= 0 && + is_temporary_fs(&s); + + return saved; } -int signal_from_string(const char *s) { - int signo; - int offset = 0; - unsigned u; +int get_home_dir(char **_h) { + struct passwd *p; + const char *e; + char *h; + uid_t u; - signo = __signal_from_string(s); - if (signo > 0) - return signo; + assert(_h); - if (startswith(s, "RTMIN+")) { - s += 6; - offset = SIGRTMIN; + /* Take the user specified one */ + e = secure_getenv("HOME"); + if (e && path_is_absolute(e)) { + h = strdup(e); + if (!h) + return -ENOMEM; + + *_h = h; + return 0; } - if (safe_atou(s, &u) >= 0) { - signo = (int) u + offset; - if (signo > 0 && signo < _NSIG) - return signo; + + /* Hardcode home directory for root to avoid NSS */ + u = getuid(); + if (u == 0) { + h = strdup("/root"); + if (!h) + return -ENOMEM; + + *_h = h; + return 0; } - return -EINVAL; -} -bool kexec_loaded(void) { - bool loaded = false; - char *s; + /* Check the database... */ + errno = 0; + p = getpwuid(u); + if (!p) + return errno > 0 ? -errno : -ESRCH; - if (read_one_line_file("/sys/kernel/kexec_loaded", &s) >= 0) { - if (s[0] == '1') - loaded = true; - free(s); - } - return loaded; + if (!path_is_absolute(p->pw_dir)) + return -EINVAL; + + h = strdup(p->pw_dir); + if (!h) + return -ENOMEM; + + *_h = h; + return 0; } -int prot_from_flags(int flags) { +int get_shell(char **_s) { + struct passwd *p; + const char *e; + char *s; + uid_t u; - switch (flags & O_ACCMODE) { + assert(_s); - case O_RDONLY: - return PROT_READ; + /* Take the user specified one */ + e = getenv("SHELL"); + if (e) { + s = strdup(e); + if (!s) + return -ENOMEM; - case O_WRONLY: - return PROT_WRITE; + *_s = s; + return 0; + } - case O_RDWR: - return PROT_READ|PROT_WRITE; + /* Hardcode home directory for root to avoid NSS */ + u = getuid(); + if (u == 0) { + s = strdup("/bin/sh"); + if (!s) + return -ENOMEM; - default: - return -EINVAL; + *_s = s; + return 0; } -} -char *format_bytes(char *buf, size_t l, off_t t) { - unsigned i; + /* Check the database... */ + errno = 0; + p = getpwuid(u); + if (!p) + return errno > 0 ? -errno : -ESRCH; - static const struct { - const char *suffix; - off_t factor; - } table[] = { - { "E", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, - { "P", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, - { "T", 1024ULL*1024ULL*1024ULL*1024ULL }, - { "G", 1024ULL*1024ULL*1024ULL }, - { "M", 1024ULL*1024ULL }, - { "K", 1024ULL }, - }; + if (!path_is_absolute(p->pw_shell)) + return -EINVAL; - for (i = 0; i < ELEMENTSOF(table); i++) { + s = strdup(p->pw_shell); + if (!s) + return -ENOMEM; - if (t >= table[i].factor) { - snprintf(buf, l, - "%llu.%llu%s", - (unsigned long long) (t / table[i].factor), - (unsigned long long) (((t*10ULL) / table[i].factor) % 10ULL), - table[i].suffix); + *_s = s; + return 0; +} - goto finish; - } - } +bool filename_is_valid(const char *p) { - snprintf(buf, l, "%lluB", (unsigned long long) t); + if (isempty(p)) + return false; -finish: - buf[l-1] = 0; - return buf; + if (strchr(p, '/')) + return false; + + if (streq(p, ".")) + return false; + if (streq(p, "..")) + return false; + + if (strlen(p) > FILENAME_MAX) + return false; + + return true; } -void* memdup(const void *p, size_t l) { - void *r; +bool string_is_safe(const char *p) { + const char *t; - assert(p); + if (!p) + return false; - r = malloc(l); - if (!r) - return NULL; + for (t = p; *t; t++) { + if (*t > 0 && *t < ' ') + return false; - memcpy(r, p, l); - return r; + if (strchr("\\\"\'\0x7f", *t)) + return false; + } + + return true; } -int fd_inc_sndbuf(int fd, size_t n) { - int r, value; - socklen_t l = sizeof(value); +/** + * Check if a string contains control characters. If 'ok' is non-NULL + * it may be a string containing additional CCs to be considered OK. + */ +bool string_has_cc(const char *p, const char *ok) { + const char *t; - r = getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, &l); - if (r >= 0 && l == sizeof(value) && (size_t) value >= n*2) - return 0; + assert(p); - /* If we have the privileges we will ignore the kernel limit. */ + for (t = p; *t; t++) { + if (ok && strchr(ok, *t)) + continue; - value = (int) n; - if (setsockopt(fd, SOL_SOCKET, SO_SNDBUFFORCE, &value, sizeof(value)) < 0) - if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, sizeof(value)) < 0) - return -errno; + if (*t > 0 && *t < ' ') + return true; - return 1; + if (*t == 127) + return true; + } + + return false; } -int fd_inc_rcvbuf(int fd, size_t n) { - int r, value; - socklen_t l = sizeof(value); +bool path_is_safe(const char *p) { - r = getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, &l); - if (r >= 0 && l == sizeof(value) && (size_t) value >= n*2) - return 0; + if (isempty(p)) + return false; + + if (streq(p, "..") || 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, "/./")) + return false; - /* If we have the privileges we will ignore the kernel limit. */ + if (strstr(p, "//")) + return false; - value = (int) n; - if (setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, &value, sizeof(value)) < 0) - if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, sizeof(value)) < 0) - return -errno; - return 1; + return true; } -int fork_agent(pid_t *pid, const int except[], unsigned n_except, const char *path, ...) { - bool stdout_is_tty, stderr_is_tty; - pid_t parent_pid, agent_pid; - sigset_t ss, saved_ss; - unsigned n, i; - va_list ap; - char **l; +/* hey glibc, APIs with callbacks without a user pointer are so useless */ +void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size, + int (*compar) (const void *, const void *, void *), void *arg) { + size_t l, u, idx; + const void *p; + int comparison; - assert(pid); - assert(path); + l = 0; + u = nmemb; + while (l < u) { + idx = (l + u) / 2; + p = (void *)(((const char *) base) + (idx * size)); + comparison = compar(key, p, arg); + if (comparison < 0) + u = idx; + else if (comparison > 0) + l = idx + 1; + else + return (void *)p; + } + return NULL; +} - /* Spawns a temporary TTY agent, making sure it goes away when - * we go away */ +void init_gettext(void) { + setlocale(LC_ALL, ""); + textdomain(GETTEXT_PACKAGE); +} - parent_pid = getpid(); +bool is_locale_utf8(void) { + const char *set; + static int cached_answer = -1; - /* First we temporarily block all signals, so that the new - * child has them blocked initially. This way, we can be sure - * that SIGTERMs are not lost we might send to the agent. */ - assert_se(sigfillset(&ss) >= 0); - assert_se(sigprocmask(SIG_SETMASK, &ss, &saved_ss) >= 0); + if (cached_answer >= 0) + goto out; - agent_pid = fork(); - if (agent_pid < 0) { - assert_se(sigprocmask(SIG_SETMASK, &saved_ss, NULL) >= 0); - return -errno; + if (!setlocale(LC_ALL, "")) { + cached_answer = true; + goto out; } - if (agent_pid != 0) { - assert_se(sigprocmask(SIG_SETMASK, &saved_ss, NULL) >= 0); - *pid = agent_pid; - return 0; + set = nl_langinfo(CODESET); + if (!set) { + cached_answer = true; + goto out; } - /* In the child: - * - * Make sure the agent goes away when the parent dies */ - if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) - _exit(EXIT_FAILURE); + if (streq(set, "UTF-8")) { + cached_answer = true; + goto out; + } - /* Make sure we actually can kill the agent, if we need to, in - * case somebody invoked us from a shell script that trapped - * SIGTERM or so... */ - reset_all_signal_handlers(); - reset_signal_mask(); + /* For LC_CTYPE=="C" return true, because CTYPE is effectly + * unset and everything can do to UTF-8 nowadays. */ + set = setlocale(LC_CTYPE, NULL); + if (!set) { + cached_answer = true; + goto out; + } - /* Check whether our parent died before we were able - * to set the death signal and unblock the signals */ - if (getppid() != parent_pid) - _exit(EXIT_SUCCESS); + /* Check result, but ignore the result if C was set + * explicitly. */ + cached_answer = + streq(set, "C") && + !getenv("LC_ALL") && + !getenv("LC_CTYPE") && + !getenv("LANG"); - /* Don't leak fds to the agent */ - close_all_fds(except, n_except); +out: + return (bool) cached_answer; +} - stdout_is_tty = isatty(STDOUT_FILENO); - stderr_is_tty = isatty(STDERR_FILENO); +const char *draw_special_char(DrawSpecialChar ch) { + static const char *draw_table[2][_DRAW_SPECIAL_CHAR_MAX] = { - if (!stdout_is_tty || !stderr_is_tty) { - int fd; + /* UTF-8 */ { + [DRAW_TREE_VERTICAL] = "\342\224\202 ", /* │ */ + [DRAW_TREE_BRANCH] = "\342\224\234\342\224\200", /* ├─ */ + [DRAW_TREE_RIGHT] = "\342\224\224\342\224\200", /* └─ */ + [DRAW_TREE_SPACE] = " ", /* */ + [DRAW_TRIANGULAR_BULLET] = "\342\200\243", /* ‣ */ + [DRAW_BLACK_CIRCLE] = "\342\227\217", /* ● */ + [DRAW_ARROW] = "\342\206\222", /* → */ + [DRAW_DASH] = "\342\200\223", /* – */ + }, - /* Detach from stdout/stderr. and reopen - * /dev/tty for them. This is important to - * ensure that when systemctl is started via - * popen() or a similar call that expects to - * read EOF we actually do generate EOF and - * not delay this indefinitely by because we - * keep an unused copy of stdin around. */ - fd = open("/dev/tty", O_WRONLY); - if (fd < 0) { - log_error_errno(errno, "Failed to open /dev/tty: %m"); - _exit(EXIT_FAILURE); + /* ASCII fallback */ { + [DRAW_TREE_VERTICAL] = "| ", + [DRAW_TREE_BRANCH] = "|-", + [DRAW_TREE_RIGHT] = "`-", + [DRAW_TREE_SPACE] = " ", + [DRAW_TRIANGULAR_BULLET] = ">", + [DRAW_BLACK_CIRCLE] = "*", + [DRAW_ARROW] = "->", + [DRAW_DASH] = "-", } + }; - if (!stdout_is_tty) - dup2(fd, STDOUT_FILENO); - - if (!stderr_is_tty) - dup2(fd, STDERR_FILENO); - - if (fd > 2) - close(fd); - } - - /* Count arguments */ - va_start(ap, path); - for (n = 0; va_arg(ap, char*); n++) - ; - va_end(ap); + return draw_table[!is_locale_utf8()][ch]; +} - /* Allocate strv */ - l = alloca(sizeof(char *) * (n + 1)); +char *strreplace(const char *text, const char *old_string, const char *new_string) { + const char *f; + char *t, *r; + size_t l, old_len, new_len; - /* Fill in arguments */ - va_start(ap, path); - for (i = 0; i <= n; i++) - l[i] = va_arg(ap, char*); - va_end(ap); + assert(text); + assert(old_string); + assert(new_string); - execv(path, l); - _exit(EXIT_FAILURE); -} + old_len = strlen(old_string); + new_len = strlen(new_string); -int setrlimit_closest(int resource, const struct rlimit *rlim) { - struct rlimit highest, fixed; + l = strlen(text); + r = new(char, l+1); + if (!r) + return NULL; - assert(rlim); + f = text; + t = r; + while (*f) { + char *a; + size_t d, nl; - if (setrlimit(resource, rlim) >= 0) - return 0; + if (!startswith(f, old_string)) { + *(t++) = *(f++); + continue; + } - if (errno != EPERM) - return -errno; + d = t - r; + nl = l - old_len + new_len; + a = realloc(r, nl + 1); + if (!a) + goto oom; - /* So we failed to set the desired setrlimit, then let's try - * to get as close as we can */ - assert_se(getrlimit(resource, &highest) == 0); + l = nl; + r = a; + t = r + d; - fixed.rlim_cur = MIN(rlim->rlim_cur, highest.rlim_max); - fixed.rlim_max = MIN(rlim->rlim_max, highest.rlim_max); + t = stpcpy(t, new_string); + f += old_len; + } - if (setrlimit(resource, &fixed) < 0) - return -errno; + *t = 0; + return r; - return 0; +oom: + free(r); + return NULL; } -int getenv_for_pid(pid_t pid, const char *field, char **_value) { - _cleanup_fclose_ FILE *f = NULL; - char *value = NULL; - int r; - bool done = false; - size_t l; - const char *path; +char *strip_tab_ansi(char **ibuf, size_t *_isz) { + const char *i, *begin = NULL; + enum { + STATE_OTHER, + STATE_ESCAPE, + STATE_BRACKET + } state = STATE_OTHER; + char *obuf = NULL; + size_t osz = 0, isz; + FILE *f; - assert(pid >= 0); - assert(field); - assert(_value); + assert(ibuf); + assert(*ibuf); - path = procfs_file_alloca(pid, "environ"); + /* Strips ANSI color and replaces TABs by 8 spaces */ + + isz = _isz ? *_isz : strlen(*ibuf); - f = fopen(path, "re"); + f = open_memstream(&obuf, &osz); if (!f) - return -errno; + return NULL; - l = strlen(field); - r = 0; + for (i = *ibuf; i < *ibuf + isz + 1; i++) { - do { - char line[LINE_MAX]; - unsigned i; + switch (state) { - for (i = 0; i < sizeof(line)-1; i++) { - int c; + case STATE_OTHER: + if (i >= *ibuf + isz) /* EOT */ + break; + else if (*i == '\x1B') + state = STATE_ESCAPE; + else if (*i == '\t') + fputs(" ", f); + else + fputc(*i, f); + break; - c = getc(f); - if (_unlikely_(c == EOF)) { - done = true; - break; - } else if (c == 0) + case STATE_ESCAPE: + if (i >= *ibuf + isz) { /* EOT */ + fputc('\x1B', f); break; + } else if (*i == '[') { + state = STATE_BRACKET; + begin = i + 1; + } else { + fputc('\x1B', f); + fputc(*i, f); + state = STATE_OTHER; + } - line[i] = c; - } - line[i] = 0; + break; - if (memcmp(line, field, l) == 0 && line[l] == '=') { - value = strdup(line + l + 1); - if (!value) - return -ENOMEM; + case STATE_BRACKET: - r = 1; + if (i >= *ibuf + isz || /* EOT */ + (!(*i >= '0' && *i <= '9') && *i != ';' && *i != 'm')) { + fputc('\x1B', f); + fputc('[', f); + state = STATE_OTHER; + i = begin-1; + } else if (*i == 'm') + state = STATE_OTHER; break; } + } - } while (!done); - - *_value = value; - return r; -} - -bool is_valid_documentation_url(const char *url) { - assert(url); - - if (startswith(url, "http://") && url[7]) - return true; - - if (startswith(url, "https://") && url[8]) - return true; + if (ferror(f)) { + fclose(f); + free(obuf); + return NULL; + } - if (startswith(url, "file:") && url[5]) - return true; + fclose(f); - if (startswith(url, "info:") && url[5]) - return true; + free(*ibuf); + *ibuf = obuf; - if (startswith(url, "man:") && url[4]) - return true; + if (_isz) + *_isz = osz; - return false; + return obuf; } -bool in_initrd(void) { - static int saved = -1; - struct statfs s; +int on_ac_power(void) { + bool found_offline = false, found_online = false; + _cleanup_closedir_ DIR *d = NULL; - if (saved >= 0) - return saved; + d = opendir("/sys/class/power_supply"); + if (!d) + return errno == ENOENT ? true : -errno; - /* We make two checks here: - * - * 1. the flag file /etc/initrd-release must exist - * 2. the root file system must be a memory file system - * - * The second check is extra paranoia, since misdetecting an - * initrd can have bad bad consequences due the initrd - * emptying when transititioning to the main systemd. - */ + for (;;) { + struct dirent *de; + _cleanup_close_ int fd = -1, device = -1; + char contents[6]; + ssize_t n; - saved = access("/etc/initrd-release", F_OK) >= 0 && - statfs("/", &s) >= 0 && - is_temporary_fs(&s); + errno = 0; + de = readdir(d); + if (!de && errno != 0) + return -errno; - return saved; -} + if (!de) + break; -void warn_melody(void) { - _cleanup_close_ int fd = -1; + if (hidden_file(de->d_name)) + continue; - fd = open("/dev/console", O_WRONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - return; + device = openat(dirfd(d), de->d_name, O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (device < 0) { + if (errno == ENOENT || errno == ENOTDIR) + continue; - /* Yeah, this is synchronous. Kinda sucks. But well... */ + return -errno; + } - ioctl(fd, KIOCSOUND, (int)(1193180/440)); - usleep(125*USEC_PER_MSEC); + fd = openat(device, "type", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) { + if (errno == ENOENT) + continue; - ioctl(fd, KIOCSOUND, (int)(1193180/220)); - usleep(125*USEC_PER_MSEC); + return -errno; + } - ioctl(fd, KIOCSOUND, (int)(1193180/220)); - usleep(125*USEC_PER_MSEC); + n = read(fd, contents, sizeof(contents)); + if (n < 0) + return -errno; - ioctl(fd, KIOCSOUND, 0); -} + if (n != 6 || memcmp(contents, "Mains\n", 6)) + continue; -int make_console_stdio(void) { - int fd, r; + safe_close(fd); + fd = openat(device, "online", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) { + if (errno == ENOENT) + continue; - /* Make /dev/console the controlling terminal and stdin/stdout/stderr */ + return -errno; + } - fd = acquire_terminal("/dev/console", false, true, true, USEC_INFINITY); - if (fd < 0) - return log_error_errno(fd, "Failed to acquire terminal: %m"); + n = read(fd, contents, sizeof(contents)); + if (n < 0) + return -errno; - r = make_stdio(fd); - if (r < 0) - return log_error_errno(r, "Failed to duplicate terminal fd: %m"); + if (n != 2 || contents[1] != '\n') + return -EIO; - return 0; + if (contents[0] == '1') { + found_online = true; + break; + } else if (contents[0] == '0') + found_offline = true; + else + return -EIO; + } + + return found_online || !found_offline; } -int get_home_dir(char **_h) { - struct passwd *p; - const char *e; - char *h; - uid_t u; +static int search_and_fopen_internal(const char *path, const char *mode, const char *root, char **search, FILE **_f) { + char **i; - assert(_h); + assert(path); + assert(mode); + assert(_f); - /* Take the user specified one */ - e = secure_getenv("HOME"); - if (e && path_is_absolute(e)) { - h = strdup(e); - if (!h) - return -ENOMEM; + if (!path_strv_resolve_uniq(search, root)) + return -ENOMEM; - *_h = h; - return 0; - } + STRV_FOREACH(i, search) { + _cleanup_free_ char *p = NULL; + FILE *f; - /* Hardcode home directory for root to avoid NSS */ - u = getuid(); - if (u == 0) { - h = strdup("/root"); - if (!h) + if (root) + p = strjoin(root, *i, "/", path, NULL); + else + p = strjoin(*i, "/", path, NULL); + if (!p) return -ENOMEM; - *_h = h; - return 0; + f = fopen(p, mode); + if (f) { + *_f = f; + return 0; + } + + if (errno != ENOENT) + return -errno; } - /* Check the database... */ - errno = 0; - p = getpwuid(u); - if (!p) - return errno > 0 ? -errno : -ESRCH; + return -ENOENT; +} - if (!path_is_absolute(p->pw_dir)) - return -EINVAL; +int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **_f) { + _cleanup_strv_free_ char **copy = NULL; - h = strdup(p->pw_dir); - if (!h) - return -ENOMEM; + assert(path); + assert(mode); + assert(_f); - *_h = h; - return 0; -} + if (path_is_absolute(path)) { + FILE *f; -int get_shell(char **_s) { - struct passwd *p; - const char *e; - char *s; - uid_t u; + f = fopen(path, mode); + if (f) { + *_f = f; + return 0; + } - assert(_s); + return -errno; + } - /* Take the user specified one */ - e = getenv("SHELL"); - if (e) { - s = strdup(e); - if (!s) - return -ENOMEM; + copy = strv_copy((char**) search); + if (!copy) + return -ENOMEM; - *_s = s; - return 0; - } + return search_and_fopen_internal(path, mode, root, copy, _f); +} - /* Hardcode home directory for root to avoid NSS */ - u = getuid(); - if (u == 0) { - s = strdup("/bin/sh"); - if (!s) - return -ENOMEM; +int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **_f) { + _cleanup_strv_free_ char **s = NULL; - *_s = s; - return 0; - } + if (path_is_absolute(path)) { + FILE *f; - /* Check the database... */ - errno = 0; - p = getpwuid(u); - if (!p) - return errno > 0 ? -errno : -ESRCH; + f = fopen(path, mode); + if (f) { + *_f = f; + return 0; + } - if (!path_is_absolute(p->pw_shell)) - return -EINVAL; + return -errno; + } - s = strdup(p->pw_shell); + s = strv_split_nulstr(search); if (!s) return -ENOMEM; - *_s = s; - return 0; + return search_and_fopen_internal(path, mode, root, s, _f); } -bool filename_is_safe(const char *p) { +char *strextend(char **x, ...) { + va_list ap; + size_t f, l; + char *r, *p; - if (isempty(p)) - return false; + assert(x); - if (strchr(p, '/')) - return false; + l = f = *x ? strlen(*x) : 0; - if (streq(p, ".")) - return false; + va_start(ap, x); + for (;;) { + const char *t; + size_t n; - if (streq(p, "..")) - return false; + t = va_arg(ap, const char *); + if (!t) + break; - if (strlen(p) > FILENAME_MAX) - return false; + n = strlen(t); + if (n > ((size_t) -1) - l) { + va_end(ap); + return NULL; + } - return true; -} + l += n; + } + va_end(ap); -bool string_is_safe(const char *p) { - const char *t; + r = realloc(*x, l+1); + if (!r) + return NULL; - if (!p) - return false; + p = r + f; - for (t = p; *t; t++) { - if (*t > 0 && *t < ' ') - return false; + va_start(ap, x); + for (;;) { + const char *t; - if (strchr("\\\"\'\0x7f", *t)) - return false; + t = va_arg(ap, const char *); + if (!t) + break; + + p = stpcpy(p, t); } + va_end(ap); - return true; -} + *p = 0; + *x = r; -/** - * Check if a string contains control characters. If 'ok' is non-NULL - * it may be a string containing additional CCs to be considered OK. - */ -bool string_has_cc(const char *p, const char *ok) { - const char *t; + return r + l; +} - assert(p); +char *strrep(const char *s, unsigned n) { + size_t l; + char *r, *p; + unsigned i; - for (t = p; *t; t++) { - if (ok && strchr(ok, *t)) - continue; + assert(s); - if (*t > 0 && *t < ' ') - return true; + l = strlen(s); + p = r = malloc(l * n + 1); + if (!r) + return NULL; - if (*t == 127) - return true; - } + for (i = 0; i < n; i++) + p = stpcpy(p, s); - return false; + *p = 0; + return r; } -bool path_is_safe(const char *p) { +void* greedy_realloc(void **p, size_t *allocated, size_t need, size_t size) { + size_t a, newalloc; + void *q; - if (isempty(p)) - return false; + assert(p); + assert(allocated); - if (streq(p, "..") || startswith(p, "../") || endswith(p, "/..") || strstr(p, "/../")) - return false; + if (*allocated >= need) + return *p; - if (strlen(p) > PATH_MAX) - return false; + newalloc = MAX(need * 2, 64u / size); + a = newalloc * size; - /* The following two checks are not really dangerous, but hey, they still are confusing */ - if (streq(p, ".") || startswith(p, "./") || endswith(p, "/.") || strstr(p, "/./")) - return false; + /* check for overflows */ + if (a < size * need) + return NULL; - if (strstr(p, "//")) - return false; + q = realloc(*p, a); + if (!q) + return NULL; - return true; + *p = q; + *allocated = newalloc; + return q; } -/* hey glibc, APIs with callbacks without a user pointer are so useless */ -void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size, - int (*compar) (const void *, const void *, void *), void *arg) { - size_t l, u, idx; - const void *p; - int comparison; +void* greedy_realloc0(void **p, size_t *allocated, size_t need, size_t size) { + size_t prev; + uint8_t *q; - l = 0; - u = nmemb; - while (l < u) { - idx = (l + u) / 2; - p = (void *)(((const char *) base) + (idx * size)); - comparison = compar(key, p, arg); - if (comparison < 0) - u = idx; - else if (comparison > 0) - l = idx + 1; - else - return (void *)p; - } - return NULL; + assert(p); + assert(allocated); + + prev = *allocated; + + q = greedy_realloc(p, allocated, need, size); + if (!q) + return NULL; + + if (*allocated > prev) + memzero(q + prev * size, (*allocated - prev) * size); + + return q; } -bool is_locale_utf8(void) { - const char *set; - static int cached_answer = -1; +bool id128_is_valid(const char *s) { + size_t i, l; - if (cached_answer >= 0) - goto out; + l = strlen(s); + if (l == 32) { - if (!setlocale(LC_ALL, "")) { - cached_answer = true; - goto out; - } + /* Simple formatted 128bit hex string */ - set = nl_langinfo(CODESET); - if (!set) { - cached_answer = true; - goto out; - } + for (i = 0; i < l; i++) { + char c = s[i]; - if (streq(set, "UTF-8")) { - cached_answer = true; - goto out; - } + if (!(c >= '0' && c <= '9') && + !(c >= 'a' && c <= 'z') && + !(c >= 'A' && c <= 'Z')) + return false; + } - /* For LC_CTYPE=="C" return true, because CTYPE is effectly - * unset and everything can do to UTF-8 nowadays. */ - set = setlocale(LC_CTYPE, NULL); - if (!set) { - cached_answer = true; - goto out; - } + } else if (l == 36) { - /* Check result, but ignore the result if C was set - * explicitly. */ - cached_answer = - streq(set, "C") && - !getenv("LC_ALL") && - !getenv("LC_CTYPE") && - !getenv("LANG"); + /* Formatted UUID */ -out: - return (bool) cached_answer; + for (i = 0; i < l; i++) { + char c = s[i]; + + if ((i == 8 || i == 13 || i == 18 || i == 23)) { + if (c != '-') + return false; + } else { + if (!(c >= '0' && c <= '9') && + !(c >= 'a' && c <= 'z') && + !(c >= 'A' && c <= 'Z')) + return false; + } + } + + } else + return false; + + return true; } -const char *draw_special_char(DrawSpecialChar ch) { - static const char *draw_table[2][_DRAW_SPECIAL_CHAR_MAX] = { +int split_pair(const char *s, const char *sep, char **l, char **r) { + char *x, *a, *b; - /* UTF-8 */ { - [DRAW_TREE_VERTICAL] = "\342\224\202 ", /* │ */ - [DRAW_TREE_BRANCH] = "\342\224\234\342\224\200", /* ├─ */ - [DRAW_TREE_RIGHT] = "\342\224\224\342\224\200", /* └─ */ - [DRAW_TREE_SPACE] = " ", /* */ - [DRAW_TRIANGULAR_BULLET] = "\342\200\243", /* ‣ */ - [DRAW_BLACK_CIRCLE] = "\342\227\217", /* ● */ - [DRAW_ARROW] = "\342\206\222", /* → */ - [DRAW_DASH] = "\342\200\223", /* – */ - }, + assert(s); + assert(sep); + assert(l); + assert(r); + + if (isempty(sep)) + return -EINVAL; + + x = strstr(s, sep); + if (!x) + return -EINVAL; + + a = strndup(s, x - s); + if (!a) + return -ENOMEM; - /* ASCII fallback */ { - [DRAW_TREE_VERTICAL] = "| ", - [DRAW_TREE_BRANCH] = "|-", - [DRAW_TREE_RIGHT] = "`-", - [DRAW_TREE_SPACE] = " ", - [DRAW_TRIANGULAR_BULLET] = ">", - [DRAW_BLACK_CIRCLE] = "*", - [DRAW_ARROW] = "->", - [DRAW_DASH] = "-", - } - }; + b = strdup(x + strlen(sep)); + if (!b) { + free(a); + return -ENOMEM; + } - return draw_table[!is_locale_utf8()][ch]; + *l = a; + *r = b; + + return 0; } -char *strreplace(const char *text, const char *old_string, const char *new_string) { - const char *f; - char *t, *r; - size_t l, old_len, new_len; +int shall_restore_state(void) { + _cleanup_free_ char *value = NULL; + int r; - assert(text); - assert(old_string); - assert(new_string); + r = get_proc_cmdline_key("systemd.restore_state=", &value); + if (r < 0) + return r; + if (r == 0) + return true; - old_len = strlen(old_string); - new_len = strlen(new_string); + return parse_boolean(value) != 0; +} - l = strlen(text); - r = new(char, l+1); - if (!r) - return NULL; +int proc_cmdline(char **ret) { + assert(ret); - f = text; - t = r; - while (*f) { - char *a; - size_t d, nl; + if (detect_container(NULL) > 0) + return get_process_cmdline(1, 0, false, ret); + else + return read_one_line_file("/proc/cmdline", ret); +} - if (!startswith(f, old_string)) { - *(t++) = *(f++); - continue; - } +int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value)) { + _cleanup_free_ char *line = NULL; + const char *p; + int r; - d = t - r; - nl = l - old_len + new_len; - a = realloc(r, nl + 1); - if (!a) - goto oom; + assert(parse_item); - l = nl; - r = a; - t = r + d; + r = proc_cmdline(&line); + if (r < 0) + return r; - t = stpcpy(t, new_string); - f += old_len; - } + p = line; + for (;;) { + _cleanup_free_ char *word = NULL; + char *value = NULL; - *t = 0; - return r; + r = unquote_first_word(&p, &word, UNQUOTE_RELAX); + if (r < 0) + return r; + if (r == 0) + break; -oom: - free(r); - return NULL; -} + /* Filter out arguments that are intended only for the + * initrd */ + if (!in_initrd() && startswith(word, "rd.")) + continue; -char *strip_tab_ansi(char **ibuf, size_t *_isz) { - const char *i, *begin = NULL; - enum { - STATE_OTHER, - STATE_ESCAPE, - STATE_BRACKET - } state = STATE_OTHER; - char *obuf = NULL; - size_t osz = 0, isz; - FILE *f; + value = strchr(word, '='); + if (value) + *(value++) = 0; - assert(ibuf); - assert(*ibuf); + r = parse_item(word, value); + if (r < 0) + return r; + } - /* Strips ANSI color and replaces TABs by 8 spaces */ + return 0; +} - isz = _isz ? *_isz : strlen(*ibuf); +int get_proc_cmdline_key(const char *key, char **value) { + _cleanup_free_ char *line = NULL, *ret = NULL; + bool found = false; + const char *p; + int r; - f = open_memstream(&obuf, &osz); - if (!f) - return NULL; + assert(key); - for (i = *ibuf; i < *ibuf + isz + 1; i++) { + r = proc_cmdline(&line); + if (r < 0) + return r; - switch (state) { + p = line; + for (;;) { + _cleanup_free_ char *word = NULL; + const char *e; - case STATE_OTHER: - if (i >= *ibuf + isz) /* EOT */ - break; - else if (*i == '\x1B') - state = STATE_ESCAPE; - else if (*i == '\t') - fputs(" ", f); - else - fputc(*i, f); + r = unquote_first_word(&p, &word, UNQUOTE_RELAX); + if (r < 0) + return r; + if (r == 0) break; - case STATE_ESCAPE: - if (i >= *ibuf + isz) { /* EOT */ - fputc('\x1B', f); - break; - } else if (*i == '[') { - state = STATE_BRACKET; - begin = i + 1; - } else { - fputc('\x1B', f); - fputc(*i, f); - state = STATE_OTHER; - } + /* Filter out arguments that are intended only for the + * initrd */ + if (!in_initrd() && startswith(word, "rd.")) + continue; - break; + if (value) { + e = startswith(word, key); + if (!e) + continue; - case STATE_BRACKET: + r = free_and_strdup(&ret, e); + if (r < 0) + return r; - if (i >= *ibuf + isz || /* EOT */ - (!(*i >= '0' && *i <= '9') && *i != ';' && *i != 'm')) { - fputc('\x1B', f); - fputc('[', f); - state = STATE_OTHER; - i = begin-1; - } else if (*i == 'm') - state = STATE_OTHER; - break; + found = true; + } else { + if (streq(word, key)) + found = true; } } - if (ferror(f)) { - fclose(f); - free(obuf); - return NULL; + if (value) { + *value = ret; + ret = NULL; } - fclose(f); - - free(*ibuf); - *ibuf = obuf; - - if (_isz) - *_isz = osz; + return found; - return obuf; } -int on_ac_power(void) { - bool found_offline = false, found_online = false; - _cleanup_closedir_ DIR *d = NULL; - - d = opendir("/sys/class/power_supply"); - if (!d) - return -errno; +int container_get_leader(const char *machine, pid_t *pid) { + _cleanup_free_ char *s = NULL, *class = NULL; + const char *p; + pid_t leader; + int r; - for (;;) { - struct dirent *de; - _cleanup_close_ int fd = -1, device = -1; - char contents[6]; - ssize_t n; + assert(machine); + assert(pid); - errno = 0; - de = readdir(d); - if (!de && errno != 0) - return -errno; + p = strjoina("/run/systemd/machines/", machine); + r = parse_env_file(p, NEWLINE, "LEADER", &s, "CLASS", &class, NULL); + if (r == -ENOENT) + return -EHOSTDOWN; + if (r < 0) + return r; + if (!s) + return -EIO; - if (!de) - break; + if (!streq_ptr(class, "container")) + return -EIO; - if (ignore_file(de->d_name)) - continue; + r = parse_pid(s, &leader); + if (r < 0) + return r; + if (leader <= 1) + return -EIO; - device = openat(dirfd(d), de->d_name, O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (device < 0) { - if (errno == ENOENT || errno == ENOTDIR) - continue; + *pid = leader; + return 0; +} - return -errno; - } +int namespace_open(pid_t pid, int *pidns_fd, int *mntns_fd, int *netns_fd, int *root_fd) { + _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, netnsfd = -1; + int rfd = -1; - fd = openat(device, "type", O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) { - if (errno == ENOENT) - continue; + assert(pid >= 0); - return -errno; - } + if (mntns_fd) { + const char *mntns; - n = read(fd, contents, sizeof(contents)); - if (n < 0) + mntns = procfs_file_alloca(pid, "ns/mnt"); + mntnsfd = open(mntns, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (mntnsfd < 0) return -errno; + } - if (n != 6 || memcmp(contents, "Mains\n", 6)) - continue; - - safe_close(fd); - fd = openat(device, "online", O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) { - if (errno == ENOENT) - continue; + if (pidns_fd) { + const char *pidns; + pidns = procfs_file_alloca(pid, "ns/pid"); + pidnsfd = open(pidns, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (pidnsfd < 0) return -errno; - } + } - n = read(fd, contents, sizeof(contents)); - if (n < 0) + if (netns_fd) { + const char *netns; + + netns = procfs_file_alloca(pid, "ns/net"); + netnsfd = open(netns, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (netnsfd < 0) return -errno; + } - if (n != 2 || contents[1] != '\n') - return -EIO; + if (root_fd) { + const char *root; - if (contents[0] == '1') { - found_online = true; - break; - } else if (contents[0] == '0') - found_offline = true; - else - return -EIO; + root = procfs_file_alloca(pid, "root"); + rfd = open(root, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (rfd < 0) + return -errno; } - return found_online || !found_offline; -} + if (pidns_fd) + *pidns_fd = pidnsfd; -static int search_and_fopen_internal(const char *path, const char *mode, const char *root, char **search, FILE **_f) { - char **i; + if (mntns_fd) + *mntns_fd = mntnsfd; - assert(path); - assert(mode); - assert(_f); + if (netns_fd) + *netns_fd = netnsfd; - if (!path_strv_resolve_uniq(search, root)) - return -ENOMEM; + if (root_fd) + *root_fd = rfd; - STRV_FOREACH(i, search) { - _cleanup_free_ char *p = NULL; - FILE *f; + pidnsfd = mntnsfd = netnsfd = -1; - if (root) - p = strjoin(root, *i, "/", path, NULL); - else - p = strjoin(*i, "/", path, NULL); - if (!p) - return -ENOMEM; + return 0; +} - f = fopen(p, mode); - if (f) { - *_f = f; - return 0; - } +int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int root_fd) { - if (errno != ENOENT) + if (pidns_fd >= 0) + if (setns(pidns_fd, CLONE_NEWPID) < 0) return -errno; - } - return -ENOENT; -} + if (mntns_fd >= 0) + if (setns(mntns_fd, CLONE_NEWNS) < 0) + return -errno; -int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **_f) { - _cleanup_strv_free_ char **copy = NULL; + if (netns_fd >= 0) + if (setns(netns_fd, CLONE_NEWNET) < 0) + return -errno; - assert(path); - assert(mode); - assert(_f); + if (root_fd >= 0) { + if (fchdir(root_fd) < 0) + return -errno; - if (path_is_absolute(path)) { - FILE *f; + if (chroot(".") < 0) + return -errno; + } - f = fopen(path, mode); - if (f) { - *_f = f; - return 0; - } + if (setresgid(0, 0, 0) < 0) + return -errno; + if (setgroups(0, NULL) < 0) return -errno; - } - copy = strv_copy((char**) search); - if (!copy) - return -ENOMEM; + if (setresuid(0, 0, 0) < 0) + return -errno; - return search_and_fopen_internal(path, mode, root, copy, _f); + return 0; } -int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **_f) { - _cleanup_strv_free_ char **s = NULL; - - if (path_is_absolute(path)) { - FILE *f; +int getpeercred(int fd, struct ucred *ucred) { + socklen_t n = sizeof(struct ucred); + struct ucred u; + int r; - f = fopen(path, mode); - if (f) { - *_f = f; - return 0; - } + assert(fd >= 0); + assert(ucred); + r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &u, &n); + if (r < 0) return -errno; - } - s = strv_split_nulstr(search); - if (!s) - return -ENOMEM; + if (n != sizeof(struct ucred)) + return -EIO; - return search_and_fopen_internal(path, mode, root, s, _f); + /* Check if the data is actually useful and not suppressed due + * to namespacing issues */ + if (u.pid <= 0) + return -ENODATA; + if (u.uid == UID_INVALID) + return -ENODATA; + if (u.gid == GID_INVALID) + return -ENODATA; + + *ucred = u; + return 0; } -char *strextend(char **x, ...) { - va_list ap; - size_t f, l; - char *r, *p; +int getpeersec(int fd, char **ret) { + socklen_t n = 64; + char *s; + int r; - assert(x); + assert(fd >= 0); + assert(ret); - l = f = *x ? strlen(*x) : 0; + s = new0(char, n); + if (!s) + return -ENOMEM; - va_start(ap, x); - for (;;) { - const char *t; - size_t n; + r = getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n); + if (r < 0) { + free(s); - t = va_arg(ap, const char *); - if (!t) - break; + if (errno != ERANGE) + return -errno; - n = strlen(t); - if (n > ((size_t) -1) - l) { - va_end(ap); - return NULL; - } + s = new0(char, n); + if (!s) + return -ENOMEM; - l += n; + r = getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n); + if (r < 0) { + free(s); + return -errno; + } } - va_end(ap); - r = realloc(*x, l+1); - if (!r) - return NULL; - - p = r + f; - - va_start(ap, x); - for (;;) { - const char *t; - - t = va_arg(ap, const char *); - if (!t) - break; - - p = stpcpy(p, t); + if (isempty(s)) { + free(s); + return -EOPNOTSUPP; } - va_end(ap); - - *p = 0; - *x = r; - return r + l; + *ret = s; + return 0; } -char *strrep(const char *s, unsigned n) { - size_t l; - char *r, *p; - unsigned i; - - assert(s); +/* This is much like like mkostemp() but is subject to umask(). */ +int mkostemp_safe(char *pattern, int flags) { + _cleanup_umask_ mode_t u; + int fd; - l = strlen(s); - p = r = malloc(l * n + 1); - if (!r) - return NULL; + assert(pattern); - for (i = 0; i < n; i++) - p = stpcpy(p, s); + u = umask(077); - *p = 0; - return r; -} + fd = mkostemp(pattern, flags); + if (fd < 0) + return -errno; -void* greedy_realloc(void **p, size_t *allocated, size_t need, size_t size) { - size_t a, newalloc; - void *q; + return fd; +} - assert(p); - assert(allocated); +int open_tmpfile(const char *path, int flags) { + char *p; + int fd; - if (*allocated >= need) - return *p; + assert(path); - newalloc = MAX(need * 2, 64u / size); - a = newalloc * size; +#ifdef O_TMPFILE + /* Try O_TMPFILE first, if it is supported */ + fd = open(path, flags|O_TMPFILE|O_EXCL, S_IRUSR|S_IWUSR); + if (fd >= 0) + return fd; +#endif - /* check for overflows */ - if (a < size * need) - return NULL; + /* Fall back to unguessable name + unlinking */ + p = strjoina(path, "/systemd-tmp-XXXXXX"); - q = realloc(*p, a); - if (!q) - return NULL; + fd = mkostemp_safe(p, flags); + if (fd < 0) + return fd; - *p = q; - *allocated = newalloc; - return q; + unlink(p); + return fd; } -void* greedy_realloc0(void **p, size_t *allocated, size_t need, size_t size) { - size_t prev; - uint8_t *q; +int fd_warn_permissions(const char *path, int fd) { + struct stat st; - assert(p); - assert(allocated); + if (fstat(fd, &st) < 0) + return -errno; - prev = *allocated; + if (st.st_mode & 0111) + log_warning("Configuration file %s is marked executable. Please remove executable permission bits. Proceeding anyway.", path); - q = greedy_realloc(p, allocated, need, size); - if (!q) - return NULL; + if (st.st_mode & 0002) + log_warning("Configuration file %s is marked world-writable. Please remove world writability permission bits. Proceeding anyway.", path); - if (*allocated > prev) - memzero(q + prev * size, (*allocated - prev) * size); + if (getpid() == 1 && (st.st_mode & 0044) != 0044) + log_warning("Configuration file %s is marked world-inaccessible. This has no effect as configuration data is accessible via APIs without restrictions. Proceeding anyway.", path); - return q; + return 0; } -bool id128_is_valid(const char *s) { - size_t i, l; +unsigned long personality_from_string(const char *p) { - l = strlen(s); - if (l == 32) { + /* Parse a personality specifier. We introduce our own + * identifiers that indicate specific ABIs, rather than just + * hints regarding the register size, since we want to keep + * things open for multiple locally supported ABIs for the + * same register size. We try to reuse the ABI identifiers + * used by libseccomp. */ - /* Simple formatted 128bit hex string */ +#if defined(__x86_64__) - for (i = 0; i < l; i++) { - char c = s[i]; + if (streq(p, "x86")) + return PER_LINUX32; - if (!(c >= '0' && c <= '9') && - !(c >= 'a' && c <= 'z') && - !(c >= 'A' && c <= 'Z')) - return false; - } + if (streq(p, "x86-64")) + return PER_LINUX; - } else if (l == 36) { +#elif defined(__i386__) - /* Formatted UUID */ + if (streq(p, "x86")) + return PER_LINUX; +#endif - for (i = 0; i < l; i++) { - char c = s[i]; + /* personality(7) documents that 0xffffffffUL is used for + * querying the current personality, hence let's use that here + * as error indicator. */ + return 0xffffffffUL; +} - if ((i == 8 || i == 13 || i == 18 || i == 23)) { - if (c != '-') - return false; - } else { - if (!(c >= '0' && c <= '9') && - !(c >= 'a' && c <= 'z') && - !(c >= 'A' && c <= 'Z')) - return false; - } - } +const char* personality_to_string(unsigned long p) { - } else - return false; +#if defined(__x86_64__) - return true; -} + if (p == PER_LINUX32) + return "x86"; -int split_pair(const char *s, const char *sep, char **l, char **r) { - char *x, *a, *b; + if (p == PER_LINUX) + return "x86-64"; - assert(s); - assert(sep); - assert(l); - assert(r); +#elif defined(__i386__) - if (isempty(sep)) - return -EINVAL; + if (p == PER_LINUX) + return "x86"; +#endif - x = strstr(s, sep); - if (!x) - return -EINVAL; + return NULL; +} - a = strndup(s, x - s); - if (!a) - return -ENOMEM; +uint64_t physical_memory(void) { + long mem; - b = strdup(x + strlen(sep)); - if (!b) { - free(a); - return -ENOMEM; - } + /* We return this as uint64_t in case we are running as 32bit + * process on a 64bit kernel with huge amounts of memory */ - *l = a; - *r = b; + mem = sysconf(_SC_PHYS_PAGES); + assert(mem > 0); - return 0; + return (uint64_t) mem * (uint64_t) page_size(); } -int shall_restore_state(void) { - _cleanup_free_ char *value = NULL; - int r; +void hexdump(FILE *f, const void *p, size_t s) { + const uint8_t *b = p; + unsigned n = 0; - r = get_proc_cmdline_key("systemd.restore_state=", &value); - if (r < 0) - return r; - if (r == 0) - return true; + assert(s == 0 || b); - return parse_boolean(value) != 0; -} + while (s > 0) { + size_t i; -int proc_cmdline(char **ret) { - assert(ret); + fprintf(f, "%04x ", n); - if (detect_container(NULL) > 0) - return get_process_cmdline(1, 0, false, ret); - else - return read_one_line_file("/proc/cmdline", ret); -} + for (i = 0; i < 16; i++) { -int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value)) { - _cleanup_free_ char *line = NULL; - const char *p; - int r; + if (i >= s) + fputs(" ", f); + else + fprintf(f, "%02x ", b[i]); - assert(parse_item); + if (i == 7) + fputc(' ', f); + } - r = proc_cmdline(&line); - if (r < 0) - return r; + fputc(' ', f); - p = line; - for (;;) { - _cleanup_free_ char *word = NULL; - char *value = NULL; + for (i = 0; i < 16; i++) { - r = unquote_first_word(&p, &word, true); - if (r < 0) - return r; - if (r == 0) + if (i >= s) + fputc(' ', f); + else + fputc(isprint(b[i]) ? (char) b[i] : '.', f); + } + + fputc('\n', f); + + if (s < 16) break; - /* Filter out arguments that are intended only for the - * initrd */ - if (!in_initrd() && startswith(word, "rd.")) - continue; + n += 16; + b += 16; + s -= 16; + } +} - value = strchr(word, '='); - if (value) - *(value++) = 0; +int update_reboot_param_file(const char *param) { + int r = 0; - r = parse_item(word, value); + if (param) { + + r = write_string_file(REBOOT_PARAM_FILE, param); if (r < 0) - return r; - } + log_error("Failed to write reboot param to " + REBOOT_PARAM_FILE": %s", strerror(-r)); + } else + unlink(REBOOT_PARAM_FILE); - return 0; + return r; } -int get_proc_cmdline_key(const char *key, char **value) { - _cleanup_free_ char *line = NULL, *ret = NULL; - bool found = false; - const char *p; - int r; +int umount_recursive(const char *prefix, int flags) { + bool again; + int n = 0, r; - assert(key); + /* Try to umount everything recursively below a + * directory. Also, take care of stacked mounts, and keep + * unmounting them until they are gone. */ - r = proc_cmdline(&line); - if (r < 0) - return r; + do { + _cleanup_fclose_ FILE *proc_self_mountinfo = NULL; - p = line; - for (;;) { - _cleanup_free_ char *word = NULL; - const char *e; + again = false; + r = 0; - r = unquote_first_word(&p, &word, true); - if (r < 0) - return r; - if (r == 0) - break; + proc_self_mountinfo = fopen("/proc/self/mountinfo", "re"); + if (!proc_self_mountinfo) + return -errno; - /* Filter out arguments that are intended only for the - * initrd */ - if (!in_initrd() && startswith(word, "rd.")) - continue; + for (;;) { + _cleanup_free_ char *path = NULL, *p = NULL; + int k; + + k = fscanf(proc_self_mountinfo, + "%*s " /* (1) mount id */ + "%*s " /* (2) parent id */ + "%*s " /* (3) major:minor */ + "%*s " /* (4) root */ + "%ms " /* (5) mount point */ + "%*s" /* (6) mount options */ + "%*[^-]" /* (7) optional fields */ + "- " /* (8) separator */ + "%*s " /* (9) file system type */ + "%*s" /* (10) mount source */ + "%*s" /* (11) mount options 2 */ + "%*[^\n]", /* some rubbish at the end */ + &path); + if (k != 1) { + if (k == EOF) + break; - if (value) { - e = startswith(word, key); - if (!e) continue; + } - r = free_and_strdup(&ret, e); + r = cunescape(path, UNESCAPE_RELAX, &p); if (r < 0) return r; - found = true; - } else { - if (streq(word, key)) - found = true; + if (!path_startswith(p, prefix)) + continue; + + if (umount2(p, flags) < 0) { + r = -errno; + continue; + } + + again = true; + n++; + + break; } - } - if (value) { - *value = ret; - ret = NULL; - } + } while (again); - return found; + return r ? r : n; +} + +static int get_mount_flags(const char *path, unsigned long *flags) { + struct statvfs buf; + if (statvfs(path, &buf) < 0) + return -errno; + *flags = buf.f_flag; + return 0; } -int container_get_leader(const char *machine, pid_t *pid) { - _cleanup_free_ char *s = NULL, *class = NULL; - const char *p; - pid_t leader; +int bind_remount_recursive(const char *prefix, bool ro) { + _cleanup_set_free_free_ Set *done = NULL; + _cleanup_free_ char *cleaned = NULL; int r; - assert(machine); - assert(pid); + /* Recursively remount a directory (and all its submounts) + * read-only or read-write. If the directory is already + * mounted, we reuse the mount and simply mark it + * MS_BIND|MS_RDONLY (or remove the MS_RDONLY for read-write + * operation). If it isn't we first make it one. Afterwards we + * apply MS_BIND|MS_RDONLY (or remove MS_RDONLY) to all + * submounts we can access, too. When mounts are stacked on + * the same mount point we only care for each individual + * "top-level" mount on each point, as we cannot + * influence/access the underlying mounts anyway. We do not + * have any effect on future submounts that might get + * propagated, they migt be writable. This includes future + * submounts that have been triggered via autofs. */ - p = strappenda("/run/systemd/machines/", machine); - r = parse_env_file(p, NEWLINE, "LEADER", &s, "CLASS", &class, NULL); - if (r == -ENOENT) - return -EHOSTDOWN; - if (r < 0) - return r; - if (!s) - return -EIO; + cleaned = strdup(prefix); + if (!cleaned) + return -ENOMEM; - if (!streq_ptr(class, "container")) - return -EIO; + path_kill_slashes(cleaned); - r = parse_pid(s, &leader); - if (r < 0) - return r; - if (leader <= 1) - return -EIO; + done = set_new(&string_hash_ops); + if (!done) + return -ENOMEM; - *pid = leader; - return 0; -} + for (;;) { + _cleanup_fclose_ FILE *proc_self_mountinfo = NULL; + _cleanup_set_free_free_ Set *todo = NULL; + bool top_autofs = false; + char *x; + unsigned long orig_flags; -int namespace_open(pid_t pid, int *pidns_fd, int *mntns_fd, int *netns_fd, int *root_fd) { - _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, netnsfd = -1; - int rfd = -1; + todo = set_new(&string_hash_ops); + if (!todo) + return -ENOMEM; - assert(pid >= 0); + proc_self_mountinfo = fopen("/proc/self/mountinfo", "re"); + if (!proc_self_mountinfo) + return -errno; - if (mntns_fd) { - const char *mntns; + for (;;) { + _cleanup_free_ char *path = NULL, *p = NULL, *type = NULL; + int k; - mntns = procfs_file_alloca(pid, "ns/mnt"); - mntnsfd = open(mntns, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (mntnsfd < 0) - return -errno; - } + k = fscanf(proc_self_mountinfo, + "%*s " /* (1) mount id */ + "%*s " /* (2) parent id */ + "%*s " /* (3) major:minor */ + "%*s " /* (4) root */ + "%ms " /* (5) mount point */ + "%*s" /* (6) mount options (superblock) */ + "%*[^-]" /* (7) optional fields */ + "- " /* (8) separator */ + "%ms " /* (9) file system type */ + "%*s" /* (10) mount source */ + "%*s" /* (11) mount options (bind mount) */ + "%*[^\n]", /* some rubbish at the end */ + &path, + &type); + if (k != 2) { + if (k == EOF) + break; - if (pidns_fd) { - const char *pidns; + continue; + } - pidns = procfs_file_alloca(pid, "ns/pid"); - pidnsfd = open(pidns, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (pidnsfd < 0) - return -errno; - } + r = cunescape(path, UNESCAPE_RELAX, &p); + if (r < 0) + return r; - if (netns_fd) { - const char *netns; + /* Let's ignore autofs mounts. If they aren't + * triggered yet, we want to avoid triggering + * them, as we don't make any guarantees for + * future submounts anyway. If they are + * already triggered, then we will find + * another entry for this. */ + if (streq(type, "autofs")) { + top_autofs = top_autofs || path_equal(cleaned, p); + continue; + } - netns = procfs_file_alloca(pid, "ns/net"); - netnsfd = open(netns, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (netnsfd < 0) - return -errno; - } + if (path_startswith(p, cleaned) && + !set_contains(done, p)) { - if (root_fd) { - const char *root; + r = set_consume(todo, p); + p = NULL; - root = procfs_file_alloca(pid, "root"); - rfd = open(root, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (rfd < 0) - return -errno; - } + if (r == -EEXIST) + continue; + if (r < 0) + return r; + } + } - if (pidns_fd) - *pidns_fd = pidnsfd; + /* If we have no submounts to process anymore and if + * the root is either already done, or an autofs, we + * are done */ + if (set_isempty(todo) && + (top_autofs || set_contains(done, cleaned))) + return 0; - if (mntns_fd) - *mntns_fd = mntnsfd; + if (!set_contains(done, cleaned) && + !set_contains(todo, cleaned)) { + /* The prefix directory itself is not yet a + * mount, make it one. */ + if (mount(cleaned, cleaned, NULL, MS_BIND|MS_REC, NULL) < 0) + return -errno; - if (netns_fd) - *netns_fd = netnsfd; + orig_flags = 0; + (void) get_mount_flags(cleaned, &orig_flags); + orig_flags &= ~MS_RDONLY; - if (root_fd) - *root_fd = rfd; + if (mount(NULL, prefix, NULL, orig_flags|MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0) + return -errno; - pidnsfd = mntnsfd = netnsfd = -1; + x = strdup(cleaned); + if (!x) + return -ENOMEM; - return 0; -} + r = set_consume(done, x); + if (r < 0) + return r; + } -int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int root_fd) { + while ((x = set_steal_first(todo))) { - if (pidns_fd >= 0) - if (setns(pidns_fd, CLONE_NEWPID) < 0) - return -errno; + r = set_consume(done, x); + if (r == -EEXIST) + continue; + if (r < 0) + return r; - if (mntns_fd >= 0) - if (setns(mntns_fd, CLONE_NEWNS) < 0) - return -errno; + /* Try to reuse the original flag set, but + * don't care for errors, in case of + * obstructed mounts */ + orig_flags = 0; + (void) get_mount_flags(x, &orig_flags); + orig_flags &= ~MS_RDONLY; - if (netns_fd >= 0) - if (setns(netns_fd, CLONE_NEWNET) < 0) - return -errno; + if (mount(NULL, x, NULL, orig_flags|MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0) { - if (root_fd >= 0) { - if (fchdir(root_fd) < 0) - return -errno; + /* Deal with mount points that are + * obstructed by a later mount */ + + if (errno != ENOENT) + return -errno; + } - if (chroot(".") < 0) - return -errno; + } } +} - if (setresgid(0, 0, 0) < 0) - return -errno; +int fflush_and_check(FILE *f) { + assert(f); - if (setgroups(0, NULL) < 0) - return -errno; + errno = 0; + fflush(f); - if (setresuid(0, 0, 0) < 0) - return -errno; + if (ferror(f)) + return errno ? -errno : -EIO; return 0; } -bool pid_is_unwaited(pid_t pid) { - /* Checks whether a PID is still valid at all, including a zombie */ +int tempfn_xxxxxx(const char *p, char **ret) { + const char *fn; + char *t; - if (pid <= 0) - return false; + assert(p); + assert(ret); - if (kill(pid, 0) >= 0) - return true; + /* + * Turns this: + * /foo/bar/waldo + * + * Into this: + * /foo/bar/.#waldoXXXXXX + */ - return errno != ESRCH; -} + fn = basename(p); + if (!filename_is_valid(fn)) + return -EINVAL; -bool pid_is_alive(pid_t pid) { - int r; + t = new(char, strlen(p) + 2 + 6 + 1); + if (!t) + return -ENOMEM; - /* Checks whether a PID is still valid and not a zombie */ + strcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), fn), "XXXXXX"); - if (pid <= 0) - return false; + *ret = path_kill_slashes(t); + return 0; +} - r = get_process_state(pid); - if (r == -ENOENT || r == 'Z') - return false; +int tempfn_random(const char *p, char **ret) { + const char *fn; + char *t, *x; + uint64_t u; + unsigned i; - return true; -} + assert(p); + assert(ret); -int getpeercred(int fd, struct ucred *ucred) { - socklen_t n = sizeof(struct ucred); - struct ucred u; - int r; + /* + * Turns this: + * /foo/bar/waldo + * + * Into this: + * /foo/bar/.#waldobaa2a261115984a9 + */ - assert(fd >= 0); - assert(ucred); + fn = basename(p); + if (!filename_is_valid(fn)) + return -EINVAL; - r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &u, &n); - if (r < 0) - return -errno; + t = new(char, strlen(p) + 2 + 16 + 1); + if (!t) + return -ENOMEM; - if (n != sizeof(struct ucred)) - return -EIO; + x = stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), fn); - /* Check if the data is actually useful and not suppressed due - * to namespacing issues */ - if (u.pid <= 0) - return -ENODATA; - if (u.uid == UID_INVALID) - return -ENODATA; - if (u.gid == GID_INVALID) - return -ENODATA; + u = random_u64(); + for (i = 0; i < 16; i++) { + *(x++) = hexchar(u & 0xF); + u >>= 4; + } - *ucred = u; + *x = 0; + + *ret = path_kill_slashes(t); return 0; } -int getpeersec(int fd, char **ret) { - socklen_t n = 64; - char *s; - int r; +int tempfn_random_child(const char *p, char **ret) { + char *t, *x; + uint64_t u; + unsigned i; - assert(fd >= 0); + assert(p); assert(ret); - s = new0(char, n); - if (!s) - return -ENOMEM; - - r = getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n); - if (r < 0) { - free(s); + /* Turns this: + * /foo/bar/waldo + * Into this: + * /foo/bar/waldo/.#3c2b6219aa75d7d0 + */ - if (errno != ERANGE) - return -errno; + t = new(char, strlen(p) + 3 + 16 + 1); + if (!t) + return -ENOMEM; - s = new0(char, n); - if (!s) - return -ENOMEM; + x = stpcpy(stpcpy(t, p), "/.#"); - r = getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n); - if (r < 0) { - free(s); - return -errno; - } + u = random_u64(); + for (i = 0; i < 16; i++) { + *(x++) = hexchar(u & 0xF); + u >>= 4; } - if (isempty(s)) { - free(s); - return -ENOTSUP; - } + *x = 0; - *ret = s; + *ret = path_kill_slashes(t); return 0; } -/* This is much like like mkostemp() but is subject to umask(). */ -int mkostemp_safe(char *pattern, int flags) { - _cleanup_umask_ mode_t u; - int fd; +int take_password_lock(const char *root) { - assert(pattern); + struct flock flock = { + .l_type = F_WRLCK, + .l_whence = SEEK_SET, + .l_start = 0, + .l_len = 0, + }; - u = umask(077); + const char *path; + int fd, r; - fd = mkostemp(pattern, flags); + /* This is roughly the same as lckpwdf(), but not as awful. We + * don't want to use alarm() and signals, hence we implement + * our own trivial version of this. + * + * Note that shadow-utils also takes per-database locks in + * addition to lckpwdf(). However, we don't given that they + * are redundant as they they invoke lckpwdf() first and keep + * it during everything they do. The per-database locks are + * awfully racy, and thus we just won't do them. */ + + if (root) + path = strjoina(root, "/etc/.pwd.lock"); + else + path = "/etc/.pwd.lock"; + + fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600); if (fd < 0) return -errno; + r = fcntl(fd, F_SETLKW, &flock); + if (r < 0) { + safe_close(fd); + return -errno; + } + return fd; } -int open_tmpfile(const char *path, int flags) { - char *p; - int fd; +int is_symlink(const char *path) { + struct stat info; - assert(path); + if (lstat(path, &info) < 0) + return -errno; -#ifdef O_TMPFILE - /* Try O_TMPFILE first, if it is supported */ - fd = open(path, flags|O_TMPFILE, S_IRUSR|S_IWUSR); - if (fd >= 0) - return fd; -#endif + return !!S_ISLNK(info.st_mode); +} - /* Fall back to unguessable name + unlinking */ - p = strappenda(path, "/systemd-tmp-XXXXXX"); +int is_dir(const char* path, bool follow) { + struct stat st; + int r; - fd = mkostemp_safe(p, flags); - if (fd < 0) - return fd; + if (follow) + r = stat(path, &st); + else + r = lstat(path, &st); + if (r < 0) + return -errno; - unlink(p); - return fd; + return !!S_ISDIR(st.st_mode); +} + +int is_device_node(const char *path) { + struct stat info; + + if (lstat(path, &info) < 0) + return -errno; + + return !!(S_ISBLK(info.st_mode) || S_ISCHR(info.st_mode)); } -int fd_warn_permissions(const char *path, int fd) { - struct stat st; +int unquote_first_word(const char **p, char **ret, UnquoteFlags flags) { + _cleanup_free_ char *s = NULL; + size_t allocated = 0, sz = 0; + int r; + + enum { + START, + VALUE, + VALUE_ESCAPE, + SINGLE_QUOTE, + SINGLE_QUOTE_ESCAPE, + DOUBLE_QUOTE, + DOUBLE_QUOTE_ESCAPE, + SPACE, + } state = START; + + assert(p); + assert(*p); + assert(ret); + + /* Parses the first word of a string, and returns it in + * *ret. Removes all quotes in the process. When parsing fails + * (because of an uneven number of quotes or similar), leaves + * the pointer *p at the first invalid character. */ + + for (;;) { + char c = **p; + + switch (state) { + + case START: + if (c == 0) + goto finish; + else if (strchr(WHITESPACE, c)) + break; + + state = VALUE; + /* fallthrough */ + + case VALUE: + if (c == 0) + goto finish; + else if (c == '\'') + state = SINGLE_QUOTE; + else if (c == '\\') + state = VALUE_ESCAPE; + else if (c == '\"') + state = DOUBLE_QUOTE; + else if (strchr(WHITESPACE, c)) + state = SPACE; + else { + if (!GREEDY_REALLOC(s, allocated, sz+2)) + return -ENOMEM; + + s[sz++] = c; + } + + break; + + case VALUE_ESCAPE: + if (c == 0) { + if (flags & UNQUOTE_RELAX) + goto finish; + return -EINVAL; + } + + if (!GREEDY_REALLOC(s, allocated, sz+7)) + return -ENOMEM; - if (fstat(fd, &st) < 0) - return -errno; + if (flags & UNQUOTE_CUNESCAPE) { + uint32_t u; - if (st.st_mode & 0111) - log_warning("Configuration file %s is marked executable. Please remove executable permission bits. Proceeding anyway.", path); + r = cunescape_one(*p, (size_t) -1, &c, &u); + if (r < 0) + return -EINVAL; - if (st.st_mode & 0002) - log_warning("Configuration file %s is marked world-writable. Please remove world writability permission bits. Proceeding anyway.", path); + (*p) += r - 1; - if (getpid() == 1 && (st.st_mode & 0044) != 0044) - log_warning("Configuration file %s is marked world-inaccessible. This has no effect as configuration data is accessible via APIs without restrictions. Proceeding anyway.", path); + if (c != 0) + s[sz++] = c; /* normal explicit char */ + else + sz += utf8_encode_unichar(s + sz, u); /* unicode chars we'll encode as utf8 */ + } else + s[sz++] = c; - return 0; -} + state = VALUE; + break; -unsigned long personality_from_string(const char *p) { + case SINGLE_QUOTE: + if (c == 0) { + if (flags & UNQUOTE_RELAX) + goto finish; + return -EINVAL; + } else if (c == '\'') + state = VALUE; + else if (c == '\\') + state = SINGLE_QUOTE_ESCAPE; + else { + if (!GREEDY_REALLOC(s, allocated, sz+2)) + return -ENOMEM; - /* Parse a personality specifier. We introduce our own - * identifiers that indicate specific ABIs, rather than just - * hints regarding the register size, since we want to keep - * things open for multiple locally supported ABIs for the - * same register size. We try to reuse the ABI identifiers - * used by libseccomp. */ + s[sz++] = c; + } -#if defined(__x86_64__) + break; - if (streq(p, "x86")) - return PER_LINUX32; + case SINGLE_QUOTE_ESCAPE: + if (c == 0) { + if (flags & UNQUOTE_RELAX) + goto finish; + return -EINVAL; + } - if (streq(p, "x86-64")) - return PER_LINUX; + if (!GREEDY_REALLOC(s, allocated, sz+7)) + return -ENOMEM; -#elif defined(__i386__) + if (flags & UNQUOTE_CUNESCAPE) { + uint32_t u; - if (streq(p, "x86")) - return PER_LINUX; -#endif + r = cunescape_one(*p, (size_t) -1, &c, &u); + if (r < 0) + return -EINVAL; - /* personality(7) documents that 0xffffffffUL is used for - * querying the current personality, hence let's use that here - * as error indicator. */ - return 0xffffffffUL; -} + (*p) += r - 1; -const char* personality_to_string(unsigned long p) { + if (c != 0) + s[sz++] = c; + else + sz += utf8_encode_unichar(s + sz, u); + } else + s[sz++] = c; -#if defined(__x86_64__) + state = SINGLE_QUOTE; + break; - if (p == PER_LINUX32) - return "x86"; + case DOUBLE_QUOTE: + if (c == 0) + return -EINVAL; + else if (c == '\"') + state = VALUE; + else if (c == '\\') + state = DOUBLE_QUOTE_ESCAPE; + else { + if (!GREEDY_REALLOC(s, allocated, sz+2)) + return -ENOMEM; - if (p == PER_LINUX) - return "x86-64"; + s[sz++] = c; + } -#elif defined(__i386__) + break; - if (p == PER_LINUX) - return "x86"; -#endif + case DOUBLE_QUOTE_ESCAPE: + if (c == 0) { + if (flags & UNQUOTE_RELAX) + goto finish; + return -EINVAL; + } - return NULL; -} + if (!GREEDY_REALLOC(s, allocated, sz+7)) + return -ENOMEM; -uint64_t physical_memory(void) { - long mem; + if (flags & UNQUOTE_CUNESCAPE) { + uint32_t u; - /* We return this as uint64_t in case we are running as 32bit - * process on a 64bit kernel with huge amounts of memory */ + r = cunescape_one(*p, (size_t) -1, &c, &u); + if (r < 0) + return -EINVAL; - mem = sysconf(_SC_PHYS_PAGES); - assert(mem > 0); + (*p) += r - 1; - return (uint64_t) mem * (uint64_t) page_size(); -} + if (c != 0) + s[sz++] = c; + else + sz += utf8_encode_unichar(s + sz, u); + } else + s[sz++] = c; -char* mount_test_option(const char *haystack, const char *needle) { + state = DOUBLE_QUOTE; + break; - struct mntent me = { - .mnt_opts = (char*) haystack - }; + case SPACE: + if (c == 0) + goto finish; + if (!strchr(WHITESPACE, c)) + goto finish; - assert(needle); + break; + } - /* Like glibc's hasmntopt(), but works on a string, not a - * struct mntent */ + (*p) ++; + } - if (!haystack) - return NULL; +finish: + if (!s) { + *ret = NULL; + return 0; + } - return hasmntopt(&me, needle); -} + s[sz] = 0; + *ret = s; + s = NULL; -void hexdump(FILE *f, const void *p, size_t s) { - const uint8_t *b = p; - unsigned n = 0; + return 1; +} - assert(s == 0 || b); +int unquote_many_words(const char **p, UnquoteFlags flags, ...) { + va_list ap; + char **l; + int n = 0, i, c, r; - while (s > 0) { - size_t i; + /* Parses a number of words from a string, stripping any + * quotes if necessary. */ - fprintf(f, "%04x ", n); + assert(p); - for (i = 0; i < 16; i++) { + /* Count how many words are expected */ + va_start(ap, flags); + for (;;) { + if (!va_arg(ap, char **)) + break; + n++; + } + va_end(ap); - if (i >= s) - fputs(" ", f); - else - fprintf(f, "%02x ", b[i]); + if (n <= 0) + return 0; - if (i == 7) - fputc(' ', f); - } + /* Read all words into a temporary array */ + l = newa0(char*, n); + for (c = 0; c < n; c++) { - fputc(' ', f); + r = unquote_first_word(p, &l[c], flags); + if (r < 0) { + int j; - for (i = 0; i < 16; i++) { + for (j = 0; j < c; j++) + free(l[j]); - if (i >= s) - fputc(' ', f); - else - fputc(isprint(b[i]) ? (char) b[i] : '.', f); + return r; } - fputc('\n', f); - - if (s < 16) + if (r == 0) break; - - n += 16; - b += 16; - s -= 16; } -} -int update_reboot_param_file(const char *param) { - int r = 0; + /* If we managed to parse all words, return them in the passed + * in parameters */ + va_start(ap, flags); + for (i = 0; i < n; i++) { + char **v; - if (param) { + v = va_arg(ap, char **); + assert(v); - r = write_string_file(REBOOT_PARAM_FILE, param); - if (r < 0) - log_error("Failed to write reboot param to " - REBOOT_PARAM_FILE": %s", strerror(-r)); - } else - unlink(REBOOT_PARAM_FILE); + *v = l[i]; + } + va_end(ap); - return r; + return c; } -int umount_recursive(const char *prefix, int flags) { - bool again; - int n = 0, r; - - /* Try to umount everything recursively below a - * directory. Also, take care of stacked mounts, and keep - * unmounting them until they are gone. */ +int free_and_strdup(char **p, const char *s) { + char *t; - do { - _cleanup_fclose_ FILE *proc_self_mountinfo = NULL; + assert(p); - again = false; - r = 0; + /* Replaces a string pointer with an strdup()ed new string, + * possibly freeing the old one. */ - proc_self_mountinfo = fopen("/proc/self/mountinfo", "re"); - if (!proc_self_mountinfo) - return -errno; + if (streq_ptr(*p, s)) + return 0; - for (;;) { - _cleanup_free_ char *path = NULL, *p = NULL; - int k; + if (s) { + t = strdup(s); + if (!t) + return -ENOMEM; + } else + t = NULL; - k = fscanf(proc_self_mountinfo, - "%*s " /* (1) mount id */ - "%*s " /* (2) parent id */ - "%*s " /* (3) major:minor */ - "%*s " /* (4) root */ - "%ms " /* (5) mount point */ - "%*s" /* (6) mount options */ - "%*[^-]" /* (7) optional fields */ - "- " /* (8) separator */ - "%*s " /* (9) file system type */ - "%*s" /* (10) mount source */ - "%*s" /* (11) mount options 2 */ - "%*[^\n]", /* some rubbish at the end */ - &path); - if (k != 1) { - if (k == EOF) - break; + free(*p); + *p = t; - continue; - } + return 1; +} - p = cunescape(path); - if (!p) - return -ENOMEM; +int ptsname_malloc(int fd, char **ret) { + size_t l = 100; - if (!path_startswith(p, prefix)) - continue; + assert(fd >= 0); + assert(ret); - if (umount2(p, flags) < 0) { - r = -errno; - continue; - } + for (;;) { + char *c; - again = true; - n++; + c = new(char, l); + if (!c) + return -ENOMEM; - break; + if (ptsname_r(fd, c, l) == 0) { + *ret = c; + return 0; + } + if (errno != ERANGE) { + free(c); + return -errno; } - } while (again); - - return r ? r : n; + free(c); + l *= 2; + } } -int bind_remount_recursive(const char *prefix, bool ro) { - _cleanup_set_free_free_ Set *done = NULL; - _cleanup_free_ char *cleaned = NULL; +int openpt_in_namespace(pid_t pid, int flags) { + _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, rootfd = -1; + _cleanup_close_pair_ int pair[2] = { -1, -1 }; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int))]; + } control = {}; + struct msghdr mh = { + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + struct cmsghdr *cmsg; + siginfo_t si; + pid_t child; int r; - /* Recursively remount a directory (and all its submounts) - * read-only or read-write. If the directory is already - * mounted, we reuse the mount and simply mark it - * MS_BIND|MS_RDONLY (or remove the MS_RDONLY for read-write - * operation). If it isn't we first make it one. Afterwards we - * apply MS_BIND|MS_RDONLY (or remove MS_RDONLY) to all - * submounts we can access, too. When mounts are stacked on - * the same mount point we only care for each individual - * "top-level" mount on each point, as we cannot - * influence/access the underlying mounts anyway. We do not - * have any effect on future submounts that might get - * propagated, they migt be writable. This includes future - * submounts that have been triggered via autofs. */ - - cleaned = strdup(prefix); - if (!cleaned) - return -ENOMEM; + assert(pid > 0); - path_kill_slashes(cleaned); + r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, &rootfd); + if (r < 0) + return r; - done = set_new(&string_hash_ops); - if (!done) - return -ENOMEM; + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) + return -errno; - for (;;) { - _cleanup_fclose_ FILE *proc_self_mountinfo = NULL; - _cleanup_set_free_free_ Set *todo = NULL; - bool top_autofs = false; - char *x; + child = fork(); + if (child < 0) + return -errno; - todo = set_new(&string_hash_ops); - if (!todo) - return -ENOMEM; + if (child == 0) { + int master; - proc_self_mountinfo = fopen("/proc/self/mountinfo", "re"); - if (!proc_self_mountinfo) - return -errno; + pair[0] = safe_close(pair[0]); - for (;;) { - _cleanup_free_ char *path = NULL, *p = NULL, *type = NULL; - int k; + r = namespace_enter(pidnsfd, mntnsfd, -1, rootfd); + if (r < 0) + _exit(EXIT_FAILURE); - k = fscanf(proc_self_mountinfo, - "%*s " /* (1) mount id */ - "%*s " /* (2) parent id */ - "%*s " /* (3) major:minor */ - "%*s " /* (4) root */ - "%ms " /* (5) mount point */ - "%*s" /* (6) mount options (superblock) */ - "%*[^-]" /* (7) optional fields */ - "- " /* (8) separator */ - "%ms " /* (9) file system type */ - "%*s" /* (10) mount source */ - "%*s" /* (11) mount options (bind mount) */ - "%*[^\n]", /* some rubbish at the end */ - &path, - &type); - if (k != 2) { - if (k == EOF) - break; + master = posix_openpt(flags); + if (master < 0) + _exit(EXIT_FAILURE); - continue; - } + cmsg = CMSG_FIRSTHDR(&mh); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + memcpy(CMSG_DATA(cmsg), &master, sizeof(int)); - p = cunescape(path); - if (!p) - return -ENOMEM; + mh.msg_controllen = cmsg->cmsg_len; - /* Let's ignore autofs mounts. If they aren't - * triggered yet, we want to avoid triggering - * them, as we don't make any guarantees for - * future submounts anyway. If they are - * already triggered, then we will find - * another entry for this. */ - if (streq(type, "autofs")) { - top_autofs = top_autofs || path_equal(cleaned, p); - continue; - } + if (sendmsg(pair[1], &mh, MSG_NOSIGNAL) < 0) + _exit(EXIT_FAILURE); - if (path_startswith(p, cleaned) && - !set_contains(done, p)) { + _exit(EXIT_SUCCESS); + } - r = set_consume(todo, p); - p = NULL; + pair[1] = safe_close(pair[1]); - if (r == -EEXIST) - continue; - if (r < 0) - return r; - } - } + r = wait_for_terminate(child, &si); + if (r < 0) + return r; + if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) + return -EIO; - /* If we have no submounts to process anymore and if - * the root is either already done, or an autofs, we - * are done */ - if (set_isempty(todo) && - (top_autofs || set_contains(done, cleaned))) - return 0; + if (recvmsg(pair[0], &mh, MSG_NOSIGNAL|MSG_CMSG_CLOEXEC) < 0) + return -errno; - if (!set_contains(done, cleaned) && - !set_contains(todo, cleaned)) { - /* The prefix directory itself is not yet a - * mount, make it one. */ - if (mount(cleaned, cleaned, NULL, MS_BIND|MS_REC, NULL) < 0) - return -errno; + for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg)) + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { + int *fds; + unsigned n_fds; - if (mount(NULL, prefix, NULL, MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0) - return -errno; + fds = (int*) CMSG_DATA(cmsg); + n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); - x = strdup(cleaned); - if (!x) - return -ENOMEM; + if (n_fds != 1) { + close_many(fds, n_fds); + return -EIO; + } - r = set_consume(done, x); - if (r < 0) - return r; + return fds[0]; } - while ((x = set_steal_first(todo))) { + return -EIO; +} - r = set_consume(done, x); - if (r == -EEXIST) - continue; - if (r < 0) - return r; +ssize_t fgetxattrat_fake(int dirfd, const char *filename, const char *attribute, void *value, size_t size, int flags) { + _cleanup_close_ int fd = -1; + ssize_t l; - if (mount(NULL, x, NULL, MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0) { + /* The kernel doesn't have a fgetxattrat() command, hence let's emulate one */ - /* Deal with mount points that are - * obstructed by a later mount */ + fd = openat(dirfd, filename, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOATIME|(flags & AT_SYMLINK_NOFOLLOW ? O_NOFOLLOW : 0)); + if (fd < 0) + return -errno; - if (errno != ENOENT) - return -errno; - } + l = fgetxattr(fd, attribute, value, size); + if (l < 0) + return -errno; - } - } + return l; } -int fflush_and_check(FILE *f) { - assert(f); +static int parse_crtime(le64_t le, usec_t *usec) { + uint64_t u; - errno = 0; - fflush(f); + assert(usec); - if (ferror(f)) - return errno ? -errno : -EIO; + u = le64toh(le); + if (u == 0 || u == (uint64_t) -1) + return -EIO; + *usec = (usec_t) u; return 0; } -char *tempfn_xxxxxx(const char *p) { - const char *fn; - char *t; - size_t k; - - assert(p); +int fd_getcrtime(int fd, usec_t *usec) { + le64_t le; + ssize_t n; - t = new(char, strlen(p) + 1 + 6 + 1); - if (!t) - return NULL; + assert(fd >= 0); + assert(usec); - fn = basename(p); - k = fn - p; + /* Until Linux gets a real concept of birthtime/creation time, + * let's fake one with xattrs */ - strcpy(stpcpy(stpcpy(mempcpy(t, p, k), "."), fn), "XXXXXX"); + n = fgetxattr(fd, "user.crtime_usec", &le, sizeof(le)); + if (n < 0) + return -errno; + if (n != sizeof(le)) + return -EIO; - return t; + return parse_crtime(le, usec); } -char *tempfn_random(const char *p) { - const char *fn; - char *t, *x; - uint64_t u; - size_t k; - unsigned i; - - assert(p); +int fd_getcrtime_at(int dirfd, const char *name, usec_t *usec, int flags) { + le64_t le; + ssize_t n; - t = new(char, strlen(p) + 1 + 16 + 1); - if (!t) - return NULL; + n = fgetxattrat_fake(dirfd, name, "user.crtime_usec", &le, sizeof(le), flags); + if (n < 0) + return -errno; + if (n != sizeof(le)) + return -EIO; - fn = basename(p); - k = fn - p; + return parse_crtime(le, usec); +} - x = stpcpy(stpcpy(mempcpy(t, p, k), "."), fn); +int path_getcrtime(const char *p, usec_t *usec) { + le64_t le; + ssize_t n; - u = random_u64(); - for (i = 0; i < 16; i++) { - *(x++) = hexchar(u & 0xF); - u >>= 4; - } + assert(p); + assert(usec); - *x = 0; + n = getxattr(p, "user.crtime_usec", &le, sizeof(le)); + if (n < 0) + return -errno; + if (n != sizeof(le)) + return -EIO; - return t; + return parse_crtime(le, usec); } -/* make sure the hostname is not "localhost" */ -bool is_localhost(const char *hostname) { - assert(hostname); +int fd_setcrtime(int fd, usec_t usec) { + le64_t le; + + assert(fd >= 0); + + if (usec <= 0) + usec = now(CLOCK_REALTIME); - /* This tries to identify local host and domain names - * described in RFC6761 plus the redhatism of .localdomain */ + le = htole64((uint64_t) usec); + if (fsetxattr(fd, "user.crtime_usec", &le, sizeof(le), 0) < 0) + return -errno; - return streq(hostname, "localhost") || - streq(hostname, "localhost.") || - streq(hostname, "localdomain.") || - streq(hostname, "localdomain") || - endswith(hostname, ".localhost") || - endswith(hostname, ".localhost.") || - endswith(hostname, ".localdomain") || - endswith(hostname, ".localdomain."); + return 0; } -int take_password_lock(const char *root) { +int chattr_fd(int fd, unsigned value, unsigned mask) { + unsigned old_attr, new_attr; + struct stat st; - struct flock flock = { - .l_type = F_WRLCK, - .l_whence = SEEK_SET, - .l_start = 0, - .l_len = 0, - }; + assert(fd >= 0); - const char *path; - int fd, r; + if (fstat(fd, &st) < 0) + return -errno; - /* This is roughly the same as lckpwdf(), but not as awful. We - * don't want to use alarm() and signals, hence we implement - * our own trivial version of this. - * - * Note that shadow-utils also takes per-database locks in - * addition to lckpwdf(). However, we don't given that they - * are redundant as they they invoke lckpwdf() first and keep - * it during everything they do. The per-database locks are - * awfully racy, and thus we just won't do them. */ + /* Explicitly check whether this is a regular file or + * directory. If it is anything else (such as a device node or + * fifo), then the ioctl will not hit the file systems but + * possibly drivers, where the ioctl might have different + * effects. Notably, DRM is using the same ioctl() number. */ - if (root) - path = strappenda(root, "/etc/.pwd.lock"); - else - path = "/etc/.pwd.lock"; + if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) + return -ENOTTY; - fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600); - if (fd < 0) + if (mask == 0) + return 0; + + if (ioctl(fd, FS_IOC_GETFLAGS, &old_attr) < 0) return -errno; - r = fcntl(fd, F_SETLKW, &flock); - if (r < 0) { - safe_close(fd); + new_attr = (old_attr & ~mask) | (value & mask); + if (new_attr == old_attr) + return 0; + + if (ioctl(fd, FS_IOC_SETFLAGS, &new_attr) < 0) return -errno; - } - return fd; + return 1; } -int is_symlink(const char *path) { - struct stat info; +int chattr_path(const char *p, unsigned value, unsigned mask) { + _cleanup_close_ int fd = -1; + + assert(p); + + if (mask == 0) + return 0; - if (lstat(path, &info) < 0) + fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd < 0) return -errno; - return !!S_ISLNK(info.st_mode); + return chattr_fd(fd, value, mask); } -int is_dir(const char* path, bool follow) { +int read_attr_fd(int fd, unsigned *ret) { struct stat st; - int r; - if (follow) - r = stat(path, &st); - else - r = lstat(path, &st); - if (r < 0) + assert(fd >= 0); + + if (fstat(fd, &st) < 0) return -errno; - return !!S_ISDIR(st.st_mode); -} + if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) + return -ENOTTY; -int unquote_first_word(const char **p, char **ret, bool relax) { - _cleanup_free_ char *s = NULL; - size_t allocated = 0, sz = 0; + if (ioctl(fd, FS_IOC_GETFLAGS, ret) < 0) + return -errno; - enum { - START, - VALUE, - VALUE_ESCAPE, - SINGLE_QUOTE, - SINGLE_QUOTE_ESCAPE, - DOUBLE_QUOTE, - DOUBLE_QUOTE_ESCAPE, - SPACE, - } state = START; + return 0; +} + +int read_attr_path(const char *p, unsigned *ret) { + _cleanup_close_ int fd = -1; assert(p); - assert(*p); assert(ret); - /* Parses the first word of a string, and returns it in - * *ret. Removes all quotes in the process. When parsing fails - * (because of an uneven number of quotes or similar), leaves - * the pointer *p at the first invalid character. */ + fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd < 0) + return -errno; - for (;;) { - char c = **p; + return read_attr_fd(fd, ret); +} - switch (state) { +static size_t nul_length(const uint8_t *p, size_t sz) { + size_t n = 0; - case START: - if (c == 0) - goto finish; - else if (strchr(WHITESPACE, c)) - break; + while (sz > 0) { + if (*p != 0) + break; - state = VALUE; - /* fallthrough */ + n++; + p++; + sz--; + } - case VALUE: - if (c == 0) - goto finish; - else if (c == '\'') - state = SINGLE_QUOTE; - else if (c == '\\') - state = VALUE_ESCAPE; - else if (c == '\"') - state = DOUBLE_QUOTE; - else if (strchr(WHITESPACE, c)) - state = SPACE; - else { - if (!GREEDY_REALLOC(s, allocated, sz+2)) - return -ENOMEM; + return n; +} - s[sz++] = c; - } +ssize_t sparse_write(int fd, const void *p, size_t sz, size_t run_length) { + const uint8_t *q, *w, *e; + ssize_t l; - break; + q = w = p; + e = q + sz; + while (q < e) { + size_t n; - case VALUE_ESCAPE: - if (c == 0) { - if (relax) - goto finish; - return -EINVAL; + n = nul_length(q, e - q); + + /* If there are more than the specified run length of + * NUL bytes, or if this is the beginning or the end + * of the buffer, then seek instead of write */ + if ((n > run_length) || + (n > 0 && q == p) || + (n > 0 && q + n >= e)) { + if (q > w) { + l = write(fd, w, q - w); + if (l < 0) + return -errno; + if (l != q -w) + return -EIO; } - if (!GREEDY_REALLOC(s, allocated, sz+2)) - return -ENOMEM; + if (lseek(fd, n, SEEK_CUR) == (off_t) -1) + return -errno; - s[sz++] = c; - state = VALUE; + q += n; + w = q; + } else if (n > 0) + q += n; + else + q ++; + } - break; + if (q > w) { + l = write(fd, w, q - w); + if (l < 0) + return -errno; + if (l != q - w) + return -EIO; + } - case SINGLE_QUOTE: - if (c == 0) { - if (relax) - goto finish; - return -EINVAL; - } else if (c == '\'') - state = VALUE; - else if (c == '\\') - state = SINGLE_QUOTE_ESCAPE; - else { - if (!GREEDY_REALLOC(s, allocated, sz+2)) - return -ENOMEM; + return q - (const uint8_t*) p; +} - s[sz++] = c; - } +void sigkill_wait(pid_t *pid) { + if (!pid) + return; + if (*pid <= 1) + return; - break; + if (kill(*pid, SIGKILL) > 0) + (void) wait_for_terminate(*pid, NULL); +} - case SINGLE_QUOTE_ESCAPE: - if (c == 0) { - if (relax) - goto finish; - return -EINVAL; - } +int syslog_parse_priority(const char **p, int *priority, bool with_facility) { + int a = 0, b = 0, c = 0; + int k; - if (!GREEDY_REALLOC(s, allocated, sz+2)) - return -ENOMEM; + assert(p); + assert(*p); + assert(priority); - s[sz++] = c; - state = SINGLE_QUOTE; - break; + if ((*p)[0] != '<') + return 0; - case DOUBLE_QUOTE: - if (c == 0) - return -EINVAL; - else if (c == '\"') - state = VALUE; - else if (c == '\\') - state = DOUBLE_QUOTE_ESCAPE; - else { - if (!GREEDY_REALLOC(s, allocated, sz+2)) - return -ENOMEM; + if (!strchr(*p, '>')) + return 0; - s[sz++] = c; - } + if ((*p)[2] == '>') { + c = undecchar((*p)[1]); + k = 3; + } else if ((*p)[3] == '>') { + b = undecchar((*p)[1]); + c = undecchar((*p)[2]); + k = 4; + } else if ((*p)[4] == '>') { + a = undecchar((*p)[1]); + b = undecchar((*p)[2]); + c = undecchar((*p)[3]); + k = 5; + } else + return 0; - break; + if (a < 0 || b < 0 || c < 0 || + (!with_facility && (a || b || c > 7))) + return 0; - case DOUBLE_QUOTE_ESCAPE: - if (c == 0) { - if (relax) - goto finish; - return -EINVAL; - } + if (with_facility) + *priority = a*100 + b*10 + c; + else + *priority = (*priority & LOG_FACMASK) | c; - if (!GREEDY_REALLOC(s, allocated, sz+2)) - return -ENOMEM; + *p += k; + return 1; +} - s[sz++] = c; - state = DOUBLE_QUOTE; - break; +ssize_t string_table_lookup(const char * const *table, size_t len, const char *key) { + size_t i; - case SPACE: - if (c == 0) - goto finish; - if (!strchr(WHITESPACE, c)) - goto finish; + if (!key) + return -1; - break; - } + for (i = 0; i < len; ++i) + if (streq_ptr(table[i], key)) + return (ssize_t)i; - (*p) ++; - } + return -1; +} -finish: - if (!s) { - *ret = NULL; - return 0; - } +void cmsg_close_all(struct msghdr *mh) { + struct cmsghdr *cmsg; - s[sz] = 0; - *ret = s; - s = NULL; + assert(mh); - return 1; + for (cmsg = CMSG_FIRSTHDR(mh); cmsg; cmsg = CMSG_NXTHDR(mh, cmsg)) + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) + close_many((int*) CMSG_DATA(cmsg), (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int)); } -int unquote_many_words(const char **p, ...) { - va_list ap; - char **l; - int n = 0, i, c, r; +int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) { + struct stat buf; + int ret; - /* Parses a number of words from a string, stripping any - * quotes if necessary. */ + ret = renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE); + if (ret >= 0) + return 0; - assert(p); + /* Even though renameat2() exists since Linux 3.15, btrfs added + * support for it later. If it is not implemented, fallback to another + * method. */ + if (errno != EINVAL) + return -errno; - /* Count how many words are expected */ - va_start(ap, p); - for (;;) { - if (!va_arg(ap, char **)) - break; - n++; + /* The link()/unlink() fallback does not work on directories. But + * renameat() without RENAME_NOREPLACE gives the same semantics on + * directories, except when newpath is an *empty* directory. This is + * good enough. */ + ret = fstatat(olddirfd, oldpath, &buf, AT_SYMLINK_NOFOLLOW); + if (ret >= 0 && S_ISDIR(buf.st_mode)) { + ret = renameat(olddirfd, oldpath, newdirfd, newpath); + return ret >= 0 ? 0 : -errno; } - va_end(ap); - if (n <= 0) - return 0; + /* If it is not a directory, use the link()/unlink() fallback. */ + ret = linkat(olddirfd, oldpath, newdirfd, newpath, 0); + if (ret < 0) + return -errno; - /* Read all words into a temporary array */ - l = newa0(char*, n); - for (c = 0; c < n; c++) { + ret = unlinkat(olddirfd, oldpath, 0); + if (ret < 0) { + /* backup errno before the following unlinkat() alters it */ + ret = errno; + (void) unlinkat(newdirfd, newpath, 0); + errno = ret; + return -errno; + } - r = unquote_first_word(p, &l[c], false); - if (r < 0) { - int j; + return 0; +} - for (j = 0; j < c; j++) - free(l[j]); +char *shell_maybe_quote(const char *s) { + const char *p; + char *r, *t; - return r; - } + assert(s); - if (r == 0) + /* Encloses a string in double quotes if necessary to make it + * OK as shell string. */ + + for (p = s; *p; p++) + if (*p <= ' ' || + *p >= 127 || + strchr(SHELL_NEED_QUOTES, *p)) break; - } - /* If we managed to parse all words, return them in the passed - * in parameters */ - va_start(ap, p); - for (i = 0; i < n; i++) { - char **v; + if (!*p) + return strdup(s); - v = va_arg(ap, char **); - assert(v); + r = new(char, 1+strlen(s)*2+1+1); + if (!r) + return NULL; - *v = l[i]; + t = r; + *(t++) = '"'; + t = mempcpy(t, s, p - s); + + for (; *p; p++) { + + if (strchr(SHELL_NEED_ESCAPE, *p)) + *(t++) = '\\'; + + *(t++) = *p; } - va_end(ap); - return c; -} + *(t++)= '"'; + *t = 0; -int free_and_strdup(char **p, const char *s) { - char *t; + return r; +} - assert(p); +int parse_mode(const char *s, mode_t *ret) { + char *x; + long l; - /* Replaces a string pointer with an strdup()ed new string, - * possibly freeing the old one. */ + assert(s); + assert(ret); - if (s) { - t = strdup(s); - if (!t) - return -ENOMEM; - } else - t = NULL; + errno = 0; + l = strtol(s, &x, 8); + if (errno != 0) + return -errno; - free(*p); - *p = t; + if (!x || x == s || *x) + return -EINVAL; + if (l < 0 || l > 07777) + return -ERANGE; + *ret = (mode_t) l; return 0; } -int sethostname_idempotent(const char *s) { - int r; - char buf[HOST_NAME_MAX + 1] = {}; +int mount_move_root(const char *path) { + assert(path); - assert(s); + if (chdir(path) < 0) + return -errno; - r = gethostname(buf, sizeof(buf)); - if (r < 0) + if (mount(path, "/", NULL, MS_MOVE, NULL) < 0) return -errno; - if (streq(buf, s)) - return 0; + if (chroot(".") < 0) + return -errno; - r = sethostname(s, strlen(s)); - if (r < 0) + if (chdir("/") < 0) return -errno; - return 1; + return 0; }