X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fshared%2Futil.c;h=1fc6c5aa1adf63476e1c7406b527b51f2896bd1f;hp=969ef2bb90a759409178ff109c29bc2c11a740fa;hb=840292befd6ad78e018f5ea16bec80e83d910071;hpb=e985665d2d226cb42b52bfcad6fd5b1586ad57d7 diff --git a/src/shared/util.c b/src/shared/util.c index 969ef2bb9..285a263cd 100644 --- a/src/shared/util.c +++ b/src/shared/util.c @@ -41,7 +41,6 @@ #include #include #include -#include #include #include #include @@ -59,6 +58,13 @@ #include #include #include +#include +#include +#undef basename + +#ifdef HAVE_SYS_AUXV_H +#include +#endif #include "macro.h" #include "util.h" @@ -70,6 +76,13 @@ #include "path-util.h" #include "exit-status.h" #include "hashmap.h" +#include "env-util.h" +#include "fileio.h" +#include "device-nodes.h" +#include "utf8.h" +#include "gunicode.h" +#include "virt.h" +#include "def.h" int saved_argc = 0; char **saved_argv = NULL; @@ -78,7 +91,7 @@ static volatile unsigned cached_columns = 0; static volatile unsigned cached_lines = 0; size_t page_size(void) { - static __thread size_t pgsz = 0; + static thread_local size_t pgsz = 0; long r; if (_likely_(pgsz > 0)) @@ -125,40 +138,6 @@ char* endswith(const char *s, const char *postfix) { return (char*) s + sl - pl; } -char* startswith(const char *s, const char *prefix) { - const char *a, *b; - - assert(s); - assert(prefix); - - a = s, b = prefix; - for (;;) { - if (*b == 0) - return (char*) a; - if (*a != *b) - return NULL; - - a++, b++; - } -} - -char* startswith_no_case(const char *s, const char *prefix) { - const char *a, *b; - - assert(s); - assert(prefix); - - a = s, b = prefix; - for (;;) { - if (*b == 0) - return (char*) a; - if (tolower(*a) != tolower(*b)) - return NULL; - - a++, b++; - } -} - bool first_word(const char *s, const char *word) { size_t sl, wl; @@ -182,44 +161,62 @@ bool first_word(const char *s, const char *word) { } int close_nointr(int fd) { - assert(fd >= 0); - - for (;;) { - int r; + int r; - r = close(fd); - if (r >= 0) - return r; + assert(fd >= 0); + r = close(fd); - if (errno != EINTR) - return -errno; - } + /* Just ignore EINTR; a retry loop is the wrong + * thing to do on Linux. + * + * http://lkml.indiana.edu/hypermail/linux/kernel/0509.1/0877.html + * https://bugzilla.gnome.org/show_bug.cgi?id=682819 + * http://utcc.utoronto.ca/~cks/space/blog/unix/CloseEINTR + * https://sites.google.com/site/michaelsafyan/software-engineering/checkforeintrwheninvokingclosethinkagain + */ + if (_unlikely_(r < 0 && errno == EINTR)) + return 0; + else if (r >= 0) + return r; + else + return -errno; } void close_nointr_nofail(int fd) { - int saved_errno = errno; + PROTECT_ERRNO; /* like close_nointr() but cannot fail, and guarantees errno * is unchanged */ assert_se(close_nointr(fd) == 0); - - errno = saved_errno; } void close_many(const int fds[], unsigned n_fd) { unsigned i; + assert(fds || n_fd <= 0); + for (i = 0; i < n_fd; i++) close_nointr_nofail(fds[i]); } +int unlink_noerrno(const char *path) { + PROTECT_ERRNO; + int r; + + r = unlink(path); + if (r < 0) + return -errno; + + return 0; +} + int parse_boolean(const char *v) { assert(v); - if (streq(v, "1") || v[0] == 'y' || v[0] == 'Y' || v[0] == 't' || v[0] == 'T' || !strcasecmp(v, "on")) + if (streq(v, "1") || v[0] == 'y' || v[0] == 'Y' || v[0] == 't' || v[0] == 'T' || strcaseeq(v, "on")) return 1; - else if (streq(v, "0") || v[0] == 'n' || v[0] == 'N' || v[0] == 'f' || v[0] == 'F' || !strcasecmp(v, "off")) + else if (streq(v, "0") || v[0] == 'n' || v[0] == 'N' || v[0] == 'f' || v[0] == 'F' || strcaseeq(v, "off")) return 0; return -EINVAL; @@ -281,7 +278,7 @@ int safe_atou(const char *s, unsigned *ret_u) { l = strtoul(s, &x, 0); if (!x || x == s || *x || errno) - return errno ? -errno : -EINVAL; + return errno > 0 ? -errno : -EINVAL; if ((unsigned long) (unsigned) l != l) return -ERANGE; @@ -301,7 +298,7 @@ int safe_atoi(const char *s, int *ret_i) { l = strtol(s, &x, 0); if (!x || x == s || *x || errno) - return errno ? -errno : -EINVAL; + return errno > 0 ? -errno : -EINVAL; if ((long) (int) l != l) return -ERANGE; @@ -344,74 +341,63 @@ int safe_atolli(const char *s, long long int *ret_lli) { return 0; } -/* Split a string into words. */ -char *split(const char *c, size_t *l, const char *separator, char **state) { - char *current; +int safe_atod(const char *s, double *ret_d) { + char *x = NULL; + double d = 0; - current = *state ? *state : (char*) c; + assert(s); + assert(ret_d); - if (!*current || *c == 0) - return NULL; + RUN_WITH_LOCALE(LC_NUMERIC_MASK, "C") { + errno = 0; + d = strtod(s, &x); + } - current += strspn(current, separator); - *l = strcspn(current, separator); - *state = current+*l; + if (!x || x == s || *x || errno) + return errno ? -errno : -EINVAL; - return (char*) current; + *ret_d = (double) d; + return 0; } -/* Split a string into words, but consider strings enclosed in '' and - * "" as words even if they include spaces. */ -char *split_quoted(const char *c, size_t *l, char **state) { - char *current, *e; +static size_t strcspn_escaped(const char *s, const char *reject) { bool escaped = false; + size_t n; + + for (n=0; s[n]; n++) { + if (escaped) + escaped = false; + else if (s[n] == '\\') + escaped = true; + else if (strchr(reject, s[n])) + return n; + } + return n; +} + +/* Split a string into words. */ +char *split(const char *c, size_t *l, const char *separator, bool quoted, char **state) { + char *current; current = *state ? *state : (char*) c; if (!*current || *c == 0) return NULL; - current += strspn(current, WHITESPACE); - - if (*current == '\'') { - current ++; - - for (e = current; *e; e++) { - if (escaped) - escaped = false; - else if (*e == '\\') - escaped = true; - else if (*e == '\'') - break; - } - - *l = e-current; - *state = *e == 0 ? e : e+1; - } else if (*current == '\"') { - current ++; - - for (e = current; *e; e++) { - if (escaped) - escaped = false; - else if (*e == '\\') - escaped = true; - else if (*e == '\"') - break; - } + current += strspn(current, separator); + if (!*current) + return NULL; - *l = e-current; - *state = *e == 0 ? e : e+1; + if (quoted && strchr("\'\"", *current)) { + char quotechar = *(current++); + *l = strcspn_escaped(current, (char[]){quotechar, '\0'}); + *state = current+*l+1; + } else if (quoted) { + *l = strcspn_escaped(current, separator); + *state = current+*l; } else { - for (e = current; *e; e++) { - if (escaped) - escaped = false; - else if (*e == '\\') - escaped = true; - else if (strchr(WHITESPACE, *e)) - break; - } - *l = e-current; - *state = e; + *l = strcspn(current, separator); + *state = current+*l; } return (char*) current; @@ -419,24 +405,22 @@ char *split_quoted(const char *c, size_t *l, char **state) { int get_parent_of_pid(pid_t pid, pid_t *_ppid) { int r; - _cleanup_fclose_ FILE *f = NULL; - char fn[PATH_MAX], line[LINE_MAX], *p; + _cleanup_free_ char *line = NULL; long unsigned ppid; + const char *p; - assert(pid > 0); + assert(pid >= 0); assert(_ppid); - assert_se(snprintf(fn, sizeof(fn)-1, "/proc/%lu/stat", (unsigned long) pid) < (int) (sizeof(fn)-1)); - char_array_0(fn); - - f = fopen(fn, "re"); - if (!f) - return -errno; + if (pid == 0) { + *_ppid = getppid(); + return 0; + } - if (!fgets(line, sizeof(line), f)) { - r = feof(f) ? -EIO : -errno; + 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 @@ -463,25 +447,17 @@ int get_parent_of_pid(pid_t pid, pid_t *_ppid) { } int get_starttime_of_pid(pid_t pid, unsigned long long *st) { - _cleanup_fclose_ FILE *f = NULL; - char fn[PATH_MAX], line[LINE_MAX], *p; + int r; + _cleanup_free_ char *line = NULL; + const char *p; - assert(pid > 0); + assert(pid >= 0); assert(st); - assert_se(snprintf(fn, sizeof(fn)-1, "/proc/%lu/stat", (unsigned long) pid) < (int) (sizeof(fn)-1)); - char_array_0(fn); - - f = fopen(fn, "re"); - if (!f) - return -errno; - - if (!fgets(line, sizeof(line), f)) { - if (ferror(f)) - return -errno; - - return -EIO; - } + 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 @@ -520,31 +496,6 @@ int get_starttime_of_pid(pid_t pid, unsigned long long *st) { return 0; } -int write_one_line_file(const char *fn, const char *line) { - _cleanup_fclose_ FILE *f = NULL; - - assert(fn); - assert(line); - - f = fopen(fn, "we"); - if (!f) - return -errno; - - errno = 0; - if (fputs(line, f) < 0) - return errno ? -errno : -EIO; - - if (!endswith(line, "\n")) - fputc('\n', f); - - fflush(f); - - if (ferror(f)) - return errno ? -errno : -EIO; - - return 0; -} - int fchmod_umask(int fd, mode_t m) { mode_t u; int r; @@ -556,540 +507,226 @@ int fchmod_umask(int fd, mode_t m) { return r; } -int write_one_line_file_atomic(const char *fn, const char *line) { - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char *p = NULL; +char *truncate_nl(char *s) { + assert(s); + + s[strcspn(s, NEWLINE)] = 0; + return s; +} + +int get_process_state(pid_t pid) { + const char *p; + char state; int r; + _cleanup_free_ char *line = NULL; - assert(fn); - assert(line); + assert(pid >= 0); - r = fopen_temporary(fn, &f, &p); + p = procfs_file_alloca(pid, "stat"); + r = read_one_line_file(p, &line); if (r < 0) return r; - fchmod_umask(fileno(f), 0644); + p = strrchr(line, ')'); + if (!p) + return -EIO; + + p++; - errno = 0; - if (fputs(line, f) < 0) { - r = -errno; - goto finish; - } + if (sscanf(p, " %c", &state) != 1) + return -EIO; - if (!endswith(line, "\n")) - fputc('\n', f); + return (unsigned char) state; +} - fflush(f); +int get_process_comm(pid_t pid, char **name) { + const char *p; + int r; - if (ferror(f)) - r = errno ? -errno : -EIO; - else { - if (rename(p, fn) < 0) - r = -errno; - else - r = 0; - } + assert(name); + assert(pid >= 0); -finish: - if (r < 0) - unlink(p); + p = procfs_file_alloca(pid, "comm"); + + r = read_one_line_file(p, name); + if (r == -ENOENT) + return -ESRCH; return r; } -int read_one_line_file(const char *fn, char **line) { +int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line) { _cleanup_fclose_ FILE *f = NULL; - char t[LINE_MAX], *c; + char *r = NULL, *k; + const char *p; + int c; - assert(fn); assert(line); + assert(pid >= 0); + + p = procfs_file_alloca(pid, "cmdline"); - f = fopen(fn, "re"); + f = fopen(p, "re"); if (!f) return -errno; - if (!fgets(t, sizeof(t), f)) { + if (max_length == 0) { + size_t len = 0, allocated = 0; - if (ferror(f)) - return errno ? -errno : -EIO; + while ((c = getc(f)) != EOF) { - t[0] = 0; - } + if (!GREEDY_REALLOC(r, allocated, len+2)) { + free(r); + return -ENOMEM; + } - c = strdup(t); - if (!c) - return -ENOMEM; - truncate_nl(c); + r[len++] = isprint(c) ? c : ' '; + } - *line = c; - return 0; -} + if (len > 0) + r[len-1] = 0; -int read_full_file(const char *fn, char **contents, size_t *size) { - _cleanup_fclose_ FILE *f = NULL; - size_t n, l; - _cleanup_free_ char *buf = NULL; - struct stat st; + } else { + bool space = false; + size_t left; - assert(fn); - assert(contents); + r = new(char, max_length); + if (!r) + return -ENOMEM; - f = fopen(fn, "re"); - if (!f) - return -errno; + k = r; + left = max_length; + while ((c = getc(f)) != EOF) { - if (fstat(fileno(f), &st) < 0) - return -errno; + if (isprint(c)) { + if (space) { + if (left <= 4) + break; - /* Safety check */ - if (st.st_size > 4*1024*1024) - return -E2BIG; + *(k++) = ' '; + left--; + space = false; + } - n = st.st_size > 0 ? st.st_size : LINE_MAX; - l = 0; + if (left <= 4) + break; - for (;;) { - char *t; - size_t k; + *(k++) = (char) c; + left--; + } else + space = true; + } - t = realloc(buf, n+1); - if (!t) - return -ENOMEM; + if (left <= 4) { + size_t n = MIN(left-1, 3U); + memcpy(k, "...", n); + k[n] = 0; + } else + *k = 0; + } - buf = t; - k = fread(buf + l, 1, n - l, f); + /* Kernel threads have no argv[] */ + if (r == NULL || r[0] == 0) { + _cleanup_free_ char *t = NULL; + int h; - if (k <= 0) { - if (ferror(f)) - return -errno; + free(r); - break; - } + if (!comm_fallback) + return -ENOENT; - l += k; - n *= 2; + h = get_process_comm(pid, &t); + if (h < 0) + return h; - /* Safety check */ - if (n > 4*1024*1024) - return -E2BIG; + r = strjoin("[", t, "]", NULL); + if (!r) + return -ENOMEM; } - buf[l] = 0; - *contents = buf; - buf = NULL; - - if (size) - *size = l; - + *line = r; return 0; } -int parse_env_file( - const char *fname, - const char *separator, ...) { +int is_kernel_thread(pid_t pid) { + const char *p; + size_t count; + char c; + bool eof; + FILE *f; - int r = 0; - char *contents = NULL, *p; + if (pid == 0) + return 0; - assert(fname); - assert(separator); + assert(pid > 0); - if ((r = read_full_file(fname, &contents, NULL)) < 0) - return r; + p = procfs_file_alloca(pid, "cmdline"); + f = fopen(p, "re"); + if (!f) + return -errno; - p = contents; - for (;;) { - const char *key = NULL; + count = fread(&c, 1, 1, f); + eof = feof(f); + fclose(f); - p += strspn(p, separator); - p += strspn(p, WHITESPACE); + /* Kernel threads have an empty cmdline */ - if (!*p) - break; - - if (!strchr(COMMENTS, *p)) { - va_list ap; - char **value; - - va_start(ap, separator); - while ((key = va_arg(ap, char *))) { - size_t n; - char *v; - - value = va_arg(ap, char **); - - n = strlen(key); - if (strncmp(p, key, n) != 0 || - p[n] != '=') - continue; - - p += n + 1; - n = strcspn(p, separator); - - if (n >= 2 && - strchr(QUOTES, p[0]) && - p[n-1] == p[0]) - v = strndup(p+1, n-2); - else - v = strndup(p, n); - - if (!v) { - r = -ENOMEM; - va_end(ap); - goto fail; - } - - if (v[0] == '\0') { - /* return empty value strings as NULL */ - free(v); - v = NULL; - } - - free(*value); - *value = v; - - p += n; - - r ++; - break; - } - va_end(ap); - } - - if (!key) - p += strcspn(p, separator); - } - -fail: - free(contents); - return r; -} - -int load_env_file(const char *fname, - char ***rl) { - - FILE _cleanup_fclose_ *f; - char *b; - char _cleanup_free_ *c = NULL; - char _cleanup_strv_free_ **m = NULL; - - assert(fname); - assert(rl); - - f = fopen(fname, "re"); - if (!f) - return -errno; - - while (!feof(f)) { - char l[LINE_MAX], *p, *u, *cs; - char **t; - - if (!fgets(l, sizeof(l), f)) { - if (!feof(f)) - return -errno; - else if (!c) - break; - } - - cs = endswith(l, "\\\n"); - if (cs) { - *cs = '\0'; - b = strappend(c, l); - if (!b) - return log_oom(); - - free(c); - c = b; - *l = '\0'; - continue; - } - - if (c) { - b = strappend(c, l); - if (!b) - return log_oom(); - - free(c); - c = b; - } - - p = strstrip(c ? c : l); - - if (!*p) - continue; - - if (strchr(COMMENTS, *p)) - continue; - - u = normalize_env_assignment(p); - if (!u) - return log_oom(); - - free(c); - c = NULL; - - t = strv_append(m, u); - free(u); - - if (!t) - return log_oom(); - - strv_free(m); - m = t; - } - - *rl = m; - m = NULL; + if (count <= 0) + return eof ? 1 : -errno; return 0; } -int write_env_file(const char *fname, char **l) { - char **i, *p; - FILE *f; - int r; - - r = fopen_temporary(fname, &f, &p); - if (r < 0) - return r; - - fchmod_umask(fileno(f), 0644); - - errno = 0; - STRV_FOREACH(i, l) { - fputs(*i, f); - fputc('\n', f); - } - - fflush(f); - - if (ferror(f)) { - if (errno != 0) - r = -errno; - else - r = -EIO; - } else { - if (rename(p, fname) < 0) - r = -errno; - else - r = 0; - } - - if (r < 0) - unlink(p); - - fclose(f); - free(p); +int get_process_capeff(pid_t pid, char **capeff) { + const char *p; - return r; -} + assert(capeff); + assert(pid >= 0); -char *truncate_nl(char *s) { - assert(s); + p = procfs_file_alloca(pid, "status"); - s[strcspn(s, NEWLINE)] = 0; - return s; + return get_status_field(p, "\nCapEff:", capeff); } -int get_process_comm(pid_t pid, char **name) { +int get_process_exe(pid_t pid, char **name) { + const char *p; + char *d; int r; + assert(pid >= 0); assert(name); - if (pid == 0) - r = read_one_line_file("/proc/self/comm", name); - else { - char *p; - if (asprintf(&p, "/proc/%lu/comm", (unsigned long) pid) < 0) - return -ENOMEM; - - r = read_one_line_file(p, name); - free(p); - } - - return r; -} - -int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line) { - char *r = NULL, *k; - int c; - FILE *f; - - assert(line); - - if (pid == 0) - f = fopen("/proc/self/cmdline", "re"); - else { - char *p; - if (asprintf(&p, "/proc/%lu/cmdline", (unsigned long) pid) < 0) - return -ENOMEM; - - f = fopen(p, "re"); - free(p); - } - - if (!f) - return -errno; - if (max_length == 0) { - size_t len = 1; - while ((c = getc(f)) != EOF) { - k = realloc(r, len+1); - if (k == NULL) { - free(r); - fclose(f); - return -ENOMEM; - } - r = k; - r[len-1] = isprint(c) ? c : ' '; - r[len] = 0; - len++; - } - } else { - bool space = false; - size_t left; - r = new(char, max_length); - if (!r) { - fclose(f); - 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; - } - - fclose(f); - - /* Kernel threads have no argv[] */ - if (r == NULL || r[0] == 0) { - char *t; - int h; - - free(r); - - if (!comm_fallback) - return -ENOENT; - - h = get_process_comm(pid, &t); - if (h < 0) - return h; - - r = strjoin("[", t, "]", NULL); - free(t); - - if (!r) - return -ENOMEM; - } - - *line = r; - return 0; -} - -int is_kernel_thread(pid_t pid) { - char *p; - size_t count; - char c; - bool eof; - FILE *f; - - if (pid == 0) - return 0; - - if (asprintf(&p, "/proc/%lu/cmdline", (unsigned long) pid) < 0) - return -ENOMEM; - - f = fopen(p, "re"); - free(p); - - if (!f) - return -errno; - - count = fread(&c, 1, 1, f); - eof = feof(f); - fclose(f); + p = procfs_file_alloca(pid, "exe"); - /* Kernel threads have an empty cmdline */ + r = readlink_malloc(p, name); + if (r < 0) + return r == -ENOENT ? -ESRCH : r; - if (count <= 0) - return eof ? 1 : -errno; + d = endswith(*name, " (deleted)"); + if (d) + *d = '\0'; return 0; } -int get_process_exe(pid_t pid, char **name) { - int r; - - assert(name); - - if (pid == 0) - r = readlink_malloc("/proc/self/exe", name); - else { - char *p; - if (asprintf(&p, "/proc/%lu/exe", (unsigned long) pid) < 0) - return -ENOMEM; - - r = readlink_malloc(p, name); - free(p); - } - - return r; -} - static int get_process_id(pid_t pid, const char *field, uid_t *uid) { - char *p; - FILE *f; - int r; + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + const char *p; + assert(field); assert(uid); if (pid == 0) return getuid(); - if (asprintf(&p, "/proc/%lu/status", (unsigned long) pid) < 0) - return -ENOMEM; - + p = procfs_file_alloca(pid, "status"); f = fopen(p, "re"); - free(p); - if (!f) return -errno; - while (!feof(f)) { - char line[LINE_MAX], *l; - - if (!fgets(line, sizeof(line), f)) { - if (feof(f)) - break; - - r = -errno; - goto finish; - } + FOREACH_LINE(line, f, return -errno) { + char *l; l = strstrip(line); @@ -1099,17 +736,11 @@ static int get_process_id(pid_t pid, const char *field, uid_t *uid) { l[strcspn(l, WHITESPACE)] = 0; - r = parse_uid(l, uid); - goto finish; + return parse_uid(l, uid); } } - r = -EIO; - -finish: - fclose(f); - - return r; + return -EIO; } int get_process_uid(pid_t pid, uid_t *uid) { @@ -1117,6 +748,7 @@ int get_process_uid(pid_t pid, uid_t *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); } @@ -1155,28 +787,31 @@ char *strappend(const char *s, const char *suffix) { return strnappend(s, suffix, suffix ? strlen(suffix) : 0); } -int readlink_malloc(const char *p, char **r) { +int readlink_malloc(const char *p, char **ret) { size_t l = 100; + int r; assert(p); - assert(r); + assert(ret); for (;;) { char *c; ssize_t n; - if (!(c = new(char, l))) + c = new(char, l); + if (!c) return -ENOMEM; - if ((n = readlink(p, c, l-1)) < 0) { - int ret = -errno; + n = readlink(p, c, l-1); + if (n < 0) { + r = -errno; free(c); - return ret; + return r; } if ((size_t) n < l-1) { c[n] = 0; - *r = c; + *ret = c; return 0; } @@ -1186,18 +821,18 @@ int readlink_malloc(const char *p, char **r) { } int readlink_and_make_absolute(const char *p, char **r) { - char *target, *k; + _cleanup_free_ char *target = NULL; + char *k; int j; assert(p); assert(r); - if ((j = readlink_malloc(p, &target)) < 0) + j = readlink_malloc(p, &target); + if (j < 0) return j; k = file_in_same_dir(p, target); - free(target); - if (!k) return -ENOMEM; @@ -1232,15 +867,14 @@ int reset_all_signal_handlers(void) { int sig; for (sig = 1; sig < _NSIG; sig++) { - struct sigaction sa; + struct sigaction sa = { + .sa_handler = SIG_DFL, + .sa_flags = SA_RESTART, + }; if (sig == SIGKILL || sig == SIGSTOP) continue; - zero(sa); - sa.sa_handler = SIG_DFL; - sa.sa_flags = SA_RESTART; - /* On Linux the first two RT signals are reserved by * glibc, and sigaction() will return EINVAL for them. */ if ((sigaction(sig, &sa, NULL) < 0)) @@ -1371,7 +1005,6 @@ int rmdir_parents(const char *path, const char *stop) { return 0; } - char hexchar(int x) { static const char table[16] = "0123456789abcdef"; @@ -1392,6 +1025,49 @@ int unhexchar(char c) { return -1; } +char *hexmem(const void *p, size_t l) { + char *r, *z; + const uint8_t *x; + + z = r = malloc(l * 2 + 1); + if (!r) + return NULL; + + for (x = p; x < (const uint8_t*) p + l; x++) { + *(z++) = hexchar(*x >> 4); + *(z++) = hexchar(*x & 15); + } + + *z = 0; + return r; +} + +void *unhexmem(const char *p, size_t l) { + uint8_t *r, *z; + const char *x; + + assert(p); + + z = r = malloc((l + 1) / 2 + 1); + if (!r) + return NULL; + + for (x = p; x < p + l; x += 2) { + int a, b; + + a = unhexchar(x[0]); + if (x+1 < p + l) + b = unhexchar(x[1]); + else + b = 0; + + *(z++) = (uint8_t) a << 4 | (uint8_t) b; + } + + *z = 0; + return r; +} + char octchar(int x) { return '0' + (x & 7); } @@ -1660,65 +1336,6 @@ char *xescape(const char *s, const char *bad) { return r; } -char *bus_path_escape(const char *s) { - char *r, *t; - const char *f; - - assert(s); - - /* Escapes all chars that D-Bus' object path cannot deal - * with. Can be reverse with bus_path_unescape() */ - - if (!(r = new(char, strlen(s)*3+1))) - return NULL; - - for (f = s, t = r; *f; f++) { - - if (!(*f >= 'A' && *f <= 'Z') && - !(*f >= 'a' && *f <= 'z') && - !(*f >= '0' && *f <= '9')) { - *(t++) = '_'; - *(t++) = hexchar(*f >> 4); - *(t++) = hexchar(*f); - } else - *(t++) = *f; - } - - *t = 0; - - return r; -} - -char *bus_path_unescape(const char *f) { - char *r, *t; - - assert(f); - - if (!(r = strdup(f))) - return NULL; - - for (t = r; *f; f++) { - - if (*f == '_') { - int a, b; - - if ((a = unhexchar(f[1])) < 0 || - (b = unhexchar(f[2])) < 0) { - /* Invalid escape code, let's take it literal then */ - *(t++) = '_'; - } else { - *(t++) = (char) ((a << 4) | b); - f += 2; - } - } else - *(t++) = *f; - } - - *t = 0; - - return r; -} - char *ascii_strlower(char *t) { char *p; @@ -1731,7 +1348,7 @@ char *ascii_strlower(char *t) { return t; } -static bool ignore_file_allow_backup(const char *filename) { +_pure_ static bool ignore_file_allow_backup(const char *filename) { assert(filename); return @@ -1794,7 +1411,7 @@ int fd_cloexec(int fd, bool cloexec) { return 0; } -static bool fd_in_set(int fd, const int fdset[], unsigned n_fdset) { +_pure_ static bool fd_in_set(int fd, const int fdset[], unsigned n_fdset) { unsigned i; assert(n_fdset == 0 || fdset); @@ -1882,6 +1499,7 @@ bool fstype_is_network(const char *fstype) { "cifs\0" "smbfs\0" "ncpfs\0" + "ncp\0" "nfs\0" "nfs4\0" "gfs\0" @@ -2112,8 +1730,10 @@ int open_terminal(const char *name, int mode) { * https://bugs.launchpad.net/ubuntu/+source/linux/+bug/554172/comments/245 */ + assert(!(mode & O_CREAT)); + for (;;) { - fd = open(name, mode); + fd = open(name, mode, 0); if (fd >= 0) break; @@ -2146,29 +1766,28 @@ int open_terminal(const char *name, int mode) { } int flush_fd(int fd) { - struct pollfd pollfd; - - zero(pollfd); - pollfd.fd = fd; - pollfd.events = POLLIN; + struct pollfd pollfd = { + .fd = fd, + .events = POLLIN, + }; for (;;) { char buf[LINE_MAX]; ssize_t l; int r; - if ((r = poll(&pollfd, 1, 0)) < 0) { - + r = poll(&pollfd, 1, 0); + if (r < 0) { if (errno == EINTR) continue; return -errno; - } - if (r == 0) + } else if (r == 0) return 0; - if ((l = read(fd, buf, sizeof(buf))) < 0) { + l = read(fd, buf, sizeof(buf)); + if (l < 0) { if (errno == EINTR) continue; @@ -2177,9 +1796,7 @@ int flush_fd(int fd) { return 0; return -errno; - } - - if (l <= 0) + } else if (l == 0) return 0; } } @@ -2193,7 +1810,6 @@ int acquire_terminal( int fd = -1, notify = -1, r = 0, wd = -1; usec_t ts = 0; - struct sigaction sa_old, sa_new; assert(name); @@ -2228,6 +1844,11 @@ int acquire_terminal( } for (;;) { + struct sigaction sa_old, sa_new = { + .sa_handler = SIG_IGN, + .sa_flags = SA_RESTART, + }; + if (notify >= 0) { r = flush_fd(notify); if (r < 0) @@ -2243,9 +1864,6 @@ int acquire_terminal( /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed * if we already own the tty. */ - zero(sa_new); - sa_new.sa_handler = SIG_IGN; - sa_new.sa_flags = SA_RESTART; assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0); /* First, try to get the tty */ @@ -2352,18 +1970,19 @@ fail: } int release_terminal(void) { - int r = 0, fd; - struct sigaction sa_old, sa_new; + int r = 0; + struct sigaction sa_old, sa_new = { + .sa_handler = SIG_IGN, + .sa_flags = SA_RESTART, + }; + _cleanup_close_ int fd; - if ((fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_NDELAY|O_CLOEXEC)) < 0) + fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_NDELAY|O_CLOEXEC); + if (fd < 0) return -errno; /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed * by our own TIOCNOTTY */ - - zero(sa_new); - sa_new.sa_handler = SIG_IGN; - sa_new.sa_flags = SA_RESTART; assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0); if (ioctl(fd, TIOCNOTTY) < 0) @@ -2371,7 +1990,6 @@ int release_terminal(void) { assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0); - close_nointr_nofail(fd); return r; } @@ -2389,13 +2007,13 @@ int sigaction_many(const struct sigaction *sa, ...) { } int ignore_signals(int sig, ...) { - struct sigaction sa; + struct sigaction sa = { + .sa_handler = SIG_IGN, + .sa_flags = SA_RESTART, + }; va_list ap; int r = 0; - zero(sa); - sa.sa_handler = SIG_IGN; - sa.sa_flags = SA_RESTART; if (sigaction(sig, &sa, NULL) < 0) r = -errno; @@ -2410,14 +2028,13 @@ int ignore_signals(int sig, ...) { } int default_signals(int sig, ...) { - struct sigaction sa; + struct sigaction sa = { + .sa_handler = SIG_DFL, + .sa_flags = SA_RESTART, + }; va_list ap; int r = 0; - zero(sa); - sa.sa_handler = SIG_DFL; - sa.sa_flags = SA_RESTART; - if (sigaction(sig, &sa, NULL) < 0) r = -errno; @@ -2449,44 +2066,31 @@ int close_pipe(int p[]) { } ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) { - uint8_t *p; + uint8_t *p = buf; ssize_t n = 0; assert(fd >= 0); assert(buf); - p = buf; - while (nbytes > 0) { ssize_t k; - if ((k = read(fd, p, nbytes)) <= 0) { - - if (k < 0 && errno == EINTR) - continue; - - if (k < 0 && errno == EAGAIN && do_poll) { - struct pollfd pollfd; - - zero(pollfd); - pollfd.fd = fd; - pollfd.events = POLLIN; - - if (poll(&pollfd, 1, -1) < 0) { - if (errno == EINTR) - continue; + k = read(fd, p, nbytes); + if (k < 0 && errno == EINTR) + continue; - return n > 0 ? n : -errno; - } + if (k < 0 && errno == EAGAIN && do_poll) { - if (pollfd.revents != POLLIN) - return n > 0 ? n : -EIO; + /* We knowingly ignore any return value here, + * and expect that any error/EOF is reported + * via read() */ - continue; - } + fd_wait_for_event(fd, POLLIN, (usec_t) -1); + continue; + } + if (k <= 0) return n > 0 ? n : (k < 0 ? -errno : 0); - } p += k; nbytes -= k; @@ -2497,45 +2101,31 @@ ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) { } ssize_t loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) { - const uint8_t *p; + const uint8_t *p = buf; ssize_t n = 0; assert(fd >= 0); assert(buf); - p = buf; - while (nbytes > 0) { ssize_t k; k = write(fd, p, nbytes); - if (k <= 0) { - - if (k < 0 && errno == EINTR) - continue; - - if (k < 0 && errno == EAGAIN && do_poll) { - struct pollfd pollfd; - - zero(pollfd); - pollfd.fd = fd; - pollfd.events = POLLOUT; - - if (poll(&pollfd, 1, -1) < 0) { - if (errno == EINTR) - continue; + if (k < 0 && errno == EINTR) + continue; - return n > 0 ? n : -errno; - } + if (k < 0 && errno == EAGAIN && do_poll) { - if (pollfd.revents != POLLOUT) - return n > 0 ? n : -EIO; + /* We knowingly ignore any return value here, + * and expect that any error/EOF is reported + * via write() */ - continue; - } + fd_wait_for_event(fd, POLLOUT, (usec_t) -1); + continue; + } + if (k <= 0) return n > 0 ? n : (k < 0 ? -errno : 0); - } p += k; nbytes -= k; @@ -2545,37 +2135,78 @@ ssize_t loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) { return n; } -int parse_bytes(const char *t, off_t *bytes) { - static const struct { +int parse_size(const char *t, off_t base, off_t *size) { + + /* 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. */ + + struct table { const char *suffix; - off_t factor; - } table[] = { - { "B", 1 }, - { "K", 1024ULL }, - { "M", 1024ULL*1024ULL }, - { "G", 1024ULL*1024ULL*1024ULL }, - { "T", 1024ULL*1024ULL*1024ULL*1024ULL }, - { "P", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, + unsigned long long factor; + }; + + 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 }, + }; + + 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 }, }; + const struct table *table; const char *p; - off_t r = 0; + unsigned long long r = 0; + unsigned n_entries, start_pos = 0; assert(t); - assert(bytes); + assert(base == 1000 || base == 1024); + assert(size); + + if (base == 1000) { + table = si; + n_entries = ELEMENTSOF(si); + } else { + table = iec; + n_entries = ELEMENTSOF(iec); + } p = t; do { long long l; + unsigned long long l2; + double frac = 0; char *e; unsigned i; errno = 0; l = strtoll(p, &e, 10); - if (errno != 0) + if (errno > 0) return -errno; if (l < 0) @@ -2584,21 +2215,51 @@ int parse_bytes(const char *t, off_t *bytes) { if (e == p) return -EINVAL; + if (*e == '.') { + e++; + if (*e >= '0' && *e <= '9') { + char *e2; + + /* strotoull itself would accept space/+/- */ + l2 = strtoull(e, &e2, 10); + + if (errno == ERANGE) + return -errno; + + /* Ignore failure. E.g. 10.M is valid */ + frac = l2; + for (; e < e2; e++) + frac /= 10; + } + } + e += strspn(e, WHITESPACE); - for (i = 0; i < ELEMENTSOF(table); i++) + for (i = start_pos; i < n_entries; i++) if (startswith(e, table[i].suffix)) { - r += (off_t) l * table[i].factor; + 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; + + r += tmp; + if ((unsigned long long) (off_t) r != r) + return -ERANGE; + p = e + strlen(table[i].suffix); + + start_pos = i + 1; break; } - if (i >= ELEMENTSOF(table)) + if (i >= n_entries) return -EINVAL; - } while (*p != 0); + } while (*p); - *bytes = r; + *size = r; return 0; } @@ -2645,7 +2306,6 @@ bool is_device_path(const char *path) { int dir_is_empty(const char *path) { _cleanup_closedir_ DIR *d; - int r; d = opendir(path); if (!d) @@ -2653,11 +2313,11 @@ int dir_is_empty(const char *path) { for (;;) { struct dirent *de; - union dirent_storage buf; - r = readdir_r(d, &buf.de, &de); - if (r > 0) - return -r; + errno = 0; + de = readdir(d); + if (!de && errno != 0) + return -errno; if (!de) return 1; @@ -2667,23 +2327,78 @@ int dir_is_empty(const char *path) { } } -unsigned long long random_ull(void) { +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) { _cleanup_close_ int fd; - uint64_t ull; - ssize_t r; + ssize_t k; fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY); if (fd < 0) - goto fallback; + return errno == ENOENT ? -ENOSYS : -errno; + + k = loop_read(fd, p, n, true); + if (k < 0) + return (int) k; + if ((size_t) k != n) + return -EIO; + + return 0; +} + +void random_bytes(void *p, size_t n) { + static bool srand_called = false; + uint8_t *q; + int r; + + r = dev_urandom(p, n); + if (r >= 0) + return; + + /* If some idiot made /dev/urandom unavailable to us, he'll + * get a PRNG instead. */ + + if (!srand_called) { + unsigned x = 0; + +#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... */ + + void *auxv; - r = loop_read(fd, &ull, sizeof(ull), true); - if (r != sizeof(ull)) - goto fallback; + auxv = (void*) getauxval(AT_RANDOM); + if (auxv) + x ^= *(unsigned*) auxv; +#endif + + x ^= (unsigned) now(CLOCK_REALTIME); + x ^= (unsigned) gettid(); - return ull; + srand(x); + srand_called = true; + } -fallback: - return random() * RAND_MAX + random(); + for (q = p; q < (uint8_t*) p + n; q ++) + *q = rand(); } void rename_process(const char name[8]) { @@ -2712,7 +2427,7 @@ void rename_process(const char name[8]) { if (!saved_argv[i]) break; - memset(saved_argv[i], 0, strlen(saved_argv[i])); + memzero(saved_argv[i], strlen(saved_argv[i])); } } } @@ -2804,7 +2519,7 @@ int getttyname_malloc(int fd, char **r) { assert(r); k = ttyname_r(fd, path, sizeof(path)); - if (k != 0) + if (k > 0) return -k; char_array_0(path); @@ -2835,26 +2550,17 @@ int getttyname_harder(int fd, char **r) { } int get_ctty_devnr(pid_t pid, dev_t *d) { - int k; - char line[LINE_MAX], *p, *fn; + int r; + _cleanup_free_ char *line = NULL; + const char *p; unsigned long ttynr; - FILE *f; - - if (asprintf(&fn, "/proc/%lu/stat", (unsigned long) (pid <= 0 ? getpid() : pid)) < 0) - return -ENOMEM; - - f = fopen(fn, "re"); - free(fn); - if (!f) - return -errno; - if (!fgets(line, sizeof(line), f)) { - k = feof(f) ? -EIO : -errno; - fclose(f); - return k; - } + assert(pid >= 0); - fclose(f); + p = procfs_file_alloca(pid, "stat"); + r = read_one_line_file(p, &line); + if (r < 0) + return r; p = strrchr(line, ')'); if (!p) @@ -2874,14 +2580,18 @@ int get_ctty_devnr(pid_t pid, dev_t *d) { if (major(ttynr) == 0 && minor(ttynr) == 0) return -ENOENT; - *d = (dev_t) ttynr; + if (d) + *d = (dev_t) ttynr; + return 0; } int get_ctty(pid_t pid, dev_t *_devnr, char **r) { - int k; - char fn[PATH_MAX], *s, *b, *p; + 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; assert(r); @@ -2890,7 +2600,6 @@ int get_ctty(pid_t pid, dev_t *_devnr, char **r) { return k; snprintf(fn, sizeof(fn), "/dev/char/%u:%u", major(devnr), minor(devnr)); - char_array_0(fn); k = readlink_malloc(fn, &s); if (k < 0) { @@ -2900,14 +2609,8 @@ int get_ctty(pid_t pid, dev_t *_devnr, char **r) { /* This is an ugly hack */ if (major(devnr) == 136) { - if (asprintf(&b, "pts/%lu", (unsigned long) minor(devnr)) < 0) - return -ENOMEM; - - *r = b; - if (_devnr) - *_devnr = devnr; - - return 0; + asprintf(&b, "pts/%lu", (unsigned long) minor(devnr)); + goto finish; } /* Probably something like the ptys which have no @@ -2915,14 +2618,7 @@ int get_ctty(pid_t pid, dev_t *_devnr, char **r) { * vaguely useful. */ b = strdup(fn + 5); - if (!b) - return -ENOMEM; - - *r = b; - if (_devnr) - *_devnr = devnr; - - return 0; + goto finish; } if (startswith(s, "/dev/")) @@ -2933,8 +2629,8 @@ int get_ctty(pid_t pid, dev_t *_devnr, char **r) { p = s; b = strdup(p); - free(s); +finish: if (!b) return -ENOMEM; @@ -2963,14 +2659,15 @@ int rm_rf_children_dangerous(int fd, bool only_dirs, bool honour_sticky, struct for (;;) { struct dirent *de; - union dirent_storage buf; bool is_dir, keep_around; struct stat st; int r; - r = readdir_r(d, &buf.de, &de); - if (r != 0 && ret == 0) { - ret = -r; + errno = 0; + de = readdir(d); + if (!de && errno != 0) { + if (ret == 0) + ret = -errno; break; } @@ -3038,10 +2735,11 @@ int rm_rf_children_dangerous(int fd, bool only_dirs, bool honour_sticky, struct return ret; } -static int is_temporary_fs(struct statfs *s) { +_pure_ static int is_temporary_fs(struct statfs *s) { assert(s); - return s->f_type == TMPFS_MAGIC || - (long)s->f_type == (long)RAMFS_MAGIC; + + return F_TYPE_EQUAL(s->f_type, TMPFS_MAGIC) || + F_TYPE_EQUAL(s->f_type, RAMFS_MAGIC); } int rm_rf_children(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev) { @@ -3164,11 +2862,13 @@ int fchmod_and_fchown(int fd, mode_t mode, uid_t uid, gid_t gid) { * first change the access mode and only then hand out * ownership to avoid a window where access is too open. */ - if (fchmod(fd, mode) < 0) - return -errno; + if (mode != (mode_t) -1) + if (fchmod(fd, mode) < 0) + return -errno; - if (fchown(fd, uid, gid) < 0) - return -errno; + if (uid != (uid_t) -1 || gid != (gid_t) -1) + if (fchown(fd, uid, gid) < 0) + return -errno; return 0; } @@ -3201,12 +2901,13 @@ cpu_set_t* cpu_set_malloc(unsigned *ncpus) { } } -int status_vprintf(const char *status, bool ellipse, const char *format, va_list ap) { +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[5]; + struct iovec iovec[6] = {}; int n = 0; + static bool prev_ephemeral; assert(format); @@ -3242,7 +2943,9 @@ int status_vprintf(const char *status, bool ellipse, const char *format, va_list } } - zero(iovec); + if (prev_ephemeral) + IOVEC_SET_STRING(iovec[n++], "\r" ANSI_ERASE_TO_END_OF_LINE); + prev_ephemeral = ephemeral; if (status) { if (!isempty(status)) { @@ -3254,7 +2957,8 @@ int status_vprintf(const char *status, bool ellipse, const char *format, va_list } IOVEC_SET_STRING(iovec[n++], s); - IOVEC_SET_STRING(iovec[n++], "\n"); + if (!ephemeral) + IOVEC_SET_STRING(iovec[n++], "\n"); if (writev(fd, iovec, n) < 0) return -errno; @@ -3262,36 +2966,19 @@ int status_vprintf(const char *status, bool ellipse, const char *format, va_list return 0; } -int status_printf(const char *status, bool ellipse, const char *format, ...) { +int status_printf(const char *status, bool ellipse, bool ephemeral, const char *format, ...) { va_list ap; int r; assert(format); va_start(ap, format); - r = status_vprintf(status, ellipse, format, ap); + r = status_vprintf(status, ellipse, ephemeral, format, ap); va_end(ap); return r; } -int status_welcome(void) { - int r; - _cleanup_free_ char *pretty_name = NULL, *ansi_color = NULL; - - r = parse_env_file("/etc/os-release", NEWLINE, - "PRETTY_NAME", &pretty_name, - "ANSI_COLOR", &ansi_color, - NULL); - if (r < 0 && r != -ENOENT) - log_warning("Failed to read /etc/os-release: %s", strerror(-r)); - - return status_printf(NULL, false, - "\nWelcome to \x1B[%sm%s\x1B[0m!\n", - isempty(ansi_color) ? "1" : ansi_color, - isempty(pretty_name) ? "Linux" : pretty_name); -} - char *replace_env(const char *format, char **env) { enum { WORD, @@ -3341,10 +3028,10 @@ char *replace_env(const char *format, char **env) { if (*e == '}') { const char *t; - if (!(t = strv_env_get_with_length(env, word+2, e-word-2))) - t = ""; + t = strempty(strv_env_get_n(env, word+2, e-word-2)); - if (!(k = strappend(r, t))) + k = strappend(r, t); + if (!k) goto fail; free(r); @@ -3385,7 +3072,8 @@ char **replace_env_argv(char **argv, char **env) { char **w, **m; unsigned q; - if ((e = strv_env_get(env, *i+1))) { + e = strv_env_get(env, *i+1); + if (e) { if (!(m = strv_split_quoted(e))) { r[k] = NULL; @@ -3427,8 +3115,7 @@ char **replace_env_argv(char **argv, char **env) { } int fd_columns(int fd) { - struct winsize ws; - zero(ws); + struct winsize ws = {}; if (ioctl(fd, TIOCGWINSZ, &ws) < 0) return -errno; @@ -3462,8 +3149,7 @@ unsigned columns(void) { } int fd_lines(int fd) { - struct winsize ws; - zero(ws); + struct winsize ws = {}; if (ioctl(fd, TIOCGWINSZ, &ws) < 0) return -errno; @@ -3512,13 +3198,9 @@ bool on_tty(void) { } int running_in_chroot(void) { - struct stat a, b; - - zero(a); - zero(b); + struct stat a = {}, b = {}; /* Only works as root */ - if (stat("/proc/1/root", &a) < 0) return -errno; @@ -3530,7 +3212,7 @@ int running_in_chroot(void) { a.st_ino != b.st_ino; } -char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) { +static char *ascii_ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) { size_t x; char *r; @@ -3543,7 +3225,7 @@ char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigne r = new0(char, new_length+1); if (!r) - return r; + return NULL; x = (new_length * percent) / 100; @@ -3561,20 +3243,94 @@ char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigne return r; } -char *ellipsize(const char *s, size_t length, unsigned percent) { - return ellipsize_mem(s, strlen(s), length, percent); -} +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; -int touch(const char *path) { - int fd; + assert(s); + assert(percent <= 100); + assert(new_length >= 3); - assert(path); + /* 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); - /* This just opens the file for writing, ensuring it - * exists. It doesn't call utimensat() the way /usr/bin/touch - * does it. */ + if (old_length <= 3 || old_length <= new_length) + return strndup(s, old_length); - fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644); + x = (new_length * percent) / 100; + + if (x > new_length - 3) + x = new_length - 3; + + k = 0; + for (i = s; k < x && i < s + old_length; i = utf8_next_char(i)) { + int c; + + c = utf8_encoded_to_unichar(i); + if (c < 0) + return NULL; + k += unichar_iswide(c) ? 2 : 1; + } + + if (k > x) /* last character was wide and went over quota */ + x ++; + + for (j = s + old_length; k < new_length && j > i; ) { + int c; + + 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); + + /* we don't actually need to ellipsize */ + if (i == j) + return memdup(s, old_length + 1); + + /* make space for ellipsis */ + j = utf8_next_char(j); + + len = i - s; + len2 = s + old_length - j; + e = new(char, len + 3 + len2 + 1); + if (!e) + return NULL; + + /* + printf("old_length=%zu new_length=%zu x=%zu len=%u len2=%u k=%u\n", + old_length, new_length, x, len, len2, k); + */ + + memcpy(e, s, len); + e[len] = 0xe2; /* tri-dot ellipsis: … */ + e[len + 1] = 0x80; + e[len + 2] = 0xa6; + + memcpy(e + len + 3, j, len2 + 1); + + return e; +} + +char *ellipsize(const char *s, size_t length, unsigned percent) { + return ellipsize_mem(s, strlen(s), length, percent); +} + +int touch(const char *path) { + int fd; + + assert(path); + + /* This just opens the file for writing, ensuring it + * exists. It doesn't call utimensat() the way /usr/bin/touch + * does it. */ + + fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644); if (fd < 0) return -errno; @@ -3695,7 +3451,7 @@ int wait_for_terminate_and_warn(const char *name, pid_t pid) { return -EPROTO; } -_noreturn_ void freeze(void) { +noreturn void freeze(void) { /* Make sure nobody waits for us on a socket anymore */ close_all_fds(NULL, 0); @@ -3733,7 +3489,9 @@ DIR *xopendirat(int fd, const char *name, int flags) { int nfd; DIR *d; - nfd = openat(fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|flags); + assert(!(flags & O_CREAT)); + + nfd = openat(fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|flags, 0); if (nfd < 0) return NULL; @@ -3759,26 +3517,23 @@ int signal_from_string_try_harder(const char *s) { } static char *tag_to_udev_node(const char *tagvalue, const char *by) { - char *dn, *t, *u; - int r; - - /* FIXME: to follow udev's logic 100% we need to leave valid - * UTF8 chars unescaped */ + _cleanup_free_ char *t = NULL, *u = NULL; + char *dn; + size_t enc_len; u = unquote(tagvalue, "\"\'"); if (u == NULL) return NULL; - t = xescape(u, "/ "); - free(u); - + enc_len = strlen(u) * 4 + 1; + t = new(char, enc_len); if (t == NULL) return NULL; - r = asprintf(&dn, "/dev/disk/by-%s/%s", by, t); - free(t); + if (encode_devnode_name(u, t, enc_len) < 0) + return NULL; - if (r < 0) + if (asprintf(&dn, "/dev/disk/by-%s/%s", by, t) < 0) return NULL; return dn; @@ -3844,33 +3599,54 @@ int vtnr_from_tty(const char *tty) { return i; } +char *resolve_dev_console(char **active) { + char *tty; + + /* 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) */ + + if (path_is_read_only_fs("/sys") > 0) + return NULL; + + if (read_one_line_file("/sys/class/tty/console/active", active) < 0) + return NULL; + + /* If multiple log outputs are configured the last one is what + * /dev/console points to */ + tty = strrchr(*active, ' '); + if (tty) + tty++; + else + tty = *active; + + if (streq(tty, "tty0")) { + char *tmp; + + /* Get the active VC (e.g. tty1) */ + if (read_one_line_file("/sys/class/tty/tty0/active", &tmp) >= 0) { + free(*active); + tty = *active = tmp; + } + } + + return tty; +} + bool tty_is_vc_resolve(const char *tty) { - char *active = NULL; - bool b; + _cleanup_free_ char *active = NULL; assert(tty); if (startswith(tty, "/dev/")) tty += 5; - /* 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) */ - if (streq(tty, "console") && path_is_read_only_fs("/sys") <= 0) - if (read_one_line_file("/sys/class/tty/console/active", &active) >= 0) { - /* If multiple log outputs are configured the - * last one is what /dev/console points to */ - tty = strrchr(active, ' '); - if (tty) - tty++; - else - tty = active; - } - - b = tty_is_vc(tty); - free(active); + if (streq(tty, "console")) { + tty = resolve_dev_console(&active); + if (!tty) + return false; + } - return b; + return tty_is_vc(tty); } const char *default_term_for_tty(const char *tty) { @@ -3914,8 +3690,8 @@ void execute_directory(const char *directory, DIR *d, char *argv[]) { assert(directory); - /* Executes all binaries in a directory in parallel and waits - * until all they all finished. */ + /* Executes all binaries in a directory in parallel and + * waits for them to finish. */ if (!d) { if (!(_d = opendir(directory))) { @@ -3981,10 +3757,9 @@ void execute_directory(const char *directory, DIR *d, char *argv[]) { while (!hashmap_isempty(pids)) { pid_t pid = PTR_TO_UINT(hashmap_first_key(pids)); - siginfo_t si; + siginfo_t si = {}; char *path; - zero(si); if (waitid(P_PID, pid, &si, WEXITED) < 0) { if (errno == EINTR) @@ -4064,13 +3839,27 @@ static bool hostname_valid_char(char c) { bool hostname_is_valid(const char *s) { const char *p; + bool dot; if (isempty(s)) return false; - for (p = s; *p; p++) - if (!hostname_valid_char(*p)) - 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; @@ -4078,31 +3867,41 @@ bool hostname_is_valid(const char *s) { return true; } -char* hostname_cleanup(char *s) { +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; + } - for (p = s, d = s; *p; p++) - if ((*p >= 'a' && *p <= 'z') || - (*p >= 'A' && *p <= 'Z') || - (*p >= '0' && *p <= '9') || - *p == '-' || - *p == '_' || - *p == '.') - *(d++) = *p; + } - *d = 0; + if (dot && d > s) + d[-1] = 0; + else + *d = 0; strshorten(s, HOST_NAME_MAX); + return s; } int pipe_eof(int fd) { - struct pollfd pollfd; - int r; + struct pollfd pollfd = { + .fd = fd, + .events = POLLIN|POLLHUP, + }; - zero(pollfd); - pollfd.fd = fd; - pollfd.events = POLLIN|POLLHUP; + int r; r = poll(&pollfd, 1, 0); if (r < 0) @@ -4115,14 +3914,16 @@ int pipe_eof(int fd) { } int fd_wait_for_event(int fd, int event, usec_t t) { - struct pollfd pollfd; - int r; - zero(pollfd); - pollfd.fd = fd; - pollfd.events = event; + struct pollfd pollfd = { + .fd = fd, + .events = event, + }; + + struct timespec ts; + int r; - r = poll(&pollfd, 1, t == (usec_t) -1 ? -1 : (int) (t / USEC_PER_MSEC)); + r = ppoll(&pollfd, 1, t == (usec_t) -1 ? NULL : timespec_store(&ts, t), NULL); if (r < 0) return -errno; @@ -4147,13 +3948,13 @@ int fopen_temporary(const char *path, FILE **_f, char **_temp_path) { if (!t) return -ENOMEM; - fn = path_get_file_name(path); - k = fn-path; + fn = basename(path); + k = fn - path; memcpy(t, path, k); t[k] = '.'; stpcpy(stpcpy(t+k+1, fn), "XXXXXX"); - fd = mkostemp(t, O_WRONLY|O_CLOEXEC); + fd = mkostemp_safe(t, O_WRONLY|O_CLOEXEC); if (fd < 0) { free(t); return -errno; @@ -4263,8 +4064,9 @@ int vt_disallocate(const char *name) { return 0; } -int copy_file(const char *from, const char *to) { - int r, fdf, fdt; +int copy_file(const char *from, const char *to, int flags) { + _cleanup_close_ int fdf = -1; + int r, fdt; assert(from); assert(to); @@ -4273,11 +4075,9 @@ int copy_file(const char *from, const char *to) { if (fdf < 0) return -errno; - fdt = open(to, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY, 0644); - if (fdt < 0) { - close_nointr_nofail(fdf); + fdt = open(to, flags|O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644); + if (fdt < 0) return -errno; - } for (;;) { char buf[PIPE_BUF]; @@ -4287,7 +4087,6 @@ int copy_file(const char *from, const char *to) { if (n < 0) { r = -errno; - close_nointr_nofail(fdf); close_nointr(fdt); unlink(to); @@ -4302,15 +4101,13 @@ int copy_file(const char *from, const char *to) { if (n != k) { r = k < 0 ? k : (errno ? -errno : -EIO); - close_nointr_nofail(fdf); close_nointr(fdt); - unlink(to); + return r; } } - close_nointr_nofail(fdf); r = close_nointr(fdt); if (r < 0) { @@ -4326,7 +4123,7 @@ int symlink_atomic(const char *from, const char *to) { _cleanup_free_ char *t; const char *fn; size_t k; - unsigned long long ull; + uint64_t u; unsigned i; int r; @@ -4337,16 +4134,16 @@ int symlink_atomic(const char *from, const char *to) { if (!t) return -ENOMEM; - fn = path_get_file_name(to); + fn = basename(to); k = fn-to; memcpy(t, to, k); t[k] = '.'; x = stpcpy(t+k+1, fn); - ull = random_ull(); + u = random_u64(); for (i = 0; i < 16; i++) { - *(x++) = hexchar(ull & 0xF); - ull >>= 4; + *(x++) = hexchar(u & 0xF); + u >>= 4; } *x = 0; @@ -4447,7 +4244,7 @@ int get_user_creds( } if (!p) - return errno != 0 ? -errno : -ESRCH; + return errno > 0 ? -errno : -ESRCH; if (uid) *uid = p->pw_uid; @@ -4481,6 +4278,23 @@ char* uid_to_name(uid_t uid) { 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, "%lu", (unsigned long) gid) < 0) + return NULL; + + return r; +} + int get_group_creds(const char **groupname, gid_t *gid) { struct group *g; gid_t id; @@ -4511,7 +4325,7 @@ int get_group_creds(const char **groupname, gid_t *gid) { } if (!g) - return errno != 0 ? -errno : -ESRCH; + return errno > 0 ? -errno : -ESRCH; if (gid) *gid = g->gr_gid; @@ -4519,14 +4333,10 @@ int get_group_creds(const char **groupname, gid_t *gid) { return 0; } -int in_group(const char *name) { - gid_t gid, *gids; +int in_gid(gid_t gid) { + gid_t *gids; int ngroups_max, r, i; - r = get_group_creds(&name, &gid); - if (r < 0) - return r; - if (getgid() == gid) return 1; @@ -4549,28 +4359,58 @@ int in_group(const char *name) { 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) { - glob_t g; - int r, k; + _cleanup_globfree_ glob_t g = {}; + int k; assert(path); - zero(g); errno = 0; k = glob(path, GLOB_NOSORT|GLOB_BRACE, NULL, &g); if (k == GLOB_NOMATCH) - r = 0; + return 0; else if (k == GLOB_NOSPACE) - r = -ENOMEM; + return -ENOMEM; else if (k == 0) - r = !strv_isempty(g.gl_pathv); + return !strv_isempty(g.gl_pathv); else - r = errno ? -errno : -EIO; + return errno ? -errno : -EIO; +} - globfree(&g); +int glob_extend(char ***strv, const char *path) { + _cleanup_globfree_ glob_t g = {}; + int k; + char **p; - return r; + 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) { @@ -4599,38 +4439,31 @@ int dirent_ensure_type(DIR *d, struct dirent *de) { } int in_search_path(const char *path, char **search) { - char **i, *parent; + char **i; + _cleanup_free_ char *parent = NULL; int r; r = path_get_parent(path, &parent); if (r < 0) return r; - r = 0; - - STRV_FOREACH(i, search) { - if (path_equal(parent, *i)) { - r = 1; - break; - } - } - - free(parent); + STRV_FOREACH(i, search) + if (path_equal(parent, *i)) + return 1; - return r; + return 0; } int get_files_in_directory(const char *path, char ***list) { - DIR *d; - int r = 0; - unsigned n = 0; - char **l = NULL; + _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 */ + * number. */ d = opendir(path); if (!d) @@ -4638,15 +4471,11 @@ int get_files_in_directory(const char *path, char ***list) { for (;;) { struct dirent *de; - union dirent_storage buf; - int k; - - k = readdir_r(d, &buf.de, &de); - if (k != 0) { - r = -k; - goto finish; - } + errno = 0; + de = readdir(d); + if (!de && errno != 0) + return -errno; if (!de) break; @@ -4656,43 +4485,25 @@ int get_files_in_directory(const char *path, char ***list) { continue; if (list) { - if ((unsigned) r >= n) { - char **t; - - n = MAX(16, 2*r); - t = realloc(l, sizeof(char*) * n); - if (!t) { - r = -ENOMEM; - goto finish; - } - - l = t; - } - - assert((unsigned) r < n); + /* one extra slot is needed for the terminating NULL */ + if (!GREEDY_REALLOC(l, bufsize, n + 2)) + return -ENOMEM; - l[r] = strdup(de->d_name); - if (!l[r]) { - r = -ENOMEM; - goto finish; - } + l[n] = strdup(de->d_name); + if (!l[n]) + return -ENOMEM; - l[++r] = NULL; + l[++n] = NULL; } else - r++; + n++; } -finish: - if (d) - closedir(d); - - if (r >= 0) { - if (list) - *list = l; - } else - strv_free(l); + if (list) { + *list = l; + l = NULL; /* avoid freeing */ + } - return r; + return n; } char *strjoin(const char *x, ...) { @@ -4753,7 +4564,7 @@ char *strjoin(const char *x, ...) { } bool is_main_thread(void) { - static __thread int cached = 0; + static thread_local int cached = 0; if (_unlikely_(cached == 0)) cached = getpid() == gettid() ? 1 : -1; @@ -4971,7 +4782,7 @@ static const char *const __signal_table[] = { DEFINE_PRIVATE_STRING_TABLE_LOOKUP(__signal, int); const char *signal_to_string(int signo) { - static __thread char buf[12]; + static thread_local char buf[sizeof("RTMIN+")-1 + DECIMAL_STR_MAX(int) + 1]; const char *name; name = __signal_to_string(signo); @@ -4979,10 +4790,10 @@ const char *signal_to_string(int signo) { return name; if (signo >= SIGRTMIN && signo <= SIGRTMAX) - snprintf(buf, sizeof(buf) - 1, "RTMIN+%d", signo - SIGRTMIN); + snprintf(buf, sizeof(buf), "RTMIN+%d", signo - SIGRTMIN); else - snprintf(buf, sizeof(buf) - 1, "%d", signo); - char_array_0(buf); + snprintf(buf, sizeof(buf), "%d", signo); + return buf; } @@ -5109,15 +4920,15 @@ int fd_inc_sndbuf(int fd, size_t n) { socklen_t l = sizeof(value); r = getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, &l); - if (r >= 0 && - l == sizeof(value) && - (size_t) value >= n*2) + if (r >= 0 && l == sizeof(value) && (size_t) value >= n*2) return 0; + /* If we have the privileges we will ignore the kernel limit. */ + value = (int) n; - r = setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, sizeof(value)); - if (r < 0) - return -errno; + 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 1; } @@ -5127,16 +4938,15 @@ int fd_inc_rcvbuf(int fd, size_t n) { socklen_t l = sizeof(value); r = getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, &l); - if (r >= 0 && - l == sizeof(value) && - (size_t) value >= n*2) + if (r >= 0 && l == sizeof(value) && (size_t) value >= n*2) return 0; - value = (int) n; - r = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, sizeof(value)); - if (r < 0) - return -errno; + /* If we have the privileges we will ignore the kernel limit. */ + 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; } @@ -5250,20 +5060,18 @@ int setrlimit_closest(int resource, const struct rlimit *rlim) { } int getenv_for_pid(pid_t pid, const char *field, char **_value) { - char path[sizeof("/proc/")-1+10+sizeof("/environ")], *value = NULL; + _cleanup_fclose_ FILE *f = NULL; + char *value = NULL; int r; - FILE *f; bool done = false; size_t l; + const char *path; + assert(pid >= 0); assert(field); assert(_value); - if (pid == 0) - pid = getpid(); - - snprintf(path, sizeof(path), "/proc/%lu/environ", (unsigned long) pid); - char_array_0(path); + path = procfs_file_alloca(pid, "environ"); f = fopen(path, "re"); if (!f) @@ -5292,10 +5100,8 @@ int getenv_for_pid(pid_t pid, const char *field, char **_value) { if (memcmp(line, field, l) == 0 && line[l] == '=') { value = strdup(line + l + 1); - if (!value) { - r = -ENOMEM; - break; - } + if (!value) + return -ENOMEM; r = 1; break; @@ -5303,67 +5109,10 @@ int getenv_for_pid(pid_t pid, const char *field, char **_value) { } while (!done); - fclose(f); - - if (r >= 0) - *_value = value; - + *_value = value; return r; } -int can_sleep(const char *type) { - char *w, *state; - size_t l, k; - int r; - _cleanup_free_ char *p = NULL; - - assert(type); - - /* If /sys is read-only we cannot sleep */ - if (access("/sys/power/state", W_OK) < 0) - return false; - - r = read_one_line_file("/sys/power/state", &p); - if (r < 0) - return false; - - k = strlen(type); - FOREACH_WORD_SEPARATOR(w, l, p, WHITESPACE, state) - if (l == k && memcmp(w, type, l) == 0) - return true; - - return false; -} - -int can_sleep_disk(const char *type) { - char *w, *state; - size_t l, k; - int r; - _cleanup_free_ char *p = NULL; - - assert(type); - - /* If /sys is read-only we cannot sleep */ - if (access("/sys/power/state", W_OK) < 0 || - access("/sys/power/disk", W_OK) < 0) - return false; - - r = read_one_line_file("/sys/power/disk", &p); - if (r < 0) - return false; - - k = strlen(type); - FOREACH_WORD_SEPARATOR(w, l, p, WHITESPACE, state) { - if (l == k && memcmp(w, type, l) == 0) - return true; - - if (l == k + 2 && w[0] == '[' && memcmp(w + 1, type, l - 2) == 0 && w[l-1] == ']') - return true; - } - - return false; -} - bool is_valid_documentation_url(const char *url) { assert(url); @@ -5386,7 +5135,7 @@ bool is_valid_documentation_url(const char *url) { } bool in_initrd(void) { - static __thread int saved = -1; + static int saved = -1; struct statfs s; if (saved >= 0) @@ -5451,10 +5200,10 @@ int make_console_stdio(void) { } int get_home_dir(char **_h) { - char *h; + struct passwd *p; const char *e; + char *h; uid_t u; - struct passwd *p; assert(_h); @@ -5484,7 +5233,7 @@ int get_home_dir(char **_h) { errno = 0; p = getpwuid(u); if (!p) - return errno ? -errno : -ESRCH; + return errno > 0 ? -errno : -ESRCH; if (!path_is_absolute(p->pw_dir)) return -EINVAL; @@ -5497,33 +5246,33 @@ int get_home_dir(char **_h) { return 0; } -int get_shell(char **_sh) { - char *sh; +int get_shell(char **_s) { + struct passwd *p; const char *e; + char *s; uid_t u; - struct passwd *p; - assert(_sh); + assert(_s); /* Take the user specified one */ e = getenv("SHELL"); if (e) { - sh = strdup(e); - if (!sh) + s = strdup(e); + if (!s) return -ENOMEM; - *_sh = sh; + *_s = s; return 0; } /* Hardcode home directory for root to avoid NSS */ u = getuid(); if (u == 0) { - sh = strdup("/bin/sh"); - if (!sh) + s = strdup("/bin/sh"); + if (!s) return -ENOMEM; - *_sh = sh; + *_s = s; return 0; } @@ -5531,47 +5280,19 @@ int get_shell(char **_sh) { errno = 0; p = getpwuid(u); if (!p) - return errno ? -errno : -ESRCH; + return errno > 0 ? -errno : -ESRCH; if (!path_is_absolute(p->pw_shell)) return -EINVAL; - sh = strdup(p->pw_shell); - if (!sh) + s = strdup(p->pw_shell); + if (!s) return -ENOMEM; - *_sh = sh; + *_s = s; return 0; } -void freep(void *p) { - free(*(void**) p); -} - -void fclosep(FILE **f) { - if (*f) - fclose(*f); -} - -void pclosep(FILE **f) { - if (*f) - pclose(*f); -} - -void closep(int *fd) { - if (*fd >= 0) - close_nointr_nofail(*fd); -} - -void closedirp(DIR **d) { - if (*d) - closedir(*d); -} - -void umaskp(mode_t *u) { - umask(*u); -} - bool filename_is_safe(const char *p) { if (isempty(p)) @@ -5608,6 +5329,22 @@ bool string_is_safe(const char *p) { return true; } +/** + * Check if a string contains control characters. + * Spaces and tabs are not considered control characters. + */ +bool string_has_cc(const char *p) { + const char *t; + + assert(p); + + for (t = p; *t; t++) + if (*t > 0 && *t < ' ' && *t != '\t') + return true; + + return false; +} + bool path_is_safe(const char *p) { if (isempty(p)) @@ -5670,12 +5407,32 @@ bool is_locale_utf8(void) { goto out; } - cached_answer = streq(set, "UTF-8"); -out: - return (bool)cached_answer; -} - -const char *draw_special_char(DrawSpecialChar ch) { + if (streq(set, "UTF-8")) { + cached_answer = true; + goto out; + } + + /* 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 result, but ignore the result if C was set + * explicitly. */ + cached_answer = + streq(set, "C") && + !getenv("LC_ALL") && + !getenv("LC_CTYPE") && + !getenv("LANG"); + +out: + return (bool) cached_answer; +} + +const char *draw_special_char(DrawSpecialChar ch) { static const char *draw_table[2][_DRAW_SPECIAL_CHAR_MAX] = { /* UTF-8 */ { [DRAW_TREE_VERT] = "\342\224\202 ", /* │ */ @@ -5683,6 +5440,7 @@ const char *draw_special_char(DrawSpecialChar ch) { [DRAW_TREE_RIGHT] = "\342\224\224\342\224\200", /* └─ */ [DRAW_TREE_SPACE] = " ", /* */ [DRAW_TRIANGULAR_BULLET] = "\342\200\243 ", /* ‣ */ + [DRAW_BLACK_CIRCLE] = "\342\227\217 ", /* ● */ }, /* ASCII fallback */ { [DRAW_TREE_VERT] = "| ", @@ -5690,6 +5448,7 @@ const char *draw_special_char(DrawSpecialChar ch) { [DRAW_TREE_RIGHT] = "`-", [DRAW_TREE_SPACE] = " ", [DRAW_TRIANGULAR_BULLET] = "> ", + [DRAW_BLACK_CIRCLE] = "* ", } }; @@ -5839,16 +5598,14 @@ int on_ac_power(void) { for (;;) { struct dirent *de; - union dirent_storage buf; - _cleanup_free_ char *p = NULL; _cleanup_close_ int fd = -1, device = -1; char contents[6]; ssize_t n; - int k; - k = readdir_r(d, &buf.de, &de); - if (k != 0) - return -k; + errno = 0; + de = readdir(d); + if (!de && errno != 0) + return -errno; if (!de) break; @@ -5906,3 +5663,631 @@ int on_ac_power(void) { return found_online || !found_offline; } + +static int search_and_fopen_internal(const char *path, const char *mode, char **search, FILE **_f) { + char **i; + + assert(path); + assert(mode); + assert(_f); + + if (!path_strv_canonicalize_absolute_uniq(search, NULL)) + return -ENOMEM; + + STRV_FOREACH(i, search) { + _cleanup_free_ char *p = NULL; + FILE *f; + + p = strjoin(*i, "/", path, NULL); + if (!p) + return -ENOMEM; + + f = fopen(p, mode); + if (f) { + *_f = f; + return 0; + } + + if (errno != ENOENT) + return -errno; + } + + return -ENOENT; +} + +int search_and_fopen(const char *path, const char *mode, const char **search, FILE **_f) { + _cleanup_strv_free_ char **copy = NULL; + + assert(path); + assert(mode); + assert(_f); + + if (path_is_absolute(path)) { + FILE *f; + + f = fopen(path, mode); + if (f) { + *_f = f; + return 0; + } + + return -errno; + } + + copy = strv_copy((char**) search); + if (!copy) + return -ENOMEM; + + return search_and_fopen_internal(path, mode, copy, _f); +} + +int search_and_fopen_nulstr(const char *path, const char *mode, const char *search, FILE **_f) { + _cleanup_strv_free_ char **s = NULL; + + if (path_is_absolute(path)) { + FILE *f; + + f = fopen(path, mode); + if (f) { + *_f = f; + return 0; + } + + return -errno; + } + + s = strv_split_nulstr(search); + if (!s) + return -ENOMEM; + + return search_and_fopen_internal(path, mode, s, _f); +} + +char *strextend(char **x, ...) { + va_list ap; + size_t f, l; + char *r, *p; + + assert(x); + + l = f = *x ? strlen(*x) : 0; + + va_start(ap, 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; + } + 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); + } + va_end(ap); + + *p = 0; + *x = r; + + return r + l; +} + +char *strrep(const char *s, unsigned n) { + size_t l; + char *r, *p; + unsigned i; + + assert(s); + + l = strlen(s); + p = r = malloc(l * n + 1); + if (!r) + return NULL; + + for (i = 0; i < n; i++) + p = stpcpy(p, s); + + *p = 0; + return r; +} + +void* greedy_realloc(void **p, size_t *allocated, size_t need) { + size_t a; + void *q; + + assert(p); + assert(allocated); + + if (*allocated >= need) + return *p; + + a = MAX(64u, need * 2); + + /* check for overflows */ + if (a < need) + return NULL; + + q = realloc(*p, a); + if (!q) + return NULL; + + *p = q; + *allocated = a; + return q; +} + +void* greedy_realloc0(void **p, size_t *allocated, size_t need) { + size_t prev; + uint8_t *q; + + assert(p); + assert(allocated); + + prev = *allocated; + + q = greedy_realloc(p, allocated, need); + if (!q) + return NULL; + + if (*allocated > prev) + memzero(&q[prev], *allocated - prev); + + return q; +} + +bool id128_is_valid(const char *s) { + size_t i, l; + + l = strlen(s); + if (l == 32) { + + /* Simple formatted 128bit hex string */ + + for (i = 0; i < l; i++) { + char c = s[i]; + + if (!(c >= '0' && c <= '9') && + !(c >= 'a' && c <= 'z') && + !(c >= 'A' && c <= 'Z')) + return false; + } + + } else if (l == 36) { + + /* Formatted UUID */ + + 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; +} + +int split_pair(const char *s, const char *sep, char **l, char **r) { + char *x, *a, *b; + + 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; + + b = strdup(x + strlen(sep)); + if (!b) { + free(a); + return -ENOMEM; + } + + *l = a; + *r = b; + + return 0; +} + +int shall_restore_state(void) { + _cleanup_free_ char *line; + char *w, *state; + size_t l; + int r; + + r = proc_cmdline(&line); + if (r < 0) + return r; + if (r == 0) /* Container ... */ + return 1; + + FOREACH_WORD_QUOTED(w, l, line, state) + if (l == 23 && strneq(w, "systemd.restore_state=0", 23)) + return 0; + + return 1; +} + +int proc_cmdline(char **ret) { + int r; + + if (detect_container(NULL) > 0) { + char *buf = NULL, *p; + size_t sz = 0; + + r = read_full_file("/proc/1/cmdline", &buf, &sz); + if (r < 0) + return r; + + for (p = buf; p + 1 < buf + sz; p++) + if (*p == 0) + *p = ' '; + + *p = 0; + *ret = buf; + return 1; + } + + r = read_one_line_file("/proc/cmdline", ret); + if (r < 0) + return r; + + return 1; +} + +int parse_proc_cmdline(int (*parse_word)(const char *word)) { + _cleanup_free_ char *line = NULL; + char *w, *state; + size_t l; + int r; + + r = proc_cmdline(&line); + if (r < 0) + log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r)); + if (r <= 0) + return 0; + + FOREACH_WORD_QUOTED(w, l, line, state) { + _cleanup_free_ char *word; + + word = strndup(w, l); + if (!word) + return log_oom(); + + r = parse_word(word); + if (r < 0) { + log_error("Failed on cmdline argument %s: %s", word, strerror(-r)); + return r; + } + } + + 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 r; + + assert(machine); + assert(pid); + + 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; + + if (!streq_ptr(class, "container")) + return -EIO; + + r = parse_pid(s, &leader); + if (r < 0) + return r; + if (leader <= 1) + return -EIO; + + *pid = leader; + return 0; +} + +int namespace_open(pid_t pid, int *pidns_fd, int *mntns_fd, int *root_fd) { + _cleanup_close_ int pidnsfd = -1, mntnsfd = -1; + const char *pidns, *mntns, *root; + int rfd; + + assert(pid >= 0); + assert(pidns_fd); + assert(mntns_fd); + assert(root_fd); + + mntns = procfs_file_alloca(pid, "ns/mnt"); + mntnsfd = open(mntns, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (mntnsfd < 0) + return -errno; + + pidns = procfs_file_alloca(pid, "ns/pid"); + pidnsfd = open(pidns, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (pidnsfd < 0) + return -errno; + + root = procfs_file_alloca(pid, "root"); + rfd = open(root, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (rfd < 0) + return -errno; + + *pidns_fd = pidnsfd; + *mntns_fd = mntnsfd; + *root_fd = rfd; + pidnsfd = -1; + mntnsfd = -1; + + return 0; +} + +int namespace_enter(int pidns_fd, int mntns_fd, int root_fd) { + assert(pidns_fd >= 0); + assert(mntns_fd >= 0); + assert(root_fd >= 0); + + if (setns(pidns_fd, CLONE_NEWPID) < 0) + return -errno; + + if (setns(mntns_fd, CLONE_NEWNS) < 0) + return -errno; + + if (fchdir(root_fd) < 0) + return -errno; + + if (chroot(".") < 0) + return -errno; + + if (setresgid(0, 0, 0) < 0) + return -errno; + + if (setresuid(0, 0, 0) < 0) + return -errno; + + return 0; +} + +bool pid_is_unwaited(pid_t pid) { + /* Checks whether a PID is still valid at all, including a zombie */ + + if (pid <= 0) + return false; + + if (kill(pid, 0) >= 0) + return true; + + return errno != ESRCH; +} + +bool pid_is_alive(pid_t pid) { + int r; + + /* Checks whether a PID is still valid and not a zombie */ + + if (pid <= 0) + return false; + + r = get_process_state(pid); + if (r == -ENOENT || r == 'Z') + return false; + + return true; +} + +int getpeercred(int fd, struct ucred *ucred) { + socklen_t n = sizeof(struct ucred); + struct ucred u; + int r; + + assert(fd >= 0); + assert(ucred); + + r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &u, &n); + if (r < 0) + return -errno; + + if (n != sizeof(struct ucred)) + return -EIO; + + /* Check if the data is actually useful and not suppressed due + * to namespacing issues */ + if (u.pid <= 0) + return -ENODATA; + + *ucred = u; + return 0; +} + +int getpeersec(int fd, char **ret) { + socklen_t n = 64; + char *s; + int r; + + assert(fd >= 0); + assert(ret); + + s = new0(char, n); + if (!s) + return -ENOMEM; + + r = getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n); + if (r < 0) { + free(s); + + if (errno != ERANGE) + return -errno; + + s = new0(char, n); + if (!s) + return -ENOMEM; + + r = getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n); + if (r < 0) { + free(s); + return -errno; + } + } + + if (isempty(s)) { + free(s); + return -ENOTSUP; + } + + *ret = s; + 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; + + assert(pattern); + + u = umask(077); + + fd = mkostemp(pattern, flags); + if (fd < 0) + return -errno; + + return fd; +} + +int open_tmpfile(const char *path, int flags) { + char *p; + int fd; + + assert(path); + +#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 + + /* Fall back to unguessable name + unlinking */ + p = strappenda(path, "/systemd-tmp-XXXXXX"); + + fd = mkostemp_safe(p, flags); + if (fd < 0) + return fd; + + unlink(p); + return fd; +} + +int fd_warn_permissions(const char *path, int fd) { + struct stat st; + + if (fstat(fd, &st) < 0) + return -errno; + + if (st.st_mode & 0111) + log_warning("Configuration file %s is marked executable. Please remove executable permission bits. Proceeding anyway.", path); + + if (st.st_mode & 0002) + log_warning("Configuration file %s is marked world-writable. Please remove world writability permission bits. Proceeding anyway.", path); + + 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 0; +} + +unsigned long personality_from_string(const char *p) { + + /* 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. */ + +#if defined(__x86_64__) + + if (streq(p, "x86")) + return PER_LINUX32; + + if (streq(p, "x86-64")) + return PER_LINUX; + +#elif defined(__i386__) + + if (streq(p, "x86")) + return PER_LINUX; +#endif + + /* personality(7) documents that 0xffffffffUL is used for + * querying the current personality, hence let's use that here + * as error indicator. */ + return 0xffffffffUL; +} + +const char* personality_to_string(unsigned long p) { + +#if defined(__x86_64__) + + if (p == PER_LINUX32) + return "x86"; + + if (p == PER_LINUX) + return "x86-64"; + +#elif defined(__i386__) + + if (p == PER_LINUX) + return "x86"; +#endif + + return NULL; +}