X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Futil.c;h=434f311e71f9cdbbdafe85229fdeb653f0b22965;hp=45de609c295411a5a9d2ee2fad08b66cbbacb2e5;hb=f601daa70143745915a8d38603969f228414af19;hpb=8fe914ec81d9f57bcc083036f528b00119ed2e3b diff --git a/src/util.c b/src/util.c index 45de609c2..434f311e7 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,8 @@ #include #include #include +#include +#include #include "macro.h" #include "util.h" @@ -55,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) { @@ -500,7 +504,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); @@ -526,6 +530,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); @@ -969,7 +1122,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; @@ -1012,7 +1165,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) @@ -1028,7 +1181,7 @@ int mkdir_p(const char *path, mode_t mode) { if ((r = mkdir_parents(path, mode)) < 0) return r; - if (mkdir(path, mode) < 0 && errno != EEXIST) + if (label_mkdir(path, mode) < 0 && errno != EEXIST) return -errno; return 0; @@ -1656,6 +1809,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; @@ -1679,6 +1889,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++) { @@ -1799,23 +2015,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); @@ -1851,9 +2073,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); @@ -2025,26 +2247,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; + } - r = -errno; - } else + e = (struct inotify_event*) inotify_buffer; + + 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; @@ -2390,6 +2620,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 @@ -2485,18 +2725,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; @@ -2535,11 +2763,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); @@ -2770,6 +2999,32 @@ void status_welcome(void) { status_printf("Welcome to \x1B[0;32m%s\x1B[0m!\n", r); /* Green for SUSE */ free(r); + +#elif defined(TARGET_GENTOO) + char *r; + + if (read_one_line_file("/etc/gentoo-release", &r) < 0) + return; + + truncate_nl(r); + + status_printf("Welcome to \x1B[1;34m%s\x1B[0m!\n", r); /* Light Blue for Gentoo */ + + free(r); + +#elif defined(TARGET_DEBIAN) + char *r; + + if (read_one_line_file("/etc/debian_version", &r) < 0) + return; + + truncate_nl(r); + + status_printf("Welcome to Debian \x1B[1;31m%s\x1B[0m!\n", r); /* Light Red for Debian */ + + free(r); +#elif defined(TARGET_ARCH) + status_printf("Welcome to \x1B[1;36mArch Linux\x1B[0m!\n"); /* Cyan for Arch */ #else #warning "You probably should add a welcome text logic here." #endif @@ -2778,7 +3033,7 @@ void status_welcome(void) { char *replace_env(const char *format, char **env) { enum { WORD, - DOLLAR, + CURLY, VARIABLE } state = WORD; @@ -2793,11 +3048,11 @@ char *replace_env(const char *format, char **env) { case WORD: if (*e == '$') - state = DOLLAR; + state = CURLY; break; - case DOLLAR: - if (*e == '(') { + case CURLY: + if (*e == '{') { if (!(k = strnappend(r, word, e-word-1))) goto fail; @@ -2821,19 +3076,19 @@ char *replace_env(const char *format, char **env) { break; case VARIABLE: - if (*e == ')') { - char *t; + if (*e == '}') { + 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; @@ -2853,12 +3108,52 @@ fail: char **replace_env_argv(char **argv, char **env) { char **r, **i; - unsigned k = 0; + unsigned k = 0, l = 0; + + l = strv_length(argv); - if (!(r = new(char*, strv_length(argv)+1))) + 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; @@ -2883,7 +3178,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; } @@ -2944,6 +3239,100 @@ 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, 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; +} + static const char *const ioprio_class_table[] = { [IOPRIO_CLASS_NONE] = "none", [IOPRIO_CLASS_RT] = "realtime", @@ -3058,7 +3447,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",