X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Futil.c;h=c2173c7222f192ae6ce5b373b3f7809cfc54539b;hp=2279efac493f1d9c7ebda2f6f60eb3217cc77a5b;hb=7640a5de1b3ffe6547200ad204d14e4f067caf4f;hpb=56cf987fe74270bde4e16c7ec9e0414a9030723b diff --git a/src/util.c b/src/util.c index 2279efac4..c2173c722 100644 --- a/src/util.c +++ b/src/util.c @@ -1,4 +1,4 @@ -/*-*- Mode: C; c-basic-offset: 8 -*-*/ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ /*** This file is part of systemd. @@ -48,6 +48,9 @@ #include #include #include +#include +#include +#include #include "macro.h" #include "util.h" @@ -55,243 +58,22 @@ #include "missing.h" #include "log.h" #include "strv.h" +#include "label.h" +#include "exit-status.h" +#include "hashmap.h" -#if HAVE_SELINUX -#include -#include +size_t page_size(void) { + static __thread size_t pgsz = 0; + long r; -static struct selabel_handle *label_hnd = NULL; + if (pgsz) + return pgsz; -static inline int use_selinux(void) { - static int use_selinux_ind = -1; + assert_se((r = sysconf(_SC_PAGESIZE)) > 0); - if (use_selinux_ind == -1) - use_selinux_ind = (is_selinux_enabled() == 1); + pgsz = (size_t) r; - return use_selinux_ind; -} - -static int label_get_file_label_from_path( - const char *label, - const char *path, - const char *class, - security_context_t *fcon) { - - security_context_t dir_con = NULL; - security_class_t sclass; - int r = 0; - - r = getfilecon(path, &dir_con); - if (r >= 0) { - r = -1; - if ((sclass = string_to_security_class(class)) != 0) - r = security_compute_create((security_context_t) label, dir_con, sclass, fcon); - } - if (r < 0) - r = -errno; - - freecon(dir_con); - return r; -} - -#endif - -int label_init(void) { - int r = 0; - -#if HAVE_SELINUX - if (use_selinux()) { - label_hnd = selabel_open(SELABEL_CTX_FILE, NULL, 0); - if (!label_hnd) { - log_full(security_getenforce() == 1 ? LOG_ERR : LOG_DEBUG, "Failed to initialize SELinux context: %m"); - r = (security_getenforce() == 1) ? -errno : 0; - } - } -#endif - - return r; -} - -int label_fix(const char *path) { - int r = 0; -#if HAVE_SELINUX - struct stat st; - security_context_t fcon; - if (use_selinux()) { - r = lstat(path, &st); - - if (r == 0) { - r = selabel_lookup_raw(label_hnd, &fcon, path, st.st_mode); - - if (r == 0) { - r = setfilecon(path, fcon); - freecon(fcon); - } - } - if (r < 0) { - log_error("Unable to fix label of %s: %m", path); - r = (security_getenforce() == 1) ? -errno : 0; - } - } -#endif - return r; -} - -void label_finish(void) { - -#if HAVE_SELINUX - if (use_selinux()) - selabel_close(label_hnd); -#endif - -} - -int label_get_socket_label_from_exe( - const char *exe, - char **label) { - int r = 0; - -#if HAVE_SELINUX - security_context_t mycon = NULL, fcon = NULL; - security_class_t sclass; - - r = getcon(&mycon); - if (r < 0) - goto fail; - - r = getfilecon(exe, &fcon); - if (r < 0) - goto fail; - - sclass = string_to_security_class("process"); - r = security_compute_create(mycon, fcon, sclass, (security_context_t *) label); - if (r == 0) - log_debug("SELinux Socket context for %s will be set to %s", exe, *label); - -fail: - if (r< 0 && security_getenforce() == 1) - r = -errno; - - freecon(mycon); - freecon(fcon); -#endif - - return r; -} - -int label_fifofile_set(const char *label, const char *path) { - int r = 0; - -#if HAVE_SELINUX - security_context_t filecon = NULL; - if (use_selinux() && label) { - if (((r = label_get_file_label_from_path(label, path, "fifo_file", &filecon)) == 0)) { - if ((r = setfscreatecon(filecon)) < 0) { - log_error("Failed to set SELinux file context (%s) on %s: %m", label, path); - r = -errno; - } - - freecon(filecon); - } - - if (r < 0 && security_getenforce() == 0) - r = 0; - } -#endif - - return r; -} - -int label_socket_set(const char *label) { - -#if HAVE_SELINUX - if (use_selinux() && setsockcreatecon((security_context_t) label) < 0) { - log_error("Failed to set SELinux context (%s) on socket: %m", label); - if (security_getenforce() == 1) - return -errno; - } -#endif - - return 0; -} - -void label_file_clear(void) { - -#if HAVE_SELINUX - if (use_selinux()) - setfscreatecon(NULL); -#endif - - return; -} - -void label_free(const char *label) { - -#if HAVE_SELINUX - if (use_selinux()) - freecon((security_context_t) label); -#endif - - return; -} - -void label_socket_clear(void) { - -#if HAVE_SELINUX - if (use_selinux()) - setsockcreatecon(NULL); -#endif - - return; -} - -static int label_mkdir( - const char *path, - mode_t mode) { - -#if HAVE_SELINUX - int r; - security_context_t fcon = NULL; - - if (use_selinux()) { - if (path[0] == '/') { - r = selabel_lookup_raw(label_hnd, &fcon, path, mode); - } - else { - char *cwd = NULL; - char *newpath = NULL; - cwd = getcwd(NULL,0); - if ((! cwd) || (asprintf(&newpath, "%s/%s",cwd,path) < 0)) { - free(cwd); - return -errno; - } - r = selabel_lookup_raw(label_hnd, &fcon, newpath, mode); - free(cwd); - free(newpath); - } - - if (r == 0) - r = setfscreatecon(fcon); - - if ((r < 0) && (errno != ENOENT)) { - log_error("Failed to set security context %s for %s", fcon, path); - - if (security_getenforce() == 1) - goto finish; - } - } - r = mkdir(path, mode); - -finish: - if (use_selinux()) { - setfscreatecon(NULL); - freecon(fcon); - } - - return r; -#else - return mkdir(path, mode); -#endif + return pgsz; } bool streq_ptr(const char *a, const char *b) { @@ -484,7 +266,7 @@ int parse_boolean(const char *v) { } int parse_pid(const char *s, pid_t* ret_pid) { - unsigned long ul; + unsigned long ul = 0; pid_t pid; int r; @@ -671,14 +453,14 @@ char **split_path_and_make_absolute(const char *p) { int get_parent_of_pid(pid_t pid, pid_t *_ppid) { int r; FILE *f; - char fn[132], line[256], *p; + char fn[PATH_MAX], line[LINE_MAX], *p; long unsigned ppid; - 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)); - fn[sizeof(fn)-1] = 0; + char_array_0(fn); if (!(f = fopen(fn, "r"))) return -errno; @@ -714,6 +496,64 @@ int get_parent_of_pid(pid_t pid, pid_t *_ppid) { return 0; } +int get_starttime_of_pid(pid_t pid, unsigned long long *st) { + int r; + FILE *f; + char fn[PATH_MAX], line[LINE_MAX], *p; + + 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); + + if (!(f = fopen(fn, "r"))) + return -errno; + + if (!(fgets(line, sizeof(line), f))) { + r = -errno; + fclose(f); + return r; + } + + fclose(f); + + /* 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 */ + + if (!(p = strrchr(line, ')'))) + return -EIO; + + p++; + + if (sscanf(p, " " + "%*c " /* state */ + "%*d " /* ppid */ + "%*d " /* pgrp */ + "%*d " /* session */ + "%*d " /* tty_nr */ + "%*d " /* tpgid */ + "%*u " /* flags */ + "%*u " /* minflt */ + "%*u " /* cminflt */ + "%*u " /* majflt */ + "%*u " /* cmajflt */ + "%*u " /* utime */ + "%*u " /* stime */ + "%*d " /* cutime */ + "%*d " /* cstime */ + "%*d " /* priority */ + "%*d " /* nice */ + "%*d " /* num_threads */ + "%*d " /* itrealvalue */ + "%llu " /* starttime */, + st) != 1) + return -EIO; + + return 0; +} + int write_one_line_file(const char *fn, const char *line) { FILE *f; int r; @@ -729,7 +569,19 @@ int write_one_line_file(const char *fn, const char *line) { goto finish; } - r = 0; + if (!endswith(line, "\n")) + fputc('\n', f); + + fflush(f); + + if (ferror(f)) { + if (errno != 0) + r = -errno; + else + r = -EIO; + } else + r = 0; + finish: fclose(f); return r; @@ -738,7 +590,7 @@ finish: int read_one_line_file(const char *fn, char **line) { FILE *f; int r; - char t[2048], *c; + char t[LINE_MAX], *c; assert(fn); assert(line); @@ -756,6 +608,8 @@ int read_one_line_file(const char *fn, char **line) { goto finish; } + truncate_nl(c); + *line = c; r = 0; @@ -764,102 +618,340 @@ finish: return r; } -char *truncate_nl(char *s) { - assert(s); - - s[strcspn(s, NEWLINE)] = 0; - return s; -} - -int get_process_name(pid_t pid, char **name) { - char *p; +int read_full_file(const char *fn, char **contents) { + FILE *f; int r; + size_t n, l; + char *buf = NULL; + struct stat st; - assert(pid >= 1); - assert(name); + if (!(f = fopen(fn, "re"))) + return -errno; - if (asprintf(&p, "/proc/%lu/comm", (unsigned long) pid) < 0) - return -ENOMEM; + if (fstat(fileno(f), &st) < 0) { + r = -errno; + goto finish; + } - r = read_one_line_file(p, name); - free(p); + n = st.st_size > 0 ? st.st_size : LINE_MAX; + l = 0; - if (r < 0) - return r; + for (;;) { + char *t; + size_t k; - truncate_nl(*name); - return 0; -} + if (!(t = realloc(buf, n+1))) { + r = -ENOMEM; + goto finish; + } -int get_process_cmdline(pid_t pid, size_t max_length, char **line) { - char *p, *r, *k; - int c; - bool space = false; - size_t left; - FILE *f; + buf = t; + k = fread(buf + l, 1, n - l, f); - assert(pid >= 1); - assert(max_length > 0); - assert(line); + if (k <= 0) { + if (ferror(f)) { + r = -errno; + goto finish; + } - if (asprintf(&p, "/proc/%lu/cmdline", (unsigned long) pid) < 0) - return -ENOMEM; + break; + } - f = fopen(p, "r"); - free(p); + l += k; + n *= 2; - if (!f) - return -errno; + /* Safety check */ + if (n > 4*1024*1024) { + r = -E2BIG; + goto finish; + } + } - if (!(r = new(char, max_length))) { - fclose(f); - return -ENOMEM; + if (buf) + buf[l] = 0; + else if (!(buf = calloc(1, 1))) { + r = -errno; + goto finish; } - k = r; - left = max_length; - while ((c = getc(f)) != EOF) { + *contents = buf; + buf = NULL; - if (isprint(c)) { - if (space) { - if (left <= 4) - break; + r = 0; - *(k++) = ' '; - left--; - space = false; - } +finish: + fclose(f); + free(buf); - if (left <= 4) - break; + return r; +} - *(k++) = (char) c; - left--; - } else - space = true; - } +int parse_env_file( + const char *fname, + const char *separator, ...) { - if (left <= 4) { - size_t n = MIN(left-1, 3U); - memcpy(k, "...", n); - k[n] = 0; - } else - *k = 0; + int r = 0; + char *contents, *p; - fclose(f); + assert(fname); + assert(separator); - /* Kernel threads have no argv[] */ - if (r[0] == 0) { - char *t; - int h; + if ((r = read_full_file(fname, &contents)) < 0) + return r; - free(r); + p = contents; + for (;;) { + const char *key = NULL; - if ((h = get_process_name(pid, &t)) < 0) - return h; + p += strspn(p, separator); + p += strspn(p, WHITESPACE); - h = asprintf(&r, "[%s]", t); - free(t); + 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 *f; + char **m = 0; + int r; + + assert(fname); + assert(rl); + + if (!(f = fopen(fname, "re"))) + return -errno; + + while (!feof(f)) { + char l[LINE_MAX], *p, *u; + char **t; + + if (!fgets(l, sizeof(l), f)) { + if (feof(f)) + break; + + r = -errno; + goto finish; + } + + p = strstrip(l); + + if (!*p) + continue; + + if (strchr(COMMENTS, *p)) + continue; + + if (!(u = normalize_env_assignment(p))) { + log_error("Out of memory"); + r = -ENOMEM; + goto finish; + } + + t = strv_append(m, u); + free(u); + + if (!t) { + log_error("Out of memory"); + r = -ENOMEM; + goto finish; + } + + strv_free(m); + m = t; + } + + r = 0; + + *rl = m; + m = NULL; + +finish: + if (f) + fclose(f); + + strv_free(m); + + return r; +} + +int write_env_file(const char *fname, char **l) { + + char **i; + FILE *f; + int r; + + f = fopen(fname, "we"); + if (!f) + return -errno; + + STRV_FOREACH(i, l) { + fputs(*i, f); + fputc('\n', f); + } + + fflush(f); + + r = ferror(f) ? -errno : 0; + fclose(f); + + return r; +} + +char *truncate_nl(char *s) { + assert(s); + + s[strcspn(s, NEWLINE)] = 0; + return s; +} + +int get_process_name(pid_t pid, char **name) { + char *p; + int r; + + assert(pid >= 1); + assert(name); + + if (asprintf(&p, "/proc/%lu/comm", (unsigned long) pid) < 0) + return -ENOMEM; + + r = read_one_line_file(p, name); + free(p); + + if (r < 0) + return r; + + return 0; +} + +int get_process_cmdline(pid_t pid, size_t max_length, char **line) { + char *p, *r, *k; + int c; + bool space = false; + size_t left; + FILE *f; + + assert(pid >= 1); + assert(max_length > 0); + assert(line); + + if (asprintf(&p, "/proc/%lu/cmdline", (unsigned long) pid) < 0) + return -ENOMEM; + + f = fopen(p, "r"); + free(p); + + if (!f) + return -errno; + + if (!(r = new(char, max_length))) { + 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[0] == 0) { + char *t; + int h; + + free(r); + + if ((h = get_process_name(pid, &t)) < 0) + return h; + + h = asprintf(&r, "[%s]", t); + free(t); if (h < 0) return -ENOMEM; @@ -1766,6 +1858,8 @@ bool ignore_file(const char *filename) { return filename[0] == '.' || streq(filename, "lost+found") || + streq(filename, "aquota.user") || + streq(filename, "aquota.group") || endswith(filename, "~") || endswith(filename, ".rpmnew") || endswith(filename, ".rpmsave") || @@ -1827,8 +1921,9 @@ int close_all_fds(const int except[], unsigned n_except) { if (ignore_file(de->d_name)) continue; - if ((r = safe_atoi(de->d_name, &fd)) < 0) - goto finish; + if (safe_atoi(de->d_name, &fd) < 0) + /* Let's better ignore this, just in case */ + continue; if (fd < 3) continue; @@ -1851,16 +1946,13 @@ int close_all_fds(const int except[], unsigned n_except) { continue; } - if ((r = close_nointr(fd)) < 0) { + if (close_nointr(fd) < 0) { /* Valgrind has its own FD and doesn't want to have it closed */ - if (errno != EBADF) - goto finish; + if (errno != EBADF && r == 0) + r = -errno; } } - r = 0; - -finish: closedir(d); return r; } @@ -1894,6 +1986,63 @@ char *format_timestamp(char *buf, size_t l, usec_t t) { return buf; } +char *format_timestamp_pretty(char *buf, size_t l, usec_t t) { + usec_t n, d; + + n = now(CLOCK_REALTIME); + + if (t <= 0 || t > n || t + USEC_PER_DAY*7 <= t) + return NULL; + + d = n - t; + + if (d >= USEC_PER_YEAR) + snprintf(buf, l, "%llu years and %llu months ago", + (unsigned long long) (d / USEC_PER_YEAR), + (unsigned long long) ((d % USEC_PER_YEAR) / USEC_PER_MONTH)); + else if (d >= USEC_PER_MONTH) + snprintf(buf, l, "%llu months and %llu days ago", + (unsigned long long) (d / USEC_PER_MONTH), + (unsigned long long) ((d % USEC_PER_MONTH) / USEC_PER_DAY)); + else if (d >= USEC_PER_WEEK) + snprintf(buf, l, "%llu weeks and %llu days ago", + (unsigned long long) (d / USEC_PER_WEEK), + (unsigned long long) ((d % USEC_PER_WEEK) / USEC_PER_DAY)); + else if (d >= 2*USEC_PER_DAY) + snprintf(buf, l, "%llu days ago", (unsigned long long) (d / USEC_PER_DAY)); + else if (d >= 25*USEC_PER_HOUR) + snprintf(buf, l, "1 day and %lluh ago", + (unsigned long long) ((d - USEC_PER_DAY) / USEC_PER_HOUR)); + else if (d >= 6*USEC_PER_HOUR) + snprintf(buf, l, "%lluh ago", + (unsigned long long) (d / USEC_PER_HOUR)); + else if (d >= USEC_PER_HOUR) + snprintf(buf, l, "%lluh %llumin ago", + (unsigned long long) (d / USEC_PER_HOUR), + (unsigned long long) ((d % USEC_PER_HOUR) / USEC_PER_MINUTE)); + else if (d >= 5*USEC_PER_MINUTE) + snprintf(buf, l, "%llumin ago", + (unsigned long long) (d / USEC_PER_MINUTE)); + else if (d >= USEC_PER_MINUTE) + snprintf(buf, l, "%llumin %llus ago", + (unsigned long long) (d / USEC_PER_MINUTE), + (unsigned long long) ((d % USEC_PER_MINUTE) / USEC_PER_SEC)); + else if (d >= USEC_PER_SEC) + snprintf(buf, l, "%llus ago", + (unsigned long long) (d / USEC_PER_SEC)); + else if (d >= USEC_PER_MSEC) + snprintf(buf, l, "%llums ago", + (unsigned long long) (d / USEC_PER_MSEC)); + else if (d > 0) + snprintf(buf, l, "%lluus ago", + (unsigned long long) d); + else + snprintf(buf, l, "now"); + + buf[l-1] = 0; + return buf; +} + char *format_timespan(char *buf, size_t l, usec_t t) { static const struct { const char *suffix; @@ -1917,6 +2066,12 @@ char *format_timespan(char *buf, size_t l, usec_t t) { if (t == (usec_t) -1) return NULL; + if (t == 0) { + snprintf(p, l, "0"); + p[l-1] = 0; + return p; + } + /* The result of this function can be parsed with parse_usec */ for (i = 0; i < ELEMENTSOF(table); i++) { @@ -1991,7 +2146,7 @@ int chvt(int vt) { int read_one_char(FILE *f, char *ret, bool *need_nl) { struct termios old_termios, new_termios; char c; - char line[1024]; + char line[LINE_MAX]; assert(f); assert(ret); @@ -2037,23 +2192,29 @@ int read_one_char(FILE *f, char *ret, bool *need_nl) { } int ask(char *ret, const char *replies, const char *text, ...) { + bool on_tty; + assert(ret); assert(replies); assert(text); + on_tty = isatty(STDOUT_FILENO); + for (;;) { va_list ap; char c; int r; bool need_nl = true; - fputs("\x1B[1m", stdout); + if (on_tty) + fputs("\x1B[1m", stdout); va_start(ap, text); vprintf(text, ap); va_end(ap); - fputs("\x1B[0m", stdout); + if (on_tty) + fputs("\x1B[0m", stdout); fflush(stdout); @@ -2089,9 +2250,9 @@ int reset_terminal(int fd) { assert(fd >= 0); - /* First, unlock termios */ - zero(termios); - ioctl(fd, TIOCSLCKTRMIOS, &termios); + /* 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); @@ -2144,8 +2305,32 @@ finish: int open_terminal(const char *name, int mode) { int fd, r; + unsigned c = 0; + + /* + * 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. + * + * https://bugs.launchpad.net/ubuntu/+source/linux/+bug/554172/comments/245 + */ + + for (;;) { + if ((fd = open(name, mode)) >= 0) + break; - if ((fd = open(name, mode)) < 0) + if (errno != EIO) + return -errno; + + if (c >= 20) + return -errno; + + usleep(50 * USEC_PER_MSEC); + c++; + } + + if (fd < 0) return -errno; if ((r = isatty(fd)) < 0) { @@ -2169,7 +2354,7 @@ int flush_fd(int fd) { pollfd.events = POLLIN; for (;;) { - char buf[1024]; + char buf[LINE_MAX]; ssize_t l; int r; @@ -2263,26 +2448,34 @@ int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocst assert(notify >= 0); for (;;) { - struct inotify_event e; + uint8_t inotify_buffer[sizeof(struct inotify_event) + FILENAME_MAX]; ssize_t l; + struct inotify_event *e; - if ((l = read(notify, &e, sizeof(e))) != sizeof(e)) { + if ((l = read(notify, &inotify_buffer, sizeof(inotify_buffer))) < 0) { - if (l < 0) { + if (errno == EINTR) + continue; - if (errno == EINTR) - continue; + r = -errno; + goto fail; + } + + e = (struct inotify_event*) inotify_buffer; - r = -errno; - } else + while (l > 0) { + size_t step; + + if (e->wd != wd || !(e->mask & IN_CLOSE)) { r = -EIO; + goto fail; + } - goto fail; - } + step = sizeof(struct inotify_event) + e->len; + assert(step <= (size_t) l); - if (e.wd != wd || !(e.mask & IN_CLOSE)) { - r = -EIO; - goto fail; + e = (struct inotify_event*) ((uint8_t*) e + step); + l -= step; } break; @@ -2611,21 +2804,13 @@ int make_stdio(int fd) { return 0; } -bool is_clean_exit(int code, int status) { +int make_null_stdio(void) { + int null_fd; - if (code == CLD_EXITED) - return status == 0; - - /* If a daemon does not implement handlers for some of the - * signals that's not considered an unclean shutdown */ - if (code == CLD_KILLED) - return - status == SIGHUP || - status == SIGINT || - status == SIGTERM || - status == SIGPIPE; + if ((null_fd = open("/dev/null", O_RDWR|O_NOCTTY)) < 0) + return -errno; - return false; + return make_stdio(null_fd); } bool is_device_path(const char *path) { @@ -2723,18 +2908,6 @@ char* gethostname_malloc(void) { return strdup(u.sysname); } -int getmachineid_malloc(char **b) { - int r; - - assert(b); - - if ((r = read_one_line_file("/var/lib/dbus/machine-id", b)) < 0) - return r; - - strstrip(*b); - return 0; -} - char* getlogname_malloc(void) { uid_t uid; long bufsize; @@ -2771,48 +2944,160 @@ char* getlogname_malloc(void) { return name; } -int getttyname_malloc(char **r) { - char path[PATH_MAX], *p, *c; +int getttyname_malloc(int fd, char **r) { + char path[PATH_MAX], *c; + int k; assert(r); - if (ttyname_r(STDIN_FILENO, path, sizeof(path)) < 0) - return -errno; + if ((k = ttyname_r(fd, path, sizeof(path))) != 0) + return -k; char_array_0(path); - p = path; - if (startswith(path, "/dev/")) - p += 5; - - if (!(c = strdup(p))) + if (!(c = strdup(startswith(path, "/dev/") ? path + 5 : path))) return -ENOMEM; *r = c; return 0; } -static int rm_rf_children(int fd, bool only_dirs) { - DIR *d; - int ret = 0; +int getttyname_harder(int fd, char **r) { + int k; + char *s; - assert(fd >= 0); + if ((k = getttyname_malloc(fd, &s)) < 0) + return k; - /* This returns the first error we run into, but nevertheless - * tries to go on */ + if (streq(s, "tty")) { + free(s); + return get_ctty(r, NULL); + } - if (!(d = fdopendir(fd))) { - close_nointr_nofail(fd); + *r = s; + return 0; +} - return errno == ENOENT ? 0 : -errno; +int get_ctty_devnr(dev_t *d) { + int k; + char line[LINE_MAX], *p; + unsigned long ttynr; + FILE *f; + + if (!(f = fopen("/proc/self/stat", "r"))) + return -errno; + + if (!(fgets(line, sizeof(line), f))) { + k = -errno; + fclose(f); + return k; } - for (;;) { - struct dirent buf, *de; - bool is_dir; - int r; + fclose(f); - if ((r = readdir_r(d, &buf, &de)) != 0) { + if (!(p = strrchr(line, ')'))) + return -EIO; + + p++; + + if (sscanf(p, " " + "%*c " /* state */ + "%*d " /* ppid */ + "%*d " /* pgrp */ + "%*d " /* session */ + "%lu ", /* ttynr */ + &ttynr) != 1) + return -EIO; + + *d = (dev_t) ttynr; + return 0; +} + +int get_ctty(char **r, dev_t *_devnr) { + int k; + char fn[PATH_MAX], *s, *b, *p; + dev_t devnr; + + assert(r); + + if ((k = get_ctty_devnr(&devnr)) < 0) + return k; + + snprintf(fn, sizeof(fn), "/dev/char/%u:%u", major(devnr), minor(devnr)); + char_array_0(fn); + + if ((k = readlink_malloc(fn, &s)) < 0) { + + if (k != -ENOENT) + return k; + + /* 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; + } + + /* Probably something like the ptys which have no + * symlink in /dev/char. Let's return something + * vaguely useful. */ + + if (!(b = strdup(fn + 5))) + return -ENOMEM; + + *r = b; + if (_devnr) + *_devnr = devnr; + + return 0; + } + + if (startswith(s, "/dev/")) + p = s + 5; + else if (startswith(s, "../")) + p = s + 3; + else + p = s; + + b = strdup(p); + free(s); + + if (!b) + return -ENOMEM; + + *r = b; + if (_devnr) + *_devnr = devnr; + + return 0; +} + +static int rm_rf_children(int fd, bool only_dirs) { + DIR *d; + int ret = 0; + + assert(fd >= 0); + + /* This returns the first error we run into, but nevertheless + * tries to go on */ + + if (!(d = fdopendir(fd))) { + close_nointr_nofail(fd); + + return errno == ENOENT ? 0 : -errno; + } + + for (;;) { + struct dirent buf, *de; + bool is_dir; + int r; + + if ((r = readdir_r(d, &buf, &de)) != 0) { if (ret == 0) ret = -r; break; @@ -2977,40 +3262,153 @@ void status_printf(const char *format, ...) { } void status_welcome(void) { + char *pretty_name = NULL, *ansi_color = NULL; + const char *const_pretty = NULL, *const_color = NULL; + int r; -#if defined(TARGET_FEDORA) - char *r; + if ((r = parse_env_file("/etc/os-release", NEWLINE, + "PRETTY_NAME", &pretty_name, + "ANSI_COLOR", &ansi_color, + NULL)) < 0) { - if (read_one_line_file("/etc/system-release", &r) < 0) - return; + if (r != -ENOENT) + log_warning("Failed to read /etc/os-release: %s", strerror(-r)); + } - truncate_nl(r); +#if defined(TARGET_FEDORA) + if (!pretty_name) { + if ((r = read_one_line_file("/etc/system-release", &pretty_name)) < 0) { - /* This tries to mimic the color magic the old Red Hat sysinit - * script did. */ + if (r != -ENOENT) + log_warning("Failed to read /etc/system-release: %s", strerror(-r)); + } + } - if (startswith(r, "Red Hat")) - status_printf("Welcome to \x1B[0;31m%s\x1B[0m!\n", r); /* Red for RHEL */ - else if (startswith(r, "Fedora")) - status_printf("Welcome to \x1B[0;34m%s\x1B[0m!\n", r); /* Blue for Fedora */ - else - status_printf("Welcome to %s!\n", r); + if (!ansi_color && pretty_name) { - free(r); + /* This tries to mimic the color magic the old Red Hat sysinit + * script did. */ + + if (startswith(pretty_name, "Red Hat")) + const_color = "0;31"; /* Red for RHEL */ + else if (startswith(pretty_name, "Fedora")) + const_color = "0;34"; /* Blue for Fedora */ + } #elif defined(TARGET_SUSE) - char *r; - if (read_one_line_file("/etc/SuSE-release", &r) < 0) - return; + if (!pretty_name) { + if ((r = read_one_line_file("/etc/SuSE-release", &pretty_name)) < 0) { - truncate_nl(r); + if (r != -ENOENT) + log_warning("Failed to read /etc/SuSE-release: %s", strerror(-r)); + } + } + + if (!ansi_color) + const_color = "0;32"; /* Green for openSUSE */ + +#elif defined(TARGET_GENTOO) + + if (!pretty_name) { + if ((r = read_one_line_file("/etc/gentoo-release", &pretty_name)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/gentoo-release: %s", strerror(-r)); + } + } + + if (!ansi_color) + const_color = "1;34"; /* Light Blue for Gentoo */ + +#elif defined(TARGET_ALTLINUX) + + if (!pretty_name) { + if ((r = read_one_line_file("/etc/altlinux-release", &pretty_name)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/altlinux-release: %s", strerror(-r)); + } + } + + if (!ansi_color) + const_color = "0;36"; /* Cyan for ALTLinux */ + + +#elif defined(TARGET_DEBIAN) + + if (!pretty_name) { + char *version; + + if ((r = read_one_line_file("/etc/debian_version", &version)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/debian_version: %s", strerror(-r)); + } else { + pretty_name = strappend("Debian ", version); + free(version); + + if (!pretty_name) + log_warning("Failed to allocate Debian version string."); + } + } + + if (!ansi_color) + const_color = "1;31"; /* Light Red for Debian */ + +#elif defined(TARGET_UBUNTU) + + if ((r = parse_env_file("/etc/lsb-release", NEWLINE, + "DISTRIB_DESCRIPTION", &pretty_name, + NULL)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/lsb-release: %s", strerror(-r)); + } + + if (!ansi_color) + const_color = "0;33"; /* Orange/Brown for Ubuntu */ + +#elif defined(TARGET_MANDRIVA) + + if (!pretty_name) { + char *s, *p; + + if ((r = read_one_line_file("/etc/mandriva-release", &s) < 0)) { + if (r != -ENOENT) + log_warning("Failed to read /etc/mandriva-release: %s", strerror(-r)); + } else { + p = strstr(s, " release "); + if (p) { + *p = '\0'; + p += 9; + p[strcspn(p, " ")] = '\0'; + + /* This corresponds to standard rc.sysinit */ + if (asprintf(&pretty_name, "%s\x1B[0;39m %s", s, p) > 0) + const_color = "1;36"; + else + log_warning("Failed to allocate Mandriva version string."); + } else + log_warning("Failed to parse /etc/mandriva-release"); + free(s); + } + } - status_printf("Welcome to \x1B[0;32m%s\x1B[0m!\n", r); /* Green for SUSE */ - free(r); -#else -#warning "You probably should add a welcome text logic here." #endif + + if (!pretty_name && !const_pretty) + const_pretty = "Linux"; + + if (!ansi_color && !const_color) + const_color = "1"; + + status_printf("\nWelcome to \x1B[%sm%s\x1B[0m!\n\n", + const_color ? const_color : ansi_color, + const_pretty ? const_pretty : pretty_name); + + free(ansi_color); + free(pretty_name); } char *replace_env(const char *format, char **env) { @@ -3060,18 +3458,18 @@ char *replace_env(const char *format, char **env) { case VARIABLE: if (*e == '}') { - char *t; + const char *t; - if ((t = strv_env_get_with_length(env, word+2, e-word-2))) { - if (!(k = strappend(r, t))) - goto fail; + if (!(t = strv_env_get_with_length(env, word+2, e-word-2))) + t = ""; - free(r); - r = k; + if (!(k = strappend(r, t))) + goto fail; - word = e+1; - } + free(r); + r = k; + word = e+1; state = WORD; } break; @@ -3101,36 +3499,39 @@ char **replace_env_argv(char **argv, char **env) { STRV_FOREACH(i, argv) { /* If $FOO appears as single word, replace it by the split up variable */ - if ((*i)[0] == '$') { - char *e = strv_env_get(env, *i+1); + if ((*i)[0] == '$' && (*i)[1] != '{') { + char *e; + char **w, **m; + unsigned q; - if (e) { - char **w, **m; - unsigned q; + if ((e = strv_env_get(env, *i+1))) { if (!(m = strv_split_quoted(e))) { r[k] = NULL; strv_free(r); return NULL; } + } else + m = NULL; - q = strv_length(m); - l = l + q - 1; + q = strv_length(m); + l = l + q - 1; - if (!(w = realloc(r, sizeof(char*) * (l+1)))) { - r[k] = NULL; - strv_free(r); - strv_free(m); - return NULL; - } + if (!(w = realloc(r, sizeof(char*) * (l+1)))) { + r[k] = NULL; + strv_free(r); + strv_free(m); + return NULL; + } - r = w; + r = w; + if (m) { memcpy(r + k, m, q * sizeof(char*)); free(m); - - k += q; - continue; } + + k += q; + continue; } /* If ${FOO} appears as part of a word, replace it by the variable as-is */ @@ -3158,7 +3559,7 @@ int columns(void) { struct winsize ws; zero(ws); - if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) >= 0) + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0) parsed_columns = ws.ws_col; } @@ -3219,6 +3620,768 @@ char *ellipsize(const char *s, unsigned length, unsigned percent) { return r; } +int touch(const char *path) { + int fd; + + assert(path); + + if ((fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644)) < 0) + return -errno; + + close_nointr_nofail(fd); + return 0; +} + +char *unquote(const char *s, const char* quotes) { + size_t l; + assert(s); + + if ((l = strlen(s)) < 2) + return strdup(s); + + if (strchr(quotes, s[0]) && s[l-1] == s[0]) + return strndup(s+1, l-2); + + return strdup(s); +} + +char *normalize_env_assignment(const char *s) { + char *name, *value, *p, *r; + + p = strchr(s, '='); + + if (!p) { + if (!(r = strdup(s))) + return NULL; + + return strstrip(r); + } + + if (!(name = strndup(s, p - s))) + return NULL; + + if (!(p = strdup(p+1))) { + free(name); + return NULL; + } + + value = unquote(strstrip(p), QUOTES); + free(p); + + if (!value) { + free(name); + return NULL; + } + + if (asprintf(&r, "%s=%s", name, value) < 0) + r = NULL; + + free(value); + free(name); + + return r; +} + +int wait_for_terminate(pid_t pid, siginfo_t *status) { + assert(pid >= 1); + assert(status); + + for (;;) { + zero(*status); + + if (waitid(P_PID, pid, status, WEXITED) < 0) { + + if (errno == EINTR) + continue; + + return -errno; + } + + return 0; + } +} + +int wait_for_terminate_and_warn(const char *name, pid_t pid) { + int r; + siginfo_t status; + + assert(name); + assert(pid > 1); + + if ((r = wait_for_terminate(pid, &status)) < 0) { + log_warning("Failed to wait for %s: %s", name, strerror(-r)); + return r; + } + + if (status.si_code == CLD_EXITED) { + if (status.si_status != 0) { + log_warning("%s failed with error code %i.", name, status.si_status); + return status.si_status; + } + + log_debug("%s succeeded.", name); + return 0; + + } else if (status.si_code == CLD_KILLED || + status.si_code == CLD_DUMPED) { + + log_warning("%s terminated by signal %s.", name, signal_to_string(status.si_status)); + return -EPROTO; + } + + log_warning("%s failed due to unknown reason.", name); + return -EPROTO; + +} + +void freeze(void) { + + /* Make sure nobody waits for us on a socket anymore */ + close_all_fds(NULL, 0); + + sync(); + + for (;;) + pause(); +} + +bool null_or_empty(struct stat *st) { + assert(st); + + if (S_ISREG(st->st_mode) && st->st_size <= 0) + return true; + + if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)) + return true; + + return false; +} + +DIR *xopendirat(int fd, const char *name, int flags) { + int nfd; + DIR *d; + + if ((nfd = openat(fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|flags)) < 0) + return NULL; + + if (!(d = fdopendir(nfd))) { + close_nointr_nofail(nfd); + return NULL; + } + + return d; +} + +int signal_from_string_try_harder(const char *s) { + int signo; + assert(s); + + if ((signo = signal_from_string(s)) <= 0) + if (startswith(s, "SIG")) + return signal_from_string(s+3); + + return signo; +} + +void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t) { + + assert(f); + assert(name); + assert(t); + + if (!dual_timestamp_is_set(t)) + return; + + fprintf(f, "%s=%llu %llu\n", + name, + (unsigned long long) t->realtime, + (unsigned long long) t->monotonic); +} + +void dual_timestamp_deserialize(const char *value, dual_timestamp *t) { + unsigned long long a, b; + + assert(value); + assert(t); + + if (sscanf(value, "%lli %llu", &a, &b) != 2) + log_debug("Failed to parse finish timestamp value %s", value); + else { + t->realtime = a; + t->monotonic = b; + } +} + +char *fstab_node_to_udev_node(const char *p) { + char *dn, *t, *u; + int r; + + /* FIXME: to follow udev's logic 100% we need to leave valid + * UTF8 chars unescaped */ + + if (startswith(p, "LABEL=")) { + + if (!(u = unquote(p+6, "\"\'"))) + return NULL; + + t = xescape(u, "/ "); + free(u); + + if (!t) + return NULL; + + r = asprintf(&dn, "/dev/disk/by-label/%s", t); + free(t); + + if (r < 0) + return NULL; + + return dn; + } + + if (startswith(p, "UUID=")) { + + if (!(u = unquote(p+5, "\"\'"))) + return NULL; + + t = xescape(u, "/ "); + free(u); + + if (!t) + return NULL; + + r = asprintf(&dn, "/dev/disk/by-uuid/%s", t); + free(t); + + if (r < 0) + return NULL; + + return dn; + } + + return strdup(p); +} + +void filter_environ(const char *prefix) { + int i, j; + assert(prefix); + + if (!environ) + return; + + for (i = 0, j = 0; environ[i]; i++) { + + if (startswith(environ[i], prefix)) + continue; + + environ[j++] = environ[i]; + } + + environ[j] = NULL; +} + +bool tty_is_vc(const char *tty) { + assert(tty); + + if (startswith(tty, "/dev/")) + tty += 5; + + return startswith(tty, "tty") && + tty[3] >= '0' && tty[3] <= '9'; +} + +const char *default_term_for_tty(const char *tty) { + char *active = NULL; + const char *term; + + assert(tty); + + if (startswith(tty, "/dev/")) + tty += 5; + + /* Resolve where /dev/console is pointing when determining + * TERM */ + if (streq(tty, "console")) + 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 */ + if ((tty = strrchr(active, ' '))) + tty++; + else + tty = active; + } + + term = tty_is_vc(tty) ? "TERM=linux" : "TERM=vt100"; + free(active); + + return term; +} + +/* Returns a short identifier for the various VM implementations */ +int detect_vm(const char **id) { + +#if defined(__i386__) || defined(__x86_64__) + + /* Both CPUID and DMI are x86 specific interfaces... */ + + static const char *const dmi_vendors[] = { + "/sys/class/dmi/id/sys_vendor", + "/sys/class/dmi/id/board_vendor", + "/sys/class/dmi/id/bios_vendor" + }; + + static const char dmi_vendor_table[] = + "QEMU\0" "qemu\0" + /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */ + "VMware\0" "vmware\0" + "VMW\0" "vmware\0" + "Microsoft Corporation\0" "microsoft\0" + "innotek GmbH\0" "oracle\0" + "Xen\0" "xen\0" + "Bochs\0" "bochs\0"; + + static const char cpuid_vendor_table[] = + "XenVMMXenVMM\0" "xen\0" + "KVMKVMKVM\0" "kvm\0" + /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */ + "VMwareVMware\0" "vmware\0" + /* http://msdn.microsoft.com/en-us/library/ff542428.aspx */ + "Microsoft Hv\0" "microsoft\0"; + + uint32_t eax, ecx; + union { + uint32_t sig32[3]; + char text[13]; + } sig; + unsigned i; + const char *j, *k; + bool hypervisor; + + /* http://lwn.net/Articles/301888/ */ + zero(sig); + +#if defined (__i386__) +#define REG_a "eax" +#define REG_b "ebx" +#elif defined (__amd64__) +#define REG_a "rax" +#define REG_b "rbx" +#endif + + /* First detect whether there is a hypervisor */ + eax = 1; + __asm__ __volatile__ ( + /* ebx/rbx is being used for PIC! */ + " push %%"REG_b" \n\t" + " cpuid \n\t" + " pop %%"REG_b" \n\t" + + : "=a" (eax), "=c" (ecx) + : "0" (eax) + ); + + hypervisor = !!(ecx & ecx & 0x80000000U); + + if (hypervisor) { + + /* There is a hypervisor, see what it is */ + eax = 0x40000000U; + __asm__ __volatile__ ( + /* ebx/rbx is being used for PIC! */ + " push %%"REG_b" \n\t" + " cpuid \n\t" + " mov %%ebx, %1 \n\t" + " pop %%"REG_b" \n\t" + + : "=a" (eax), "=r" (sig.sig32[0]), "=c" (sig.sig32[1]), "=d" (sig.sig32[2]) + : "0" (eax) + ); + + NULSTR_FOREACH_PAIR(j, k, cpuid_vendor_table) + if (streq(sig.text, j)) { + + if (id) + *id = k; + + return 1; + } + } + + for (i = 0; i < ELEMENTSOF(dmi_vendors); i++) { + char *s; + int r; + const char *found = NULL; + + if ((r = read_one_line_file(dmi_vendors[i], &s)) < 0) { + if (r != -ENOENT) + return r; + + continue; + } + + NULSTR_FOREACH_PAIR(j, k, dmi_vendor_table) + if (startswith(s, j)) + found = k; + free(s); + + if (found) { + if (id) + *id = found; + + return 1; + } + } + + if (hypervisor) { + if (id) + *id = "other"; + + return 1; + } + +#endif + return 0; +} + +int detect_container(const char **id) { + FILE *f; + + /* Unfortunately many of these operations require root access + * in one way or another */ + + if (geteuid() != 0) + return -EPERM; + + if (running_in_chroot() > 0) { + + if (id) + *id = "chroot"; + + return 1; + } + + /* /proc/vz exists in container and outside of the container, + * /proc/bc only outside of the container. */ + if (access("/proc/vz", F_OK) >= 0 && + access("/proc/bc", F_OK) < 0) { + + if (id) + *id = "openvz"; + + return 1; + } + + if ((f = fopen("/proc/self/cgroup", "r"))) { + + for (;;) { + char line[LINE_MAX], *p; + + if (!fgets(line, sizeof(line), f)) + break; + + if (!(p = strchr(strstrip(line), ':'))) + continue; + + if (strncmp(p, ":ns:", 4)) + continue; + + if (!streq(p, ":ns:/")) { + fclose(f); + + if (id) + *id = "pidns"; + + return 1; + } + } + + fclose(f); + } + + return 0; +} + +/* Returns a short identifier for the various VM/container implementations */ +int detect_virtualization(const char **id) { + static __thread const char *cached_id = NULL; + const char *_id; + int r; + + if (cached_id) { + + if (cached_id == (const char*) -1) + return 0; + + if (id) + *id = cached_id; + + return 1; + } + + if ((r = detect_container(&_id)) != 0) + goto finish; + + r = detect_vm(&_id); + +finish: + if (r > 0) { + cached_id = _id; + + if (id) + *id = _id; + } else if (r == 0) + cached_id = (const char*) -1; + + return r; +} + +void execute_directory(const char *directory, DIR *d, char *argv[]) { + DIR *_d = NULL; + struct dirent *de; + Hashmap *pids = NULL; + + assert(directory); + + /* Executes all binaries in a directory in parallel and waits + * until all they all finished. */ + + if (!d) { + if (!(_d = opendir(directory))) { + + if (errno == ENOENT) + return; + + log_error("Failed to enumerate directory %s: %m", directory); + return; + } + + d = _d; + } + + if (!(pids = hashmap_new(trivial_hash_func, trivial_compare_func))) { + log_error("Failed to allocate set."); + goto finish; + } + + while ((de = readdir(d))) { + char *path; + pid_t pid; + int k; + + if (ignore_file(de->d_name)) + continue; + + if (de->d_type != DT_REG && + de->d_type != DT_LNK && + de->d_type != DT_UNKNOWN) + continue; + + if (asprintf(&path, "%s/%s", directory, de->d_name) < 0) { + log_error("Out of memory"); + continue; + } + + if ((pid = fork()) < 0) { + log_error("Failed to fork: %m"); + free(path); + continue; + } + + if (pid == 0) { + char *_argv[2]; + /* Child */ + + if (!argv) { + _argv[0] = path; + _argv[1] = NULL; + argv = _argv; + } else + if (!argv[0]) + argv[0] = path; + + execv(path, argv); + + log_error("Failed to execute %s: %m", path); + _exit(EXIT_FAILURE); + } + + log_debug("Spawned %s as %lu", path, (unsigned long) pid); + + if ((k = hashmap_put(pids, UINT_TO_PTR(pid), path)) < 0) { + log_error("Failed to add PID to set: %s", strerror(-k)); + free(path); + } + } + + while (!hashmap_isempty(pids)) { + siginfo_t si; + char *path; + + zero(si); + if (waitid(P_ALL, 0, &si, WEXITED) < 0) { + + if (errno == EINTR) + continue; + + log_error("waitid() failed: %m"); + goto finish; + } + + if ((path = hashmap_remove(pids, UINT_TO_PTR(si.si_pid)))) { + if (!is_clean_exit(si.si_code, si.si_status)) { + if (si.si_code == CLD_EXITED) + log_error("%s exited with exit status %i.", path, si.si_status); + else + log_error("%s terminated by signal %s.", path, signal_to_string(si.si_status)); + } else + log_debug("%s exited successfully.", path); + + free(path); + } + } + +finish: + if (_d) + closedir(_d); + + if (pids) + hashmap_free_free(pids); +} + +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 nulstr_contains(const char*nulstr, const char *needle) { + const char *i; + + 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; +} + +void parse_syslog_priority(char **p, int *priority) { + int a = 0, b = 0, c = 0; + int k; + + assert(p); + assert(*p); + assert(priority); + + if ((*p)[0] != '<') + return; + + if (!strchr(*p, '>')) + return; + + 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; + + if (a < 0 || b < 0 || c < 0) + return; + + *priority = a*100+b*10+c; + *p += k; +} + +int have_effective_cap(int value) { + cap_t cap; + cap_flag_value_t fv; + int r; + + if (!(cap = cap_get_proc())) + return -errno; + + if (cap_get_flag(cap, value, CAP_EFFECTIVE, &fv) < 0) + r = -errno; + else + r = fv == CAP_SET; + + cap_free(cap); + return r; +} + +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; + + if (isempty(s)) + return false; + + for (p = s; *p; p++) + if (!hostname_valid_char(*p)) + return false; + + if (p-s > HOST_NAME_MAX) + return false; + + return true; +} + +char* hostname_cleanup(char *s) { + char *p, *d; + + 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; + + strshorten(s, HOST_NAME_MAX); + return s; +} + static const char *const ioprio_class_table[] = { [IOPRIO_CLASS_NONE] = "none", [IOPRIO_CLASS_RT] = "realtime", @@ -3239,7 +4402,7 @@ static const char *const sigchld_code_table[] = { DEFINE_STRING_TABLE_LOOKUP(sigchld_code, int); -static const char *const log_facility_table[LOG_NFACILITIES] = { +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", @@ -3262,7 +4425,7 @@ static const char *const log_facility_table[LOG_NFACILITIES] = { [LOG_FAC(LOG_LOCAL7)] = "local7" }; -DEFINE_STRING_TABLE_LOOKUP(log_facility, int); +DEFINE_STRING_TABLE_LOOKUP(log_facility_unshifted, int); static const char *const log_level_table[] = { [LOG_EMERG] = "emerg", @@ -3333,7 +4496,9 @@ static const char *const signal_table[] = { [SIGPIPE] = "PIPE", [SIGALRM] = "ALRM", [SIGTERM] = "TERM", - [SIGSTKFLT] = "STKFLT", +#ifdef SIGSTKFLT + [SIGSTKFLT] = "STKFLT", /* Linux on SPARC doesn't know SIGSTKFLT */ +#endif [SIGCHLD] = "CHLD", [SIGCONT] = "CONT", [SIGSTOP] = "STOP",