X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Futil.c;h=bdc194e80c9ce45bde0975d7ef9c5ab4b4482bfd;hp=2e5827f32dcf3671a26545d5250312e265bcb8c3;hb=1b39d4b9d271f2309a193d4e4fae6633248b0e94;hpb=fa776d8e962da9d90459e2f3e86a2a0c6366ee12 diff --git a/src/util.c b/src/util.c index 2e5827f32..bdc194e80 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) { @@ -499,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); @@ -525,6 +530,149 @@ 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 *seperator, ...) { + + int r; + char *contents, *p; + + assert(fname); + assert(seperator); + + if ((r = read_full_file(fname, &contents)) < 0) + return r; + + p = contents; + for (;;) { + const char *key = NULL; + + p += strspn(p, seperator); + p += strspn(p, WHITESPACE); + + if (!*p) + break; + + if (!strchr(COMMENTS, *p)) { + va_list ap; + char **value; + + va_start(ap, seperator); + 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, seperator); + + 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; + } + + free(*value); + *value = v; + + p += n; + break; + } + va_end(ap); + } + + if (!key) + p += strcspn(p, seperator); + } + + r = 0; + +fail: + free(contents); + return r; +} + char *truncate_nl(char *s) { assert(s); @@ -609,8 +757,22 @@ int get_process_cmdline(pid_t pid, size_t max_length, char **line) { fclose(f); - if (r[0] == 0) - return get_process_name(pid, line); + /* 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; @@ -698,6 +860,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; @@ -912,7 +1116,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; @@ -955,8 +1159,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) @@ -972,7 +1175,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; @@ -1600,6 +1803,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; @@ -1623,6 +1883,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++) { @@ -1743,23 +2009,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); @@ -1789,10 +2061,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; @@ -1975,7 +2259,7 @@ int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocst } if (e.wd != wd || !(e.mask & IN_CLOSE)) { - r = -errno; + r = -EIO; goto fail; } @@ -2202,25 +2486,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; } @@ -2323,6 +2606,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 @@ -2418,18 +2711,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; @@ -2468,11 +2749,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); @@ -2498,7 +2780,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 (;;) { @@ -2522,7 +2805,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; } @@ -2535,7 +2818,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; } @@ -2546,13 +2829,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; } } @@ -2702,6 +2985,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 @@ -2710,7 +3019,7 @@ void status_welcome(void) { char *replace_env(const char *format, char **env) { enum { WORD, - DOLLAR, + CURLY, VARIABLE } state = WORD; @@ -2725,11 +3034,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; @@ -2753,19 +3062,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; @@ -2785,12 +3094,52 @@ fail: char **replace_env_argv(char **argv, char **env) { char **r, **i; - unsigned k = 0; + unsigned k = 0, l = 0; - if (!(r = new(char*, strv_length(argv)+1))) + 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; @@ -2815,7 +3164,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; } @@ -2825,6 +3174,134 @@ int columns(void) { 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; + +} + static const char *const ioprio_class_table[] = { [IOPRIO_CLASS_NONE] = "none", [IOPRIO_CLASS_RT] = "realtime", @@ -2922,3 +3399,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);