X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Futil.c;h=dfc1dc6b85a94a7dec0bf01cc2b74dfc08938c9f;hp=da71e4d08b270a8da6c9d7a347f8cbb33ef451b0;hb=ee4cbc2c85edacab721c710e1f99aedd5c2e3a3a;hpb=35d50f55f346c71fd5e957d35ebcae1c50b1f9ce diff --git a/src/util.c b/src/util.c index da71e4d08..dfc1dc6b8 100644 --- a/src/util.c +++ b/src/util.c @@ -55,6 +55,7 @@ #include #include #include +#include #include "macro.h" #include "util.h" @@ -73,7 +74,7 @@ size_t page_size(void) { static __thread size_t pgsz = 0; long r; - if (_likely_(pgsz)) + if (_likely_(pgsz > 0)) return pgsz; assert_se((r = sysconf(_SC_PAGESIZE)) > 0); @@ -704,15 +705,22 @@ int read_one_line_file(const char *fn, char **line) { assert(fn); assert(line); - if (!(f = fopen(fn, "re"))) + f = fopen(fn, "re"); + if (!f) return -errno; - if (!(fgets(t, sizeof(t), f))) { - r = feof(f) ? -EIO : -errno; - goto finish; + if (!fgets(t, sizeof(t), f)) { + + if (ferror(f)) { + r = -errno; + goto finish; + } + + t[0] = 0; } - if (!(c = strdup(t))) { + c = strdup(t); + if (!c) { r = -ENOMEM; goto finish; } @@ -884,7 +892,7 @@ int load_env_file( char ***rl) { FILE *f; - char **m = 0; + char **m = NULL; int r; assert(fname); @@ -993,46 +1001,51 @@ char *truncate_nl(char *s) { return s; } -int get_process_name(pid_t pid, char **name) { - char *p; +int get_process_comm(pid_t pid, char **name) { int r; - assert(pid >= 1); assert(name); - if (asprintf(&p, "/proc/%lu/comm", (unsigned long) pid) < 0) - return -ENOMEM; - - r = read_one_line_file(p, name); - free(p); + if (pid == 0) + r = read_one_line_file("/proc/self/comm", name); + else { + char *p; + if (asprintf(&p, "/proc/%lu/comm", (unsigned long) pid) < 0) + return -ENOMEM; - if (r < 0) - return r; + r = read_one_line_file(p, name); + free(p); + } - return 0; + return r; } -int get_process_cmdline(pid_t pid, size_t max_length, char **line) { - char *p, *r, *k; +int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line) { + char *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; + if (pid == 0) + f = fopen("/proc/self/cmdline", "re"); + else { + char *p; + if (asprintf(&p, "/proc/%lu/cmdline", (unsigned long) pid) < 0) + return -ENOMEM; - f = fopen(p, "re"); - free(p); + f = fopen(p, "re"); + free(p); + } if (!f) return -errno; - if (!(r = new(char, max_length))) { + r = new(char, max_length); + if (!r) { fclose(f); return -ENOMEM; } @@ -1076,13 +1089,17 @@ int get_process_cmdline(pid_t pid, size_t max_length, char **line) { free(r); - if ((h = get_process_name(pid, &t)) < 0) + if (!comm_fallback) + return -ENOENT; + + h = get_process_comm(pid, &t); + if (h < 0) return h; - h = asprintf(&r, "[%s]", t); + r = join("[", t, "]", NULL); free(t); - if (h < 0) + if (!r) return -ENOMEM; } @@ -1090,6 +1107,107 @@ int get_process_cmdline(pid_t pid, size_t max_length, char **line) { return 0; } +int is_kernel_thread(pid_t pid) { + char *p; + size_t count; + char c; + bool eof; + FILE *f; + + if (pid == 0) + return 0; + + if (asprintf(&p, "/proc/%lu/cmdline", (unsigned long) pid) < 0) + return -ENOMEM; + + f = fopen(p, "re"); + free(p); + + if (!f) + return -errno; + + count = fread(&c, 1, 1, f); + eof = feof(f); + fclose(f); + + /* Kernel threads have an empty cmdline */ + + if (count <= 0) + return eof ? 1 : -errno; + + return 0; +} + +int get_process_exe(pid_t pid, char **name) { + int r; + + assert(name); + + if (pid == 0) + r = readlink_malloc("/proc/self/exe", name); + else { + char *p; + if (asprintf(&p, "/proc/%lu/exe", (unsigned long) pid) < 0) + return -ENOMEM; + + r = readlink_malloc(p, name); + free(p); + } + + return r; +} + +int get_process_uid(pid_t pid, uid_t *uid) { + char *p; + FILE *f; + int r; + + assert(uid); + + if (pid == 0) + return getuid(); + + if (asprintf(&p, "/proc/%lu/status", (unsigned long) pid) < 0) + return -ENOMEM; + + f = fopen(p, "re"); + free(p); + + if (!f) + return -errno; + + while (!feof(f)) { + char line[LINE_MAX], *l; + + if (!fgets(line, sizeof(line), f)) { + if (feof(f)) + break; + + r = -errno; + goto finish; + } + + l = strstrip(line); + + if (startswith(l, "Uid:")) { + l += 4; + l += strspn(l, WHITESPACE); + + l[strcspn(l, WHITESPACE)] = 0; + + r = parse_uid(l, uid); + goto finish; + } + } + + r = -EIO; + +finish: + fclose(f); + + return r; +} + char *strnappend(const char *s, const char *suffix, size_t b) { size_t a; char *r; @@ -1715,7 +1833,8 @@ char *cunescape_length(const char *s, size_t length) { /* Undoes C style string escaping */ - if (!(r = new(char, length+1))) + r = new(char, length+1); + if (!r) return r; for (f = s, t = r; f < s + length; f++) { @@ -1769,8 +1888,10 @@ char *cunescape_length(const char *s, size_t length) { /* hexadecimal encoding */ int a, b; - if ((a = unhexchar(f[1])) < 0 || - (b = unhexchar(f[2])) < 0) { + a = unhexchar(f[1]); + b = unhexchar(f[2]); + + if (a < 0 || b < 0) { /* Invalid escape code, let's take it literal then */ *(t++) = '\\'; *(t++) = 'x'; @@ -1793,9 +1914,11 @@ char *cunescape_length(const char *s, size_t length) { /* octal encoding */ int a, b, c; - if ((a = unoctchar(f[0])) < 0 || - (b = unoctchar(f[1])) < 0 || - (c = unoctchar(f[2])) < 0) { + a = unoctchar(f[0]); + b = unoctchar(f[1]); + c = unoctchar(f[2]); + + if (a < 0 || b < 0 || c < 0) { /* Invalid escape code, let's take it literal then */ *(t++) = '\\'; *(t++) = f[0]; @@ -2084,13 +2207,47 @@ int fd_cloexec(int fd, bool cloexec) { return 0; } +static bool fd_in_set(int fd, const int fdset[], unsigned n_fdset) { + unsigned i; + + assert(n_fdset == 0 || fdset); + + for (i = 0; i < n_fdset; i++) + if (fdset[i] == fd) + return true; + + return false; +} + int close_all_fds(const int except[], unsigned n_except) { DIR *d; struct dirent *de; int r = 0; - if (!(d = opendir("/proc/self/fd"))) - return -errno; + assert(n_except == 0 || except); + + d = opendir("/proc/self/fd"); + if (!d) { + int fd; + struct rlimit rl; + + /* When /proc isn't available (for example in chroots) + * the fallback is brute forcing through the fd + * table */ + + assert_se(getrlimit(RLIMIT_NOFILE, &rl) >= 0); + for (fd = 3; fd < (int) rl.rlim_max; fd ++) { + + if (fd_in_set(fd, except, n_except)) + continue; + + if (close_nointr(fd) < 0) + if (errno != EBADF && r == 0) + r = -errno; + } + + return r; + } while ((de = readdir(d))) { int fd = -1; @@ -2108,20 +2265,8 @@ int close_all_fds(const int except[], unsigned n_except) { if (fd == dirfd(d)) continue; - if (except) { - bool found; - unsigned i; - - found = false; - for (i = 0; i < n_except; i++) - if (except[i] == fd) { - found = true; - break; - } - - if (found) - continue; - } + if (fd_in_set(fd, except, n_except)) + continue; if (close_nointr(fd) < 0) { /* Valgrind has its own FD and doesn't want to have it closed */ @@ -2323,7 +2468,7 @@ fail: return r; } -int read_one_char(FILE *f, char *ret, bool *need_nl) { +int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) { struct termios old_termios, new_termios; char c; char line[LINE_MAX]; @@ -2341,6 +2486,13 @@ int read_one_char(FILE *f, char *ret, bool *need_nl) { if (tcsetattr(fileno(f), TCSADRAIN, &new_termios) >= 0) { size_t k; + if (t != (usec_t) -1) { + if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0) { + tcsetattr(fileno(f), TCSADRAIN, &old_termios); + return -ETIMEDOUT; + } + } + k = fread(&c, 1, 1, f); tcsetattr(fileno(f), TCSADRAIN, &old_termios); @@ -2356,7 +2508,11 @@ int read_one_char(FILE *f, char *ret, bool *need_nl) { } } - if (!(fgets(line, sizeof(line), f))) + if (t != (usec_t) -1) + if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0) + return -ETIMEDOUT; + + if (!fgets(line, sizeof(line), f)) return -EIO; truncate_nl(line); @@ -2387,18 +2543,19 @@ int ask(char *ret, const char *replies, const char *text, ...) { bool need_nl = true; if (on_tty) - fputs("\x1B[1m", stdout); + fputs(ANSI_HIGHLIGHT_ON, stdout); va_start(ap, text); vprintf(text, ap); va_end(ap); if (on_tty) - fputs("\x1B[0m", stdout); + fputs(ANSI_HIGHLIGHT_OFF, stdout); fflush(stdout); - if ((r = read_one_char(stdin, &c, &need_nl)) < 0) { + r = read_one_char(stdin, &c, (usec_t) -1, &need_nl); + if (r < 0) { if (r == -EBADMSG) { puts("Bad input, please try again."); @@ -2421,10 +2578,9 @@ int ask(char *ret, const char *replies, const char *text, ...) { } } -int reset_terminal_fd(int fd) { +int reset_terminal_fd(int fd, bool switch_to_text) { struct termios termios; int r = 0; - long arg; /* Set terminal to some sane defaults */ @@ -2437,9 +2593,12 @@ int reset_terminal_fd(int fd) { /* Disable exclusive mode, just in case */ ioctl(fd, TIOCNXCL); + /* Switch to text mode */ + if (switch_to_text) + ioctl(fd, KDSETMODE, KD_TEXT); + /* Enable console unicode mode */ - arg = K_UNICODE; - ioctl(fd, KDSKBMODE, &arg); + ioctl(fd, KDSKBMODE, K_UNICODE); if (tcgetattr(fd, &termios) < 0) { r = -errno; @@ -2490,7 +2649,7 @@ int reset_terminal(const char *name) { if (fd < 0) return fd; - r = reset_terminal_fd(fd); + r = reset_terminal_fd(fd, true); close_nointr_nofail(fd); return r; @@ -2645,7 +2804,7 @@ int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocst ssize_t l; struct inotify_event *e; - if ((l = read(notify, &inotify_buffer, sizeof(inotify_buffer))) < 0) { + if ((l = read(notify, inotify_buffer, sizeof(inotify_buffer))) < 0) { if (errno == EINTR) continue; @@ -2684,7 +2843,8 @@ 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(fd)) < 0) + r = reset_terminal_fd(fd, true); + if (r < 0) log_warning("Failed to reset terminal: %s", strerror(-r)); return fd; @@ -2856,7 +3016,8 @@ ssize_t loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) { while (nbytes > 0) { ssize_t k; - if ((k = write(fd, p, nbytes)) <= 0) { + k = write(fd, p, nbytes); + if (k <= 0) { if (k < 0 && errno == EINTR) continue; @@ -2995,6 +3156,8 @@ int parse_bytes(const char *t, off_t *bytes) { { "M", 1024ULL*1024ULL }, { "G", 1024ULL*1024ULL*1024ULL }, { "T", 1024ULL*1024ULL*1024ULL*1024ULL }, + { "P", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, + { "E", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, { "", 1 }, }; @@ -3134,11 +3297,15 @@ fallback: void rename_process(const char name[8]) { assert(name); - prctl(PR_SET_NAME, name); + /* This is a like a poor man's setproctitle(). It changes the + * comm field, argv[0], and also the glibc's internally used + * name of the process. For the first one a limit of 16 chars + * applies, to the second one usually one of 10 (i.e. length + * of "/sbin/init"), to the third one one of 7 (i.e. length of + * "systemd"). If you pass a longer string it will be + * truncated */ - /* This is a like a poor man's setproctitle(). The string - * passed should fit in 7 chars (i.e. the length of - * "systemd") */ + prctl(PR_SET_NAME, name); if (program_invocation_name) strncpy(program_invocation_name, name, strlen(program_invocation_name)); @@ -3399,7 +3566,9 @@ static int rm_rf_children(int fd, bool only_dirs, bool honour_sticky) { } if (honour_sticky) - keep_around = st.st_uid == 0 && (st.st_mode & S_ISVTX); + keep_around = + (st.st_uid == 0 || st.st_uid == getuid()) && + (st.st_mode & S_ISVTX); is_dir = S_ISDIR(st.st_mode); @@ -3413,7 +3582,9 @@ static int rm_rf_children(int fd, bool only_dirs, bool honour_sticky) { continue; } - keep_around = st.st_uid == 0 && (st.st_mode & S_ISVTX); + keep_around = + (st.st_uid == 0 || st.st_uid == getuid()) && + (st.st_mode & S_ISVTX); } is_dir = de->d_type == DT_DIR; @@ -3422,7 +3593,8 @@ static int rm_rf_children(int fd, bool only_dirs, bool honour_sticky) { if (is_dir) { int subdir_fd; - if ((subdir_fd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC)) < 0) { + subdir_fd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (subdir_fd < 0) { if (ret == 0 && errno != ENOENT) ret = -errno; continue; @@ -3475,7 +3647,7 @@ int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky if (delete_root) { - if (honour_sticky && file_is_sticky(path) > 0) + if (honour_sticky && file_is_priv_sticky(path) > 0) return r; if (rmdir(path) < 0 && errno != ENOENT) { @@ -3494,10 +3666,28 @@ int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) { * first change the access mode and only then hand out * ownership to avoid a window where access is too open. */ - if (chmod(path, mode) < 0) + if (mode != (mode_t) -1) + if (chmod(path, mode) < 0) + return -errno; + + if (uid != (uid_t) -1 || gid != (gid_t) -1) + if (chown(path, uid, gid) < 0) + return -errno; + + return 0; +} + +int fchmod_and_fchown(int fd, mode_t mode, uid_t uid, gid_t gid) { + assert(fd >= 0); + + /* Under the assumption that we are running privileged we + * first change the access mode and only then hand out + * ownership to avoid a window where access is too open. */ + + if (fchmod(fd, mode) < 0) return -errno; - if (chown(path, uid, gid) < 0) + if (fchown(fd, uid, gid) < 0) return -errno; return 0; @@ -3531,9 +3721,12 @@ cpu_set_t* cpu_set_malloc(unsigned *ncpus) { } } -void status_vprintf(const char *format, va_list ap) { - char *s = NULL; - int fd = -1; +void status_vprintf(const char *status, bool ellipse, const char *format, va_list ap) { + char *s = NULL, *spaces = NULL, *e; + int fd = -1, c; + size_t emax, sl, left; + struct iovec iovec[5]; + int n = 0; assert(format); @@ -3543,25 +3736,69 @@ void status_vprintf(const char *format, va_list ap) { if (vasprintf(&s, format, ap) < 0) goto finish; - if ((fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC)) < 0) + fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); + if (fd < 0) goto finish; - write(fd, s, strlen(s)); + if (ellipse) { + c = fd_columns(fd); + if (c <= 0) + c = 80; + + if (status) { + sl = 2 + 6 + 1; /* " [" status "]" */ + emax = (size_t) c > sl ? c - sl - 1 : 0; + } else + emax = c - 1; + + e = ellipsize(s, emax, 75); + if (e) { + free(s); + s = e; + } + } + + zero(iovec); + IOVEC_SET_STRING(iovec[n++], s); + + if (ellipse) { + sl = strlen(s); + left = emax > sl ? emax - sl : 0; + if (left > 0) { + spaces = malloc(left); + if (spaces) { + memset(spaces, ' ', left); + iovec[n].iov_base = spaces; + iovec[n].iov_len = left; + n++; + } + } + } + + if (status) { + IOVEC_SET_STRING(iovec[n++], " ["); + IOVEC_SET_STRING(iovec[n++], status); + IOVEC_SET_STRING(iovec[n++], "]\n"); + } else + IOVEC_SET_STRING(iovec[n++], "\n"); + + writev(fd, iovec, n); finish: free(s); + free(spaces); if (fd >= 0) close_nointr_nofail(fd); } -void status_printf(const char *format, ...) { +void status_printf(const char *status, bool ellipse, const char *format, ...) { va_list ap; assert(format); va_start(ap, format); - status_vprintf(format, ap); + status_vprintf(status, ellipse, format, ap); va_end(ap); } @@ -3579,146 +3816,15 @@ void status_welcome(void) { 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)); - } - } - - 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)); - } - } - - if (!ansi_color) - const_color = "0;32"; /* Green for openSUSE */ - -#elif defined(TARGET_GENTOO) - - if (!pretty_name) { - if ((r = read_one_line_file("/etc/gentoo-release", &pretty_name)) < 0) { - - if (r != -ENOENT) - log_warning("Failed to read /etc/gentoo-release: %s", strerror(-r)); - } - } - - if (!ansi_color) - const_color = "1;34"; /* Light Blue for Gentoo */ - -#elif defined(TARGET_ALTLINUX) - - if (!pretty_name) { - if ((r = read_one_line_file("/etc/altlinux-release", &pretty_name)) < 0) { - - if (r != -ENOENT) - log_warning("Failed to read /etc/altlinux-release: %s", strerror(-r)); - } - } - - if (!ansi_color) - const_color = "0;36"; /* Cyan for ALTLinux */ - - -#elif defined(TARGET_DEBIAN) - - if (!pretty_name) { - char *version; - - if ((r = read_one_line_file("/etc/debian_version", &version)) < 0) { - - if (r != -ENOENT) - log_warning("Failed to read /etc/debian_version: %s", strerror(-r)); - } else { - pretty_name = strappend("Debian ", version); - free(version); - - if (!pretty_name) - log_warning("Failed to allocate Debian version string."); - } - } - - if (!ansi_color) - const_color = "1;31"; /* Light Red for Debian */ - -#elif defined(TARGET_UBUNTU) - - if ((r = parse_env_file("/etc/lsb-release", NEWLINE, - "DISTRIB_DESCRIPTION", &pretty_name, - NULL)) < 0) { - - if (r != -ENOENT) - log_warning("Failed to read /etc/lsb-release: %s", strerror(-r)); - } - - if (!ansi_color) - const_color = "0;33"; /* Orange/Brown for Ubuntu */ - -#elif defined(TARGET_MANDRIVA) - - if (!pretty_name) { - char *s, *p; - - if ((r = read_one_line_file("/etc/mandriva-release", &s) < 0)) { - if (r != -ENOENT) - log_warning("Failed to read /etc/mandriva-release: %s", strerror(-r)); - } else { - p = strstr(s, " release "); - if (p) { - *p = '\0'; - p += 9; - p[strcspn(p, " ")] = '\0'; - - /* This corresponds to standard rc.sysinit */ - if (asprintf(&pretty_name, "%s\x1B[0;39m %s", s, p) > 0) - const_color = "1;36"; - else - log_warning("Failed to allocate Mandriva version string."); - } else - log_warning("Failed to parse /etc/mandriva-release"); - free(s); - } - } -#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", + status_printf(NULL, + false, + "\nWelcome to \x1B[%sm%s\x1B[0m!\n", const_color ? const_color : ansi_color, const_pretty ? const_pretty : pretty_name); @@ -3860,23 +3966,32 @@ char **replace_env_argv(char **argv, char **env) { return r; } -int columns(void) { +int fd_columns(int fd) { + struct winsize ws; + zero(ws); + + if (ioctl(fd, TIOCGWINSZ, &ws) < 0) + return -errno; + + if (ws.ws_col <= 0) + return -EIO; + + return ws.ws_col; +} + +unsigned columns(void) { static __thread int parsed_columns = 0; const char *e; if (_likely_(parsed_columns > 0)) return parsed_columns; - if ((e = getenv("COLUMNS"))) + e = getenv("COLUMNS"); + if (e) 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 = fd_columns(STDOUT_FILENO); if (parsed_columns <= 0) parsed_columns = 80; @@ -3884,57 +3999,93 @@ int columns(void) { return parsed_columns; } -int running_in_chroot(void) { - struct stat a, b; - - zero(a); - zero(b); - - /* Only works as root */ +int fd_lines(int fd) { + struct winsize ws; + zero(ws); - if (stat("/proc/1/root", &a) < 0) + if (ioctl(fd, TIOCGWINSZ, &ws) < 0) return -errno; - if (stat("/", &b) < 0) - return -errno; + if (ws.ws_row <= 0) + return -EIO; + + return ws.ws_row; +} + +unsigned lines(void) { + static __thread int parsed_lines = 0; + const char *e; + + if (_likely_(parsed_lines > 0)) + return parsed_lines; + + e = getenv("LINES"); + if (e) + parsed_lines = atoi(e); + + if (parsed_lines <= 0) + parsed_lines = fd_lines(STDOUT_FILENO); + + if (parsed_lines <= 0) + parsed_lines = 25; + + return parsed_lines; +} + +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 *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) { + size_t x; char *r; assert(s); assert(percent <= 100); - assert(length >= 3); - - l = strlen(s); + assert(new_length >= 3); - if (l <= 3 || l <= length) - return strdup(s); + if (old_length <= 3 || old_length <= new_length) + return strndup(s, old_length); - if (!(r = new0(char, length+1))) + r = new0(char, new_length+1); + if (!r) return r; - x = (length * percent) / 100; + x = (new_length * percent) / 100; - if (x > length - 3) - x = length - 3; + if (x > new_length - 3) + x = new_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); + s + old_length - (new_length - x - 3), + new_length - x - 3); return r; } +char *ellipsize(const char *s, size_t length, unsigned percent) { + return ellipsize_mem(s, strlen(s), length, percent); +} + int touch(const char *path) { int fd; @@ -3951,7 +4102,8 @@ char *unquote(const char *s, const char* quotes) { size_t l; assert(s); - if ((l = strlen(s)) < 2) + l = strlen(s); + if (l < 2) return strdup(s); if (strchr(quotes, s[0]) && s[l-1] == s[0]) @@ -4053,7 +4205,7 @@ int wait_for_terminate_and_warn(const char *name, pid_t pid) { } -void freeze(void) { +_noreturn_ void freeze(void) { /* Make sure nobody waits for us on a socket anymore */ close_all_fds(NULL, 0); @@ -4243,34 +4395,40 @@ int vtnr_from_tty(const char *tty) { return i; } -const char *default_term_for_tty(const char *tty) { +bool tty_is_vc_resolve(const char *tty) { char *active = NULL; - const char *term; + bool b; assert(tty); if (startswith(tty, "/dev/")) tty += 5; - /* Resolve where /dev/console is pointing when determining - * TERM */ + /* Resolve where /dev/console is pointing to */ 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 = strrchr(active, ' '); + if (tty) tty++; else tty = active; } - term = tty_is_vc(tty) ? "TERM=linux" : "TERM=vt100"; + b = tty_is_vc(tty); free(active); - return term; + return b; } -bool dirent_is_file(struct dirent *de) { +const char *default_term_for_tty(const char *tty) { + assert(tty); + + return tty_is_vc_resolve(tty) ? "TERM=linux" : "TERM=vt100"; +} + +bool dirent_is_file(const struct dirent *de) { assert(de); if (ignore_file(de->d_name)) @@ -4284,6 +4442,15 @@ bool dirent_is_file(struct dirent *de) { return true; } +bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) { + assert(de); + + if (!dirent_is_file(de)) + return false; + + return endswith(de->d_name, suffix); +} + void execute_directory(const char *directory, DIR *d, char *argv[]) { DIR *_d = NULL; struct dirent *de; @@ -4358,11 +4525,12 @@ void execute_directory(const char *directory, DIR *d, char *argv[]) { } while (!hashmap_isempty(pids)) { + pid_t pid = PTR_TO_UINT(hashmap_first_key(pids)); siginfo_t si; char *path; zero(si); - if (waitid(P_ALL, 0, &si, WEXITED) < 0) { + if (waitid(P_PID, pid, &si, WEXITED) < 0) { if (errno == EINTR) continue; @@ -4456,6 +4624,98 @@ void parse_syslog_priority(char **p, int *priority) { *p += k; } +void skip_syslog_pid(char **buf) { + char *p; + + assert(buf); + assert(*buf); + + p = *buf; + + if (*p != '[') + return; + + p++; + p += strspn(p, "0123456789"); + + if (*p != ']') + return; + + p++; + + *buf = p; +} + +void skip_syslog_date(char **buf) { + enum { + LETTER, + SPACE, + NUMBER, + SPACE_OR_NUMBER, + COLON + } sequence[] = { + LETTER, LETTER, LETTER, + SPACE, + SPACE_OR_NUMBER, NUMBER, + SPACE, + SPACE_OR_NUMBER, NUMBER, + COLON, + SPACE_OR_NUMBER, NUMBER, + COLON, + SPACE_OR_NUMBER, NUMBER, + SPACE + }; + + char *p; + unsigned i; + + assert(buf); + assert(*buf); + + p = *buf; + + for (i = 0; i < ELEMENTSOF(sequence); i++, p++) { + + if (!*p) + return; + + switch (sequence[i]) { + + case SPACE: + if (*p != ' ') + return; + break; + + case SPACE_OR_NUMBER: + if (*p == ' ') + break; + + /* fall through */ + + case NUMBER: + if (*p < '0' || *p > '9') + return; + + break; + + case LETTER: + if (!(*p >= 'A' && *p <= 'Z') && + !(*p >= 'a' && *p <= 'z')) + return; + + break; + + case COLON: + if (*p != ':') + return; + break; + + } + } + + *buf = p; +} + int have_effective_cap(int value) { cap_t cap; cap_flag_value_t fv; @@ -4544,6 +4804,24 @@ int pipe_eof(int fd) { return pollfd.revents & POLLHUP; } +int fd_wait_for_event(int fd, int event, usec_t t) { + struct pollfd pollfd; + int r; + + zero(pollfd); + pollfd.fd = fd; + pollfd.events = event; + + r = poll(&pollfd, 1, t == (usec_t) -1 ? -1 : (int) (t / USEC_PER_MSEC)); + if (r < 0) + return -errno; + + if (r == 0) + return 0; + + return pollfd.revents; +} + int fopen_temporary(const char *path, FILE **_f, char **_temp_path) { FILE *f; char *t; @@ -4675,21 +4953,6 @@ int vt_disallocate(const char *name) { return 0; } - -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 buffer, *de; @@ -4715,7 +4978,7 @@ static int files_add(Hashmap *h, const char *path, const char *suffix) { if (!de) break; - if (!file_is_conf(de, suffix)) + if (!dirent_is_file_with_suffix(de, suffix)) continue; if (asprintf(&p, "%s/%s", path, de->d_name) < 0) { @@ -4877,13 +5140,84 @@ int hwclock_reset_localtime_delta(void) { return 0; } +int rtc_open(int flags) { + int fd; + DIR *d; + + /* First, we try to make use of the /dev/rtc symlink. If that + * doesn't exist, we open the first RTC which has hctosys=1 + * set. If we don't find any we just take the first RTC that + * exists at all. */ + + fd = open("/dev/rtc", flags); + if (fd >= 0) + return fd; + + d = opendir("/sys/class/rtc"); + if (!d) + goto fallback; + + for (;;) { + char *p, *v; + struct dirent buf, *de; + int r; + + r = readdir_r(d, &buf, &de); + if (r != 0) + goto fallback; + + if (!de) + goto fallback; + + if (ignore_file(de->d_name)) + continue; + + p = join("/sys/class/rtc/", de->d_name, "/hctosys", NULL); + if (!p) { + closedir(d); + return -ENOMEM; + } + + r = read_one_line_file(p, &v); + free(p); + + if (r < 0) + continue; + + r = parse_boolean(v); + free(v); + + if (r <= 0) + continue; + + p = strappend("/dev/", de->d_name); + fd = open(p, flags); + free(p); + + if (fd >= 0) { + closedir(d); + return fd; + } + } + +fallback: + if (d) + closedir(d); + + fd = open("/dev/rtc0", flags); + if (fd < 0) + return -errno; + + return fd; +} + int hwclock_get_time(struct tm *tm) { int fd; int err = 0; assert(tm); - fd = open("/dev/rtc0", O_RDONLY|O_CLOEXEC); + fd = rtc_open(O_RDONLY|O_CLOEXEC); if (fd < 0) return -errno; @@ -4907,7 +5241,7 @@ int hwclock_set_time(const struct tm *tm) { assert(tm); - fd = open("/dev/rtc0", O_RDONLY|O_CLOEXEC); + fd = rtc_open(O_RDONLY|O_CLOEXEC); if (fd < 0) return -errno; @@ -5066,21 +5400,27 @@ int symlink_or_copy_atomic(const char *from, const char *to) { } int audit_session_from_pid(pid_t pid, uint32_t *id) { - char *p, *s; + char *s; uint32_t u; int r; - assert(pid >= 1); assert(id); if (have_effective_cap(CAP_AUDIT_CONTROL) <= 0) return -ENOENT; - if (asprintf(&p, "/proc/%lu/sessionid", (unsigned long) pid) < 0) - return -ENOMEM; + if (pid == 0) + r = read_one_line_file("/proc/self/sessionid", &s); + else { + char *p; + + if (asprintf(&p, "/proc/%lu/sessionid", (unsigned long) pid) < 0) + return -ENOMEM; + + r = read_one_line_file(p, &s); + free(p); + } - r = read_one_line_file(p, &s); - free(p); if (r < 0) return r; @@ -5097,6 +5437,51 @@ int audit_session_from_pid(pid_t pid, uint32_t *id) { return 0; } +int audit_loginuid_from_pid(pid_t pid, uid_t *uid) { + char *s; + uid_t u; + int r; + + assert(uid); + + /* Only use audit login uid if we are executed with sufficient + * capabilities so that pam_loginuid could do its job. If we + * are lacking the CAP_AUDIT_CONTROL capabality we most likely + * are being run in a container and /proc/self/loginuid is + * useless since it probably contains a uid of the host + * system. */ + + if (have_effective_cap(CAP_AUDIT_CONTROL) <= 0) + return -ENOENT; + + if (pid == 0) + r = read_one_line_file("/proc/self/loginuid", &s); + else { + char *p; + + if (asprintf(&p, "/proc/%lu/loginuid", (unsigned long) pid) < 0) + return -ENOMEM; + + r = read_one_line_file(p, &s); + free(p); + } + + if (r < 0) + return r; + + r = parse_uid(s, &u); + free(s); + + if (r < 0) + return r; + + if (u == (uid_t) -1) + return -ENOENT; + + *uid = (uid_t) u; + return 0; +} + bool display_is_local(const char *display) { assert(display); @@ -5224,6 +5609,36 @@ int get_group_creds(const char **groupname, gid_t *gid) { return 0; } +int in_group(const char *name) { + gid_t gid, *gids; + int ngroups_max, r, i; + + r = get_group_creds(&name, &gid); + if (r < 0) + return r; + + if (getgid() == gid) + return 1; + + if (getegid() == gid) + return 1; + + ngroups_max = sysconf(_SC_NGROUPS_MAX); + assert(ngroups_max > 0); + + gids = alloca(sizeof(gid_t) * ngroups_max); + + r = getgroups(ngroups_max, gids); + if (r < 0) + return -errno; + + for (i = 0; i < r; i++) + if (gids[i] == gid) + return 1; + + return 0; +} + int glob_exists(const char *path) { glob_t g; int r, k; @@ -5488,7 +5903,7 @@ int block_get_whole_disk(dev_t d, dev_t *ret) { return -ENOENT; } -int file_is_sticky(const char *p) { +int file_is_priv_sticky(const char *p) { struct stat st; assert(p); @@ -5497,7 +5912,7 @@ int file_is_sticky(const char *p) { return -errno; return - st.st_uid == 0 && + (st.st_uid == 0 || st.st_uid == getuid()) && (st.st_mode & S_ISVTX); } @@ -5704,6 +6119,24 @@ int strdup_or_null(const char *a, char **b) { return 0; } +int prot_from_flags(int flags) { + + switch (flags & O_ACCMODE) { + + case O_RDONLY: + return PROT_READ; + + case O_WRONLY: + return PROT_WRITE; + + case O_RDWR: + return PROT_READ|PROT_WRITE; + + default: + return -EINVAL; + } +} + unsigned long cap_last_cap(void) { static __thread unsigned long saved; static __thread bool valid = false; @@ -5736,3 +6169,88 @@ unsigned long cap_last_cap(void) { return p; } + +char *format_bytes(char *buf, size_t l, off_t t) { + unsigned i; + + static const struct { + const char *suffix; + off_t factor; + } table[] = { + { "E", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, + { "P", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, + { "T", 1024ULL*1024ULL*1024ULL*1024ULL }, + { "G", 1024ULL*1024ULL*1024ULL }, + { "M", 1024ULL*1024ULL }, + { "K", 1024ULL }, + }; + + for (i = 0; i < ELEMENTSOF(table); i++) { + + if (t >= table[i].factor) { + snprintf(buf, l, + "%llu.%llu%s", + (unsigned long long) (t / table[i].factor), + (unsigned long long) (((t*10ULL) / table[i].factor) % 10ULL), + table[i].suffix); + + goto finish; + } + } + + snprintf(buf, l, "%lluB", (unsigned long long) t); + +finish: + buf[l-1] = 0; + return buf; + +} + +void* memdup(const void *p, size_t l) { + void *r; + + assert(p); + + r = malloc(l); + if (!r) + return NULL; + + memcpy(r, p, l); + return r; +} + +int fd_inc_sndbuf(int fd, size_t n) { + int r, value; + socklen_t l = sizeof(value); + + r = getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, &l); + if (r >= 0 && + l == sizeof(value) && + (size_t) value >= n*2) + return 0; + + value = (int) n; + r = setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, sizeof(value)); + if (r < 0) + return -errno; + + return 1; +} + +int fd_inc_rcvbuf(int fd, size_t n) { + int r, value; + socklen_t l = sizeof(value); + + r = getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, &l); + if (r >= 0 && + l == sizeof(value) && + (size_t) value >= n*2) + return 0; + + value = (int) n; + r = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, sizeof(value)); + if (r < 0) + return -errno; + + return 1; +}