X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Futil.c;h=d12b5e772da601b9379e0c0efde09481564d7a56;hp=b66eb466b6cc79a8006b44c690eea95f56ca5da4;hb=22927a3654915670c6d2c7f2e60ea5f54cd2503d;hpb=4fd5948e74b776b6d68ba55f558da5f354179e52 diff --git a/src/util.c b/src/util.c index b66eb466b..d12b5e772 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. @@ -47,6 +47,9 @@ #include #include #include +#include +#include +#include #include "macro.h" #include "util.h" @@ -54,6 +57,8 @@ #include "missing.h" #include "log.h" #include "strv.h" +#include "label.h" +#include "exit-status.h" bool streq_ptr(const char *a, const char *b) { @@ -307,40 +312,6 @@ int safe_atoi(const char *s, int *ret_i) { return 0; } -int safe_atolu(const char *s, long unsigned *ret_lu) { - char *x = NULL; - unsigned long l; - - assert(s); - assert(ret_lu); - - errno = 0; - l = strtoul(s, &x, 0); - - if (!x || *x || errno) - return errno ? -errno : -EINVAL; - - *ret_lu = l; - return 0; -} - -int safe_atoli(const char *s, long int *ret_li) { - char *x = NULL; - long l; - - assert(s); - assert(ret_li); - - errno = 0; - l = strtol(s, &x, 0); - - if (!x || *x || errno) - return errno ? -errno : -EINVAL; - - *ret_li = l; - return 0; -} - int safe_atollu(const char *s, long long unsigned *ret_llu) { char *x = NULL; unsigned long long l; @@ -394,7 +365,8 @@ char *split(const char *c, size_t *l, const char *separator, char **state) { /* 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; + char *current, *e; + bool escaped = false; current = *state ? *state : (char*) c; @@ -405,26 +377,45 @@ char *split_quoted(const char *c, size_t *l, char **state) { if (*current == '\'') { current ++; - *l = strcspn(current, "'"); - *state = current+*l; - if (**state == '\'') - (*state)++; + 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 ++; - *l = strcspn(current, "\""); - *state = current+*l; - if (**state == '\"') - (*state)++; + 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 { - *l = strcspn(current, WHITESPACE); - *state = current+*l; + 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; } - /* FIXME: Cannot deal with strings that have spaces AND ticks - * in them */ - return (char*) current; } @@ -504,6 +495,9 @@ int write_one_line_file(const char *fn, const char *line) { goto finish; } + if (!endswith(line, "\n")) + fputc('\n', f); + r = 0; finish: fclose(f); @@ -513,7 +507,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); @@ -539,6 +533,155 @@ finish: return r; } +int read_full_file(const char *fn, char **contents) { + FILE *f; + int r; + size_t n, l; + char *buf = NULL; + struct stat st; + + if (!(f = fopen(fn, "re"))) + return -errno; + + if (fstat(fileno(f), &st) < 0) { + r = -errno; + goto finish; + } + + n = st.st_size > 0 ? st.st_size : LINE_MAX; + l = 0; + + for (;;) { + char *t; + size_t k; + + if (!(t = realloc(buf, n+1))) { + r = -ENOMEM; + goto finish; + } + + buf = t; + k = fread(buf + l, 1, n - l, f); + + if (k <= 0) { + if (ferror(f)) { + r = -errno; + goto finish; + } + + break; + } + + l += k; + n *= 2; + + /* Safety check */ + if (n > 4*1024*1024) { + r = -E2BIG; + goto finish; + } + } + + if (buf) + buf[l] = 0; + else if (!(buf = calloc(1, 1))) { + r = -errno; + goto finish; + } + + *contents = buf; + buf = NULL; + + r = 0; + +finish: + fclose(f); + free(buf); + + return r; +} + +int parse_env_file( + const char *fname, + const char *separator, ...) { + + int r = 0; + char *contents, *p; + + assert(fname); + assert(separator); + + if ((r = read_full_file(fname, &contents)) < 0) + return r; + + p = contents; + for (;;) { + const char *key = NULL; + + p += strspn(p, separator); + p += strspn(p, WHITESPACE); + + 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; +} + char *truncate_nl(char *s) { assert(s); @@ -566,15 +709,101 @@ int get_process_name(pid_t pid, char **name) { return 0; } -char *strappend(const char *s, const char *suffix) { - size_t a, b; +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; + } + + *line = r; + return 0; +} + +char *strnappend(const char *s, const char *suffix, size_t b) { + size_t a; char *r; + if (!s && !suffix) + return strdup(""); + + if (!s) + return strndup(suffix, b); + + if (!suffix) + return strdup(s); + assert(s); assert(suffix); a = strlen(s); - b = strlen(suffix); if (!(r = new(char, a+b+1))) return NULL; @@ -586,6 +815,10 @@ char *strappend(const char *s, const char *suffix) { return r; } +char *strappend(const char *s, const char *suffix) { + return strnappend(s, suffix, suffix ? strlen(suffix) : 0); +} + int readlink_malloc(const char *p, char **r) { size_t l = 100; @@ -636,6 +869,48 @@ int readlink_and_make_absolute(const char *p, char **r) { return 0; } +int parent_of_path(const char *path, char **_r) { + const char *e, *a = NULL, *b = NULL, *p; + char *r; + bool slash = false; + + assert(path); + assert(_r); + + if (!*path) + return -EINVAL; + + for (e = path; *e; e++) { + + if (!slash && *e == '/') { + a = b; + b = e; + slash = true; + } else if (slash && *e != '/') + slash = false; + } + + if (*(e-1) == '/') + p = a; + else + p = b; + + if (!p) + return -EINVAL; + + if (p == path) + r = strdup("/"); + else + r = strndup(path, p-path); + + if (!r) + return -ENOMEM; + + *_r = r; + return 0; +} + + char *file_name_from_path(const char *p) { char *r; @@ -850,7 +1125,7 @@ char *file_in_same_dir(const char *path, const char *filename) { int safe_mkdir(const char *path, mode_t mode, uid_t uid, gid_t gid) { struct stat st; - if (mkdir(path, mode) >= 0) + if (label_mkdir(path, mode) >= 0) if (chmod_and_chown(path, mode, uid, gid) < 0) return -errno; @@ -893,8 +1168,7 @@ int mkdir_parents(const char *path, mode_t mode) { if (!(t = strndup(path, e - path))) return -ENOMEM; - r = mkdir(t, mode); - + r = label_mkdir(t, mode); free(t); if (r < 0 && errno != EEXIST) @@ -910,7 +1184,7 @@ int mkdir_p(const char *path, mode_t mode) { if ((r = mkdir_parents(path, mode)) < 0) return r; - if (mkdir(path, mode) < 0) + if (label_mkdir(path, mode) < 0 && errno != EEXIST) return -errno; return 0; @@ -1082,7 +1356,7 @@ char *cescape(const char *s) { return r; } -char *cunescape(const char *s) { +char *cunescape_length(const char *s, size_t length) { char *r, *t; const char *f; @@ -1090,10 +1364,10 @@ char *cunescape(const char *s) { /* Undoes C style string escaping */ - if (!(r = new(char, strlen(s)+1))) + if (!(r = new(char, length+1))) return r; - for (f = s, t = r; *f; f++) { + for (f = s, t = r; f < s + length; f++) { if (*f != '\\') { *(t++) = *f; @@ -1135,6 +1409,11 @@ char *cunescape(const char *s) { *(t++) = '\''; break; + case 's': + /* This is an extension of the XDG syntax files */ + *(t++) = ' '; + break; + case 'x': { /* hexadecimal encoding */ int a, b; @@ -1185,7 +1464,7 @@ char *cunescape(const char *s) { default: /* Invalid escape code, let's take it literal then */ *(t++) = '\\'; - *(t++) = 'f'; + *(t++) = *f; break; } } @@ -1195,6 +1474,9 @@ finish: return r; } +char *cunescape(const char *s) { + return cunescape_length(s, strlen(s)); +} char *xescape(const char *s, const char *bad) { char *r, *t; @@ -1402,6 +1684,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") || @@ -1530,6 +1814,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; @@ -1553,6 +1894,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++) { @@ -1673,23 +2020,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); @@ -1719,10 +2072,22 @@ int ask(char *ret, const char *replies, const char *text, ...) { int reset_terminal(int fd) { struct termios termios; int r = 0; + long arg; + + /* Set terminal to some sane defaults */ assert(fd >= 0); - /* Set terminal to some sane defaults */ + /* 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); + + /* Enable console unicode mode */ + arg = K_UNICODE; + ioctl(fd, KDSKBMODE, &arg); if (tcgetattr(fd, &termios) < 0) { r = -errno; @@ -1887,26 +2252,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 = -errno; - goto fail; + e = (struct inotify_event*) ((uint8_t*) e + step); + l -= step; } break; @@ -2132,25 +2505,24 @@ ssize_t loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) { int path_is_mount_point(const char *t) { struct stat a, b; - char *copy; + char *parent; + int r; if (lstat(t, &a) < 0) { - if (errno == ENOENT) return 0; return -errno; } - if (!(copy = strdup(t))) - return -ENOMEM; + if ((r = parent_of_path(t, &parent)) < 0) + return r; - if (lstat(dirname(copy), &b) < 0) { - free(copy); - return -errno; - } + r = lstat(parent, &b); + free(parent); - free(copy); + if (r < 0) + return -errno; return a.st_dev != b.st_dev; } @@ -2236,6 +2608,15 @@ int make_stdio(int fd) { return 0; } +int make_null_stdio(void) { + int null_fd; + + if ((null_fd = open("/dev/null", O_RDWR|O_NOCTTY)) < 0) + return -errno; + + return make_stdio(null_fd); +} + bool is_clean_exit(int code, int status) { if (code == CLD_EXITED) @@ -2253,6 +2634,16 @@ bool is_clean_exit(int code, int status) { return false; } +bool is_clean_exit_lsb(int code, int status) { + + if (is_clean_exit(code, status)) + return true; + + return + code == CLD_EXITED && + (status == EXIT_NOTINSTALLED || status == EXIT_NOTCONFIGURED); +} + bool is_device_path(const char *path) { /* Returns true on paths that refer to a device, either in @@ -2348,18 +2739,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; @@ -2398,11 +2777,12 @@ char* getlogname_malloc(void) { int getttyname_malloc(char **r) { char path[PATH_MAX], *p, *c; + int k; assert(r); - if (ttyname_r(STDIN_FILENO, path, sizeof(path)) < 0) - return -errno; + if ((k = ttyname_r(STDIN_FILENO, path, sizeof(path))) != 0) + return -k; char_array_0(path); @@ -2428,7 +2808,8 @@ static int rm_rf_children(int fd, bool only_dirs) { if (!(d = fdopendir(fd))) { close_nointr_nofail(fd); - return -errno; + + return errno == ENOENT ? 0 : -errno; } for (;;) { @@ -2452,7 +2833,7 @@ static int rm_rf_children(int fd, bool only_dirs) { struct stat st; if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { - if (ret == 0) + if (ret == 0 && errno != ENOENT) ret = -errno; continue; } @@ -2465,7 +2846,7 @@ static int rm_rf_children(int fd, bool only_dirs) { int subdir_fd; if ((subdir_fd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC)) < 0) { - if (ret == 0) + if (ret == 0 && errno != ENOENT) ret = -errno; continue; } @@ -2476,13 +2857,13 @@ static int rm_rf_children(int fd, bool only_dirs) { } if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) { - if (ret == 0) + if (ret == 0 && errno != ENOENT) ret = -errno; } } else if (!only_dirs) { if (unlinkat(fd, de->d_name, 0) < 0) { - if (ret == 0) + if (ret == 0 && errno != ENOENT) ret = -errno; } } @@ -2538,6 +2919,612 @@ int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) { return 0; } +cpu_set_t* cpu_set_malloc(unsigned *ncpus) { + cpu_set_t *r; + unsigned n = 1024; + + /* Allocates the cpuset in the right size */ + + for (;;) { + if (!(r = CPU_ALLOC(n))) + return NULL; + + if (sched_getaffinity(0, CPU_ALLOC_SIZE(n), r) >= 0) { + CPU_ZERO_S(CPU_ALLOC_SIZE(n), r); + + if (ncpus) + *ncpus = n; + + return r; + } + + CPU_FREE(r); + + if (errno != EINVAL) + return NULL; + + n *= 2; + } +} + +void status_vprintf(const char *format, va_list ap) { + char *s = NULL; + int fd = -1; + + assert(format); + + /* This independent of logging, as status messages are + * optional and go exclusively to the console. */ + + if (vasprintf(&s, format, ap) < 0) + goto finish; + + if ((fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC)) < 0) + goto finish; + + write(fd, s, strlen(s)); + +finish: + free(s); + + if (fd >= 0) + close_nointr_nofail(fd); +} + +void status_printf(const char *format, ...) { + va_list ap; + + assert(format); + + va_start(ap, format); + status_vprintf(format, ap); + va_end(ap); +} + +void status_welcome(void) { + char *pretty_name = NULL, *ansi_color = NULL; + const char *const_pretty = NULL, *const_color = NULL; + int r; + + if ((r = parse_env_file("/etc/os-release", NEWLINE, + "PRETTY_NAME", &pretty_name, + "ANSI_COLOR", &ansi_color, + NULL)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/os-release: %s", strerror(-r)); + } + +#if defined(TARGET_FEDORA) + if (!pretty_name) { + if ((r = read_one_line_file("/etc/system-release", &pretty_name)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/system-release: %s", strerror(-r)); + } else + truncate_nl(pretty_name); + } + + if (!ansi_color && pretty_name) { + + /* 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) + + if (!pretty_name) { + if ((r = read_one_line_file("/etc/SuSE-release", &pretty_name)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/SuSE-release: %s", strerror(-r)); + } else + truncate_nl(pretty_name); + } + + 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)); + } else + truncate_nl(pretty_name); + } + + if (!ansi_color) + const_color = "1;34"; /* Light Blue for Gentoo */ + +#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 { + truncate_nl(version); + pretty_name = strappend("Debian ", version); + free(version); + } + } + + 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_ARCH) + + if (!pretty_name) + const_pretty = "Arch Linux"; + + if (!ansi_color) + const_color = "1;36"; /* Cyan for Arch */ +#endif + + if (!pretty_name && !const_pretty) + const_pretty = "Linux"; + + if (!ansi_color && !const_color) + const_color = "1"; + + status_printf("Welcome to \x1B[%sm%s\x1B[0m!\n", + const_color ? const_color : ansi_color, + const_pretty ? const_pretty : pretty_name); +} + +char *replace_env(const char *format, char **env) { + enum { + WORD, + CURLY, + VARIABLE + } state = WORD; + + const char *e, *word = format; + char *r = NULL, *k; + + assert(format); + + for (e = format; *e; e ++) { + + switch (state) { + + case WORD: + if (*e == '$') + state = CURLY; + break; + + case CURLY: + if (*e == '{') { + if (!(k = strnappend(r, word, e-word-1))) + goto fail; + + free(r); + r = k; + + word = e-1; + state = VARIABLE; + + } else if (*e == '$') { + if (!(k = strnappend(r, word, e-word))) + goto fail; + + free(r); + r = k; + + word = e+1; + state = WORD; + } else + state = WORD; + break; + + case VARIABLE: + if (*e == '}') { + const char *t; + + if (!(t = strv_env_get_with_length(env, word+2, e-word-2))) + t = ""; + + if (!(k = strappend(r, t))) + goto fail; + + free(r); + r = k; + + word = e+1; + state = WORD; + } + break; + } + } + + if (!(k = strnappend(r, word, e-word))) + goto fail; + + free(r); + return k; + +fail: + free(r); + return NULL; +} + +char **replace_env_argv(char **argv, char **env) { + char **r, **i; + unsigned k = 0, l = 0; + + l = strv_length(argv); + + if (!(r = new(char*, l+1))) + return NULL; + + STRV_FOREACH(i, argv) { + + /* If $FOO appears as single word, replace it by the split up variable */ + if ((*i)[0] == '$' && (*i)[1] != '{') { + char *e; + char **w, **m; + unsigned q; + + if ((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; + + if (!(w = realloc(r, sizeof(char*) * (l+1)))) { + r[k] = NULL; + strv_free(r); + strv_free(m); + return NULL; + } + + r = w; + if (m) { + memcpy(r + k, m, q * sizeof(char*)); + free(m); + } + + k += q; + continue; + } + + /* If ${FOO} appears as part of a word, replace it by the variable as-is */ + if (!(r[k++] = replace_env(*i, env))) { + strv_free(r); + return NULL; + } + } + + r[k] = NULL; + return r; +} + +int columns(void) { + static __thread int parsed_columns = 0; + const char *e; + + if (parsed_columns > 0) + return parsed_columns; + + if ((e = getenv("COLUMNS"))) + parsed_columns = atoi(e); + + if (parsed_columns <= 0) { + struct winsize ws; + zero(ws); + + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0) + parsed_columns = ws.ws_col; + } + + if (parsed_columns <= 0) + parsed_columns = 80; + + return parsed_columns; +} + +int running_in_chroot(void) { + struct stat a, b; + + zero(a); + zero(b); + + /* Only works as root */ + + if (stat("/proc/1/root", &a) < 0) + return -errno; + + if (stat("/", &b) < 0) + return -errno; + + return + a.st_dev != b.st_dev || + a.st_ino != b.st_ino; +} + +char *ellipsize(const char *s, unsigned length, unsigned percent) { + size_t l, x; + char *r; + + assert(s); + assert(percent <= 100); + assert(length >= 3); + + l = strlen(s); + + if (l <= 3 || l <= length) + return strdup(s); + + if (!(r = new0(char, length+1))) + return r; + + x = (length * percent) / 100; + + if (x > length - 3) + x = length - 3; + + memcpy(r, s, x); + r[x] = '.'; + r[x+1] = '.'; + r[x+2] = '.'; + memcpy(r + x + 3, + s + l - (length - x - 3), + length - x - 3); + + return r; +} + +int touch(const char *path) { + int fd; + + assert(path); + + if ((fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, 0666)) < 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); +} + +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 -EPROTO; + } + + 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) { + 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) { + return fdopendir(openat(fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC)); +} + +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", ascii_strlower(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; +} + +const char *default_term_for_tty(const char *tty) { + assert(tty); + + if (startswith(tty, "/dev/")) + tty += 5; + + if (startswith(tty, "tty") && + tty[3] >= '0' && tty[3] <= '9') + return "TERM=linux"; + + /* FIXME: Proper handling of /dev/console would be cool */ + + return "TERM=vt100-nav"; +} + static const char *const ioprio_class_table[] = { [IOPRIO_CLASS_NONE] = "none", [IOPRIO_CLASS_RT] = "realtime", @@ -2635,3 +3622,41 @@ static const char* const ip_tos_table[] = { }; DEFINE_STRING_TABLE_LOOKUP(ip_tos, int); + +static const char *const signal_table[] = { + [SIGHUP] = "HUP", + [SIGINT] = "INT", + [SIGQUIT] = "QUIT", + [SIGILL] = "ILL", + [SIGTRAP] = "TRAP", + [SIGABRT] = "ABRT", + [SIGBUS] = "BUS", + [SIGFPE] = "FPE", + [SIGKILL] = "KILL", + [SIGUSR1] = "USR1", + [SIGSEGV] = "SEGV", + [SIGUSR2] = "USR2", + [SIGPIPE] = "PIPE", + [SIGALRM] = "ALRM", + [SIGTERM] = "TERM", +#ifdef SIGSTKFLT + [SIGSTKFLT] = "STKFLT", /* Linux on SPARC doesn't know SIGSTKFLT */ +#endif + [SIGCHLD] = "CHLD", + [SIGCONT] = "CONT", + [SIGSTOP] = "STOP", + [SIGTSTP] = "TSTP", + [SIGTTIN] = "TTIN", + [SIGTTOU] = "TTOU", + [SIGURG] = "URG", + [SIGXCPU] = "XCPU", + [SIGXFSZ] = "XFSZ", + [SIGVTALRM] = "VTALRM", + [SIGPROF] = "PROF", + [SIGWINCH] = "WINCH", + [SIGIO] = "IO", + [SIGPWR] = "PWR", + [SIGSYS] = "SYS" +}; + +DEFINE_STRING_TABLE_LOOKUP(signal, int);