X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Futil.c;h=4046938fcdbef876c9dfdee5f3bfc8d3faf52cfa;hp=ce8695be25f6c3fad0ee1e2b06028278b93db824;hb=7948c4dfbea73ac21250b588089039aa17a90386;hpb=dd36de4d520fc77f0e2ea83f560040d36be3ee50 diff --git a/src/util.c b/src/util.c index ce8695be2..4046938fc 100644 --- a/src/util.c +++ b/src/util.c @@ -50,6 +50,9 @@ #include #include #include +#include +#include +#include #include "macro.h" #include "util.h" @@ -59,6 +62,21 @@ #include "strv.h" #include "label.h" #include "exit-status.h" +#include "hashmap.h" + +size_t page_size(void) { + static __thread size_t pgsz = 0; + long r; + + if (pgsz) + return pgsz; + + assert_se((r = sysconf(_SC_PAGESIZE)) > 0); + + pgsz = (size_t) r; + + return pgsz; +} bool streq_ptr(const char *a, const char *b) { @@ -250,7 +268,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; @@ -437,14 +455,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; @@ -480,6 +498,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; @@ -495,7 +571,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; @@ -522,6 +610,8 @@ int read_one_line_file(const char *fn, char **line) { goto finish; } + truncate_nl(c); + *line = c; r = 0; @@ -600,13 +690,13 @@ finish: int parse_env_file( const char *fname, - const char *seperator, ...) { + const char *separator, ...) { int r = 0; char *contents, *p; assert(fname); - assert(seperator); + assert(separator); if ((r = read_full_file(fname, &contents)) < 0) return r; @@ -615,7 +705,7 @@ int parse_env_file( for (;;) { const char *key = NULL; - p += strspn(p, seperator); + p += strspn(p, separator); p += strspn(p, WHITESPACE); if (!*p) @@ -625,7 +715,7 @@ int parse_env_file( va_list ap; char **value; - va_start(ap, seperator); + va_start(ap, separator); while ((key = va_arg(ap, char *))) { size_t n; char *v; @@ -638,7 +728,7 @@ int parse_env_file( continue; p += n + 1; - n = strcspn(p, seperator); + n = strcspn(p, separator); if (n >= 2 && strchr(QUOTES, p[0]) && @@ -671,7 +761,7 @@ int parse_env_file( } if (!key) - p += strcspn(p, seperator); + p += strcspn(p, separator); } fail: @@ -679,6 +769,96 @@ fail: 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); @@ -702,7 +882,6 @@ int get_process_name(pid_t pid, char **name) { if (r < 0) return r; - truncate_nl(*name); return 0; } @@ -1032,6 +1211,26 @@ char **strv_path_canonicalize(char **l) { return l; } +char **strv_path_remove_empty(char **l) { + char **f, **t; + + if (!l) + return NULL; + + for (f = t = l; *f; f++) { + + if (dir_is_empty(*f) > 0) { + free(*f); + continue; + } + + *(t++) = *f; + } + + *t = NULL; + return l; +} + int reset_all_signal_handlers(void) { int sig; @@ -1681,6 +1880,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") || @@ -1742,8 +1943,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; @@ -1766,16 +1968,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; } @@ -1944,7 +2143,7 @@ bool fstype_is_network(const char *fstype) { int chvt(int vt) { int fd, r = 0; - if ((fd = open("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC)) < 0) + if ((fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC)) < 0) return -errno; if (vt < 0) { @@ -1969,7 +2168,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); @@ -2064,7 +2263,7 @@ int ask(char *ret, const char *replies, const char *text, ...) { } } -int reset_terminal(int fd) { +int reset_terminal_fd(int fd) { struct termios termios; int r = 0; long arg; @@ -2126,10 +2325,47 @@ finish: return r; } +int reset_terminal(const char *name) { + int fd, r; + + fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + r = reset_terminal_fd(fd); + close_nointr_nofail(fd); + + return r; +} + 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 (errno != EIO) + return -errno; + + if (c >= 20) + return -errno; - if ((fd = open(name, mode)) < 0) + usleep(50 * USEC_PER_MSEC); + c++; + } + + if (fd < 0) return -errno; if ((r = isatty(fd)) < 0) { @@ -2153,7 +2389,7 @@ int flush_fd(int fd) { pollfd.events = POLLIN; for (;;) { - char buf[1024]; + char buf[LINE_MAX]; ssize_t l; int r; @@ -2222,8 +2458,8 @@ int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocst /* We pass here O_NOCTTY only so that we can check the return * value TIOCSCTTY and have a reliable way to figure out if we * successfully became the controlling process of the tty */ - if ((fd = open_terminal(name, O_RDWR|O_NOCTTY)) < 0) - return -errno; + if ((fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC)) < 0) + return fd; /* First, try to get the tty */ r = ioctl(fd, TIOCSCTTY, force); @@ -2247,26 +2483,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; @@ -2282,7 +2526,7 @@ int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocst if (notify >= 0) close_nointr_nofail(notify); - if ((r = reset_terminal(fd)) < 0) + if ((r = reset_terminal_fd(fd)) < 0) log_warning("Failed to reset terminal: %s", strerror(-r)); return fd; @@ -2595,31 +2839,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; - - return false; -} - -bool is_clean_exit_lsb(int code, int status) { - - if (is_clean_exit(code, status)) - return true; + if ((null_fd = open("/dev/null", O_RDWR|O_NOCTTY)) < 0) + return -errno; - return - code == CLD_EXITED && - (status == EXIT_NOTINSTALLED || status == EXIT_NOTCONFIGURED); + return make_stdio(null_fd); } bool is_device_path(const char *path) { @@ -2753,28 +2979,139 @@ 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 ((k = ttyname_r(STDIN_FILENO, path, sizeof(path))) != 0) + 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; } +int getttyname_harder(int fd, char **r) { + int k; + char *s; + + if ((k = getttyname_malloc(fd, &s)) < 0) + return k; + + if (streq(s, "tty")) { + free(s); + return get_ctty(r, NULL); + } + + *r = s; + return 0; +} + +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; + } + + fclose(f); + + 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; @@ -2960,66 +3297,164 @@ 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)); + } + } - status_printf("Welcome to \x1B[0;32m%s\x1B[0m!\n", r); /* Green for SUSE */ - free(r); + if (!ansi_color) + const_color = "0;32"; /* Green for openSUSE */ #elif defined(TARGET_GENTOO) - char *r; - if (read_one_line_file("/etc/gentoo-release", &r) < 0) - return; + if (!pretty_name) { + if ((r = read_one_line_file("/etc/gentoo-release", &pretty_name)) < 0) { - truncate_nl(r); + if (r != -ENOENT) + log_warning("Failed to read /etc/gentoo-release: %s", strerror(-r)); + } + } - status_printf("Welcome to \x1B[1;34m%s\x1B[0m!\n", r); /* Light Blue for Gentoo */ + if (!ansi_color) + const_color = "1;34"; /* Light Blue for Gentoo */ - free(r); +#elif defined(TARGET_ALTLINUX) -#elif defined(TARGET_DEBIAN) - char *r; + if (!pretty_name) { + if ((r = read_one_line_file("/etc/altlinux-release", &pretty_name)) < 0) { - if (read_one_line_file("/etc/debian_version", &r) < 0) - return; + if (r != -ENOENT) + log_warning("Failed to read /etc/altlinux-release: %s", strerror(-r)); + } + } - truncate_nl(r); + if (!ansi_color) + const_color = "0;36"; /* Cyan for ALTLinux */ - 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." +#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); + } + } +#elif defined(TARGET_MEEGO) + + if (!pretty_name) { + if ((r = read_one_line_file("/etc/meego-release", &pretty_name)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/meego-release: %s", strerror(-r)); + } + } + + if (!ansi_color) + const_color = "1;35"; /* Bright Magenta for MeeGo */ #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) { @@ -3236,7 +3671,7 @@ int touch(const char *path) { assert(path); - if ((fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, 0666)) < 0) + if ((fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644)) < 0) return -errno; close_nointr_nofail(fd); @@ -3256,6 +3691,43 @@ char *unquote(const char *s, const char* quotes) { 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); @@ -3290,7 +3762,7 @@ int wait_for_terminate_and_warn(const char *name, pid_t pid) { 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; + return status.si_status; } log_debug("%s succeeded.", name); @@ -3308,6 +3780,736 @@ int wait_for_terminate_and_warn(const char *name, pid_t pid) { } +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 & 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; +} + +int terminal_vhangup_fd(int fd) { + if (ioctl(fd, TIOCVHANGUP) < 0) + return -errno; + + return 0; +} + +int terminal_vhangup(const char *name) { + int fd, r; + + fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + r = terminal_vhangup_fd(fd); + close_nointr_nofail(fd); + + return r; +} + +int vt_disallocate(const char *name) { + int fd, r; + unsigned u; + + /* Deallocate the VT if possible. If not possible + * (i.e. because it is the active one), at least clear it + * entirely (including the scrollback buffer) */ + + if (!startswith(name, "/dev/")) + return -EINVAL; + + if (!tty_is_vc(name)) { + /* So this is not a VT. I guess we cannot deallocate + * it then. But let's at least clear the screen */ + + fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + loop_write(fd, "\033[H\033[2J", 7, false); /* clear screen */ + close_nointr_nofail(fd); + + return 0; + } + + if (!startswith(name, "/dev/tty")) + return -EINVAL; + + r = safe_atou(name+8, &u); + if (r < 0) + return r; + + if (u <= 0) + return -EINVAL; + + /* Try to deallocate */ + fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + r = ioctl(fd, VT_DISALLOCATE, u); + close_nointr_nofail(fd); + + if (r >= 0) + return 0; + + if (errno != EBUSY) + return -errno; + + /* Couldn't deallocate, so let's clear it fully with + * scrollback */ + fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + /* Requires Linux 2.6.40 */ + loop_write(fd, "\033[H\033[3J", 7, false); /* clear screen including scrollback */ + close_nointr_nofail(fd); + + return 0; +} + static const char *const ioprio_class_table[] = { [IOPRIO_CLASS_NONE] = "none", [IOPRIO_CLASS_RT] = "realtime", @@ -3328,7 +4530,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", @@ -3351,7 +4553,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", @@ -3443,3 +4645,199 @@ static const char *const signal_table[] = { }; DEFINE_STRING_TABLE_LOOKUP(signal, int); + +static int file_is_conf(const struct dirent *d, const char *suffix) { + assert(d); + + if (ignore_file(d->d_name)) + return 0; + + if (d->d_type != DT_REG && + d->d_type != DT_LNK && + d->d_type != DT_UNKNOWN) + return 0; + + return endswith(d->d_name, suffix); +} + +static int files_add(Hashmap *h, const char *path, const char *suffix) { + DIR *dir; + struct dirent *de; + int r = 0; + + dir = opendir(path); + if (!dir) { + if (errno == ENOENT) + return 0; + return -errno; + } + + for (de = readdir(dir); de; de = readdir(dir)) { + char *p, *f; + const char *base; + + if (!file_is_conf(de, suffix)) + continue; + + if (asprintf(&p, "%s/%s", path, de->d_name) < 0) { + r = -ENOMEM; + goto finish; + } + + f = canonicalize_file_name(p); + if (!f) { + log_error("Failed to canonicalize file name '%s': %m", p); + free(p); + continue; + } + free(p); + + log_debug("found: %s\n", f); + base = f + strlen(path) + 1; + if (hashmap_put(h, base, f) <= 0) + free(f); + } + +finish: + closedir(dir); + return r; +} + +static int base_cmp(const void *a, const void *b) { + const char *s1, *s2; + + s1 = *(char * const *)a; + s2 = *(char * const *)b; + return strcmp(file_name_from_path(s1), file_name_from_path(s2)); +} + +int conf_files_list(char ***strv, const char *suffix, const char *dir, ...) { + Hashmap *fh = NULL; + char **dirs = NULL; + char **files = NULL; + char **p; + va_list ap; + int r = 0; + + va_start(ap, dir); + dirs = strv_new_ap(dir, ap); + va_end(ap); + if (!dirs) { + r = -ENOMEM; + goto finish; + } + if (!strv_path_canonicalize(dirs)) { + r = -ENOMEM; + goto finish; + } + if (!strv_uniq(dirs)) { + r = -ENOMEM; + goto finish; + } + + fh = hashmap_new(string_hash_func, string_compare_func); + if (!fh) { + r = -ENOMEM; + goto finish; + } + + STRV_FOREACH(p, dirs) { + if (files_add(fh, *p, suffix) < 0) { + log_error("Failed to search for files."); + r = -EINVAL; + goto finish; + } + } + + files = hashmap_get_strv(fh); + if (files == NULL) { + log_error("Failed to compose list of files."); + r = -ENOMEM; + goto finish; + } + + qsort(files, hashmap_size(fh), sizeof(char *), base_cmp); +finish: + strv_free(dirs); + hashmap_free(fh); + *strv = files; + return r; +} + +bool hwclock_is_localtime(void) { + FILE *f; + char line[LINE_MAX]; + bool local = false; + + /* + * The third line of adjtime is "UTC" or "LOCAL" or nothing. + * # /etc/adjtime + * 0.0 0 0.0 + * 0 + * UTC + */ + f = fopen("/etc/adjtime", "re"); + if (f) { + if (fgets(line, sizeof(line), f) && + fgets(line, sizeof(line), f) && + fgets(line, sizeof(line), f) ) { + if (!strcmp(line, "LOCAL\n")) + local = true; + } + fclose(f); + } + return local; +} + +int hwclock_apply_localtime_delta(void) { + const struct timeval *tv_null = NULL; + struct timeval tv; + struct tm *tm; + int minuteswest; + struct timezone tz; + + gettimeofday(&tv, NULL); + tm = localtime(&tv.tv_sec); + minuteswest = tm->tm_gmtoff / 60; + + tz.tz_minuteswest = -minuteswest; + tz.tz_dsttime = 0; /* DST_NONE*/ + + /* + * If the hardware clock does not run in UTC, but in local time: + * The very first time we set the kernel's timezone, it will warp + * the clock so that it runs in UTC instead of local time. + */ + if (settimeofday(tv_null, &tz) < 0) + return -errno; + else + return minuteswest; +} + +int hwclock_get_time(struct tm *tm) { + int fd; + int err = 0; + + fd = open("/dev/rtc0", O_RDONLY|O_CLOEXEC); + if (fd < 0) + return -errno; + if (ioctl(fd, RTC_RD_TIME, tm) < 0) + err = -errno; + close(fd); + + return err; +} + +int hwclock_set_time(const struct tm *tm) { + int fd; + int err = 0; + + fd = open("/dev/rtc0", O_RDONLY|O_CLOEXEC); + if (fd < 0) + return -errno; + if (ioctl(fd, RTC_SET_TIME, tm) < 0) + err = -errno; + close(fd); + + return err; +}