X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fshared%2Futil.c;h=887cc6749e7610a2a75b5e890451bfd037fc3bea;hp=d8d3f1a16d302f95305a90ab1ffe121e0fb43893;hb=2fbe635a83a79f8889afec421ae3990ea106fb91;hpb=6edd7d0a09171ea5ae8e01b7b1cbcb0bdfbfeb16 diff --git a/src/shared/util.c b/src/shared/util.c index d8d3f1a16..887cc6749 100644 --- a/src/shared/util.c +++ b/src/shared/util.c @@ -50,11 +50,12 @@ #include #include #include -#include #include #include #include #include +#include +#include #include "macro.h" #include "util.h" @@ -488,7 +489,7 @@ char *split_quoted(const char *c, size_t *l, char **state) { int get_parent_of_pid(pid_t pid, pid_t *_ppid) { int r; - FILE *f; + _cleanup_fclose_ FILE *f = NULL; char fn[PATH_MAX], line[LINE_MAX], *p; long unsigned ppid; @@ -498,22 +499,22 @@ int get_parent_of_pid(pid_t pid, pid_t *_ppid) { 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, "re"))) + f = fopen(fn, "re"); + if (!f) return -errno; - if (!(fgets(line, sizeof(line), f))) { + if (!fgets(line, sizeof(line), f)) { r = feof(f) ? -EIO : -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, ')'))) + p = strrchr(line, ')'); + if (!p) return -EIO; p++; @@ -597,7 +598,8 @@ int write_one_line_file(const char *fn, const char *line) { assert(fn); assert(line); - if (!(f = fopen(fn, "we"))) + f = fopen(fn, "we"); + if (!f) return -errno; errno = 0; @@ -683,8 +685,7 @@ finish: } int read_one_line_file(const char *fn, char **line) { - FILE *f; - int r; + _cleanup_fclose_ FILE *f = NULL; char t[LINE_MAX], *c; assert(fn); @@ -696,50 +697,37 @@ int read_one_line_file(const char *fn, char **line) { if (!fgets(t, sizeof(t), f)) { - if (ferror(f)) { - r = -errno; - goto finish; - } + if (ferror(f)) + return -errno; t[0] = 0; } c = strdup(t); - if (!c) { - r = -ENOMEM; - goto finish; - } - + if (!c) + return -ENOMEM; truncate_nl(c); *line = c; - r = 0; - -finish: - fclose(f); - return r; + return 0; } int read_full_file(const char *fn, char **contents, size_t *size) { - FILE *f; - int r; + _cleanup_fclose_ FILE *f = NULL; size_t n, l; - char *buf = NULL; + _cleanup_free_ char *buf = NULL; struct stat st; - if (!(f = fopen(fn, "re"))) + f = fopen(fn, "re"); + if (!f) return -errno; - if (fstat(fileno(f), &st) < 0) { - r = -errno; - goto finish; - } + if (fstat(fileno(f), &st) < 0) + return -errno; /* Safety check */ - if (st.st_size > 4*1024*1024) { - r = -E2BIG; - goto finish; - } + if (st.st_size > 4*1024*1024) + return -E2BIG; n = st.st_size > 0 ? st.st_size : LINE_MAX; l = 0; @@ -748,19 +736,16 @@ int read_full_file(const char *fn, char **contents, size_t *size) { char *t; size_t k; - if (!(t = realloc(buf, n+1))) { - r = -ENOMEM; - goto finish; - } + t = realloc(buf, n+1); + if (!t) + return -ENOMEM; buf = t; k = fread(buf + l, 1, n - l, f); if (k <= 0) { - if (ferror(f)) { - r = -errno; - goto finish; - } + if (ferror(f)) + return -errno; break; } @@ -769,10 +754,8 @@ int read_full_file(const char *fn, char **contents, size_t *size) { n *= 2; /* Safety check */ - if (n > 4*1024*1024) { - r = -E2BIG; - goto finish; - } + if (n > 4*1024*1024) + return -E2BIG; } buf[l] = 0; @@ -782,13 +765,7 @@ int read_full_file(const char *fn, char **contents, size_t *size) { if (size) *size = l; - r = 0; - -finish: - fclose(f); - free(buf); - - return r; + return 0; } int parse_env_file( @@ -907,8 +884,7 @@ int load_env_file( continue; if (!(u = normalize_env_assignment(p))) { - log_error("Out of memory"); - r = -ENOMEM; + r = log_oom(); goto finish; } @@ -916,8 +892,7 @@ int load_env_file( free(u); if (!t) { - log_error("Out of memory"); - r = -ENOMEM; + r = log_oom(); goto finish; } @@ -1081,7 +1056,7 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char * if (h < 0) return h; - r = join("[", t, "]", NULL); + r = strjoin("[", t, "]", NULL); free(t); if (!r) @@ -1562,19 +1537,25 @@ char *cescape(const char *s) { return r; } -char *cunescape_length(const char *s, size_t length) { +char *cunescape_length_with_prefix(const char *s, size_t length, const char *prefix) { char *r, *t; const char *f; + size_t pl; assert(s); - /* Undoes C style string escaping */ + /* Undoes C style string escaping, and optionally prefixes it. */ + + pl = prefix ? strlen(prefix) : 0; - r = new(char, length+1); + r = new(char, pl+length+1); if (!r) return r; - for (f = s, t = r; f < s + length; f++) { + if (prefix) + memcpy(r, prefix, pl); + + for (f = s, t = r + pl; f < s + length; f++) { if (*f != '\\') { *(t++) = *f; @@ -1685,7 +1666,13 @@ finish: return r; } +char *cunescape_length(const char *s, size_t length) { + return cunescape_length_with_prefix(s, length, NULL); +} + char *cunescape(const char *s) { + assert(s); + return cunescape_length(s, strlen(s)); } @@ -1788,7 +1775,7 @@ char *ascii_strlower(char *t) { return t; } -bool ignore_file(const char *filename) { +static bool ignore_file_allow_backup(const char *filename) { assert(filename); return @@ -1796,7 +1783,6 @@ bool ignore_file(const char *filename) { streq(filename, "lost+found") || streq(filename, "aquota.user") || streq(filename, "aquota.group") || - endswith(filename, "~") || endswith(filename, ".rpmnew") || endswith(filename, ".rpmsave") || endswith(filename, ".rpmorig") || @@ -1805,6 +1791,15 @@ bool ignore_file(const char *filename) { endswith(filename, ".swp"); } +bool ignore_file(const char *filename) { + assert(filename); + + if (endswith(filename, "~")) + return false; + + return ignore_file_allow_backup(filename); +} + int fd_nonblock(int fd, bool nonblock) { int flags; @@ -2305,12 +2300,14 @@ int open_terminal(const char *name, int mode) { */ for (;;) { - if ((fd = open(name, mode)) >= 0) + fd = open(name, mode); + if (fd >= 0) break; if (errno != EIO) return -errno; + /* Max 1s in total */ if (c >= 20) return -errno; @@ -2321,7 +2318,8 @@ int open_terminal(const char *name, int mode) { if (fd < 0) return -errno; - if ((r = isatty(fd)) < 0) { + r = isatty(fd); + if (r < 0) { close_nointr_nofail(fd); return -errno; } @@ -2373,8 +2371,16 @@ int flush_fd(int fd) { } } -int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocstty_eperm) { - int fd = -1, notify = -1, r, wd = -1; +int acquire_terminal( + const char *name, + bool fail, + bool force, + bool ignore_tiocstty_eperm, + usec_t timeout) { + + int fd = -1, notify = -1, r = 0, wd = -1; + usec_t ts = 0; + struct sigaction sa_old, sa_new; assert(name); @@ -2391,40 +2397,57 @@ int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocst * on the same tty as an untrusted user this should not be a * problem. (Which he probably should not do anyway.) */ + if (timeout != (usec_t) -1) + ts = now(CLOCK_MONOTONIC); + if (!fail && !force) { - if ((notify = inotify_init1(IN_CLOEXEC)) < 0) { + notify = inotify_init1(IN_CLOEXEC | (timeout != (usec_t) -1 ? IN_NONBLOCK : 0)); + if (notify < 0) { r = -errno; goto fail; } - if ((wd = inotify_add_watch(notify, name, IN_CLOSE)) < 0) { + wd = inotify_add_watch(notify, name, IN_CLOSE); + if (wd < 0) { r = -errno; goto fail; } } for (;;) { - if (notify >= 0) - if ((r = flush_fd(notify)) < 0) + if (notify >= 0) { + r = flush_fd(notify); + if (r < 0) goto fail; + } /* 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|O_CLOEXEC)) < 0) + fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) return fd; + /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed + * if we already own the tty. */ + zero(sa_new); + sa_new.sa_handler = SIG_IGN; + sa_new.sa_flags = SA_RESTART; + assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0); + /* First, try to get the tty */ - r = ioctl(fd, TIOCSCTTY, force); + if (ioctl(fd, TIOCSCTTY, force) < 0) + r = -errno; + + assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0); /* Sometimes it makes sense to ignore TIOCSCTTY * returning EPERM, i.e. when very likely we already * are have this controlling terminal. */ - if (r < 0 && errno == EPERM && ignore_tiocstty_eperm) + if (r < 0 && r == -EPERM && ignore_tiocstty_eperm) r = 0; - if (r < 0 && (force || fail || errno != EPERM)) { - r = -errno; + if (r < 0 && (force || fail || r != -EPERM)) { goto fail; } @@ -2440,9 +2463,29 @@ 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 (timeout != (usec_t) -1) { + usec_t n; + + n = now(CLOCK_MONOTONIC); + if (ts + timeout < n) { + r = -ETIMEDOUT; + goto fail; + } - if (errno == EINTR) + r = fd_wait_for_event(fd, POLLIN, ts + timeout - n); + if (r < 0) + goto fail; + + if (r == 0) { + r = -ETIMEDOUT; + goto fail; + } + } + + l = read(notify, inotify_buffer, sizeof(inotify_buffer)); + if (l < 0) { + + if (errno == EINTR || errno == EAGAIN) continue; r = -errno; @@ -2706,7 +2749,7 @@ int parse_usec(const char *t, usec_t *usec) { { "m", USEC_PER_MINUTE }, { "usec", 1ULL }, { "us", 1ULL }, - { "", USEC_PER_SEC }, + { "", USEC_PER_SEC }, /* default is sec */ }; const char *p; @@ -2752,6 +2795,71 @@ int parse_usec(const char *t, usec_t *usec) { return 0; } +int parse_nsec(const char *t, nsec_t *nsec) { + static const struct { + const char *suffix; + nsec_t nsec; + } table[] = { + { "sec", NSEC_PER_SEC }, + { "s", NSEC_PER_SEC }, + { "min", NSEC_PER_MINUTE }, + { "hr", NSEC_PER_HOUR }, + { "h", NSEC_PER_HOUR }, + { "d", NSEC_PER_DAY }, + { "w", NSEC_PER_WEEK }, + { "msec", NSEC_PER_MSEC }, + { "ms", NSEC_PER_MSEC }, + { "m", NSEC_PER_MINUTE }, + { "usec", NSEC_PER_USEC }, + { "us", NSEC_PER_USEC }, + { "nsec", 1ULL }, + { "ns", 1ULL }, + { "", 1ULL }, /* default is nsec */ + }; + + const char *p; + nsec_t r = 0; + + assert(t); + assert(nsec); + + p = t; + do { + long long l; + char *e; + unsigned i; + + errno = 0; + l = strtoll(p, &e, 10); + + if (errno != 0) + return -errno; + + if (l < 0) + return -ERANGE; + + if (e == p) + return -EINVAL; + + e += strspn(e, WHITESPACE); + + for (i = 0; i < ELEMENTSOF(table); i++) + if (startswith(e, table[i].suffix)) { + r += (nsec_t) l * table[i].nsec; + p = e + strlen(table[i].suffix); + break; + } + + if (i >= ELEMENTSOF(table)) + return -EINVAL; + + } while (*p != 0); + + *nsec = r; + + return 0; +} + int parse_bytes(const char *t, off_t *bytes) { static const struct { const char *suffix; @@ -2835,7 +2943,8 @@ int make_stdio(int fd) { int make_null_stdio(void) { int null_fd; - if ((null_fd = open("/dev/null", O_RDWR|O_NOCTTY)) < 0) + null_fd = open("/dev/null", O_RDWR|O_NOCTTY); + if (null_fd < 0) return -errno; return make_stdio(null_fd); @@ -2885,7 +2994,8 @@ unsigned long long random_ull(void) { uint64_t ull; ssize_t r; - if ((fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY)) < 0) + fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) goto fallback; r = loop_read(fd, &ull, sizeof(ull), true); @@ -2948,32 +3058,35 @@ char* gethostname_malloc(void) { assert_se(uname(&u) >= 0); - if (u.nodename[0]) + if (!isempty(u.nodename) && !streq(u.nodename, "(none)")) return strdup(u.nodename); return strdup(u.sysname); } -char* getlogname_malloc(void) { - uid_t uid; +bool hostname_is_set(void) { + struct utsname u; + + assert_se(uname(&u) >= 0); + + return !isempty(u.nodename) && !streq(u.nodename, "(none)"); +} + +static char *lookup_uid(uid_t uid) { long bufsize; char *buf, *name; struct passwd pwbuf, *pw = NULL; - struct stat st; - - if (isatty(STDIN_FILENO) && fstat(STDIN_FILENO, &st) >= 0) - uid = st.st_uid; - else - uid = getuid(); /* Shortcut things to avoid NSS lookups */ if (uid == 0) return strdup("root"); - if ((bufsize = sysconf(_SC_GETPW_R_SIZE_MAX)) <= 0) + bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); + if (bufsize <= 0) bufsize = 4096; - if (!(buf = malloc(bufsize))) + buf = malloc(bufsize); + if (!buf) return NULL; if (getpwuid_r(uid, &pwbuf, buf, bufsize, &pw) == 0 && pw) { @@ -2990,6 +3103,28 @@ char* getlogname_malloc(void) { return name; } +char* getlogname_malloc(void) { + uid_t uid; + struct stat st; + + if (isatty(STDIN_FILENO) && fstat(STDIN_FILENO, &st) >= 0) + uid = st.st_uid; + else + uid = getuid(); + + return lookup_uid(uid); +} + +char *getusername_malloc(void) { + const char *e; + + e = getenv("USER"); + if (e) + return strdup(e); + + return lookup_uid(getuid()); +} + int getttyname_malloc(int fd, char **r) { char path[PATH_MAX], *c; int k; @@ -3130,16 +3265,17 @@ int get_ctty(pid_t pid, dev_t *_devnr, char **r) { return 0; } -static int rm_rf_children(int fd, bool only_dirs, bool honour_sticky) { +int rm_rf_children_dangerous(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev) { DIR *d; int ret = 0; assert(fd >= 0); /* This returns the first error we run into, but nevertheless - * tries to go on */ + * tries to go on. This closes the passed fd. */ - if (!(d = fdopendir(fd))) { + d = fdopendir(fd); + if (!d) { close_nointr_nofail(fd); return errno == ENOENT ? 0 : -errno; @@ -3147,12 +3283,13 @@ static int rm_rf_children(int fd, bool only_dirs, bool honour_sticky) { for (;;) { struct dirent buf, *de; - bool is_dir, keep_around = false; + bool is_dir, keep_around; + struct stat st; int r; - if ((r = readdir_r(d, &buf, &de)) != 0) { - if (ret == 0) - ret = -r; + r = readdir_r(d, &buf, &de); + if (r != 0 && ret == 0) { + ret = -r; break; } @@ -3162,54 +3299,43 @@ static int rm_rf_children(int fd, bool only_dirs, bool honour_sticky) { if (streq(de->d_name, ".") || streq(de->d_name, "..")) continue; - if (de->d_type == DT_UNKNOWN) { - struct stat st; - + if (de->d_type == DT_UNKNOWN || + honour_sticky || + (de->d_type == DT_DIR && root_dev)) { if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { if (ret == 0 && errno != ENOENT) ret = -errno; continue; } - if (honour_sticky) - keep_around = - (st.st_uid == 0 || st.st_uid == getuid()) && - (st.st_mode & S_ISVTX); - is_dir = S_ISDIR(st.st_mode); - + keep_around = + honour_sticky && + (st.st_uid == 0 || st.st_uid == getuid()) && + (st.st_mode & S_ISVTX); } else { - if (honour_sticky) { - struct stat st; - - if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { - if (ret == 0 && errno != ENOENT) - ret = -errno; - continue; - } - - keep_around = - (st.st_uid == 0 || st.st_uid == getuid()) && - (st.st_mode & S_ISVTX); - } - is_dir = de->d_type == DT_DIR; + keep_around = false; } if (is_dir) { int subdir_fd; - subdir_fd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + /* if root_dev is set, remove subdirectories only, if device is same as dir */ + if (root_dev && st.st_dev != root_dev->st_dev) + continue; + + subdir_fd = openat(fd, de->d_name, + O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); if (subdir_fd < 0) { if (ret == 0 && errno != ENOENT) ret = -errno; continue; } - if ((r = rm_rf_children(subdir_fd, only_dirs, honour_sticky)) < 0) { - if (ret == 0) - ret = r; - } + r = rm_rf_children_dangerous(subdir_fd, only_dirs, honour_sticky, root_dev); + if (r < 0 && ret == 0) + ret = r; if (!keep_around) if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) { @@ -3231,26 +3357,83 @@ static int rm_rf_children(int fd, bool only_dirs, bool honour_sticky) { return ret; } -int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky) { - int fd; - int r; +int rm_rf_children(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev) { + struct statfs s; + + assert(fd >= 0); + + if (fstatfs(fd, &s) < 0) { + close_nointr_nofail(fd); + return -errno; + } + + /* We refuse to clean disk file systems with this call. This + * is extra paranoia just to be sure we never ever remove + * non-state data */ + + if (s.f_type != TMPFS_MAGIC && + s.f_type != RAMFS_MAGIC) { + log_error("Attempted to remove disk file system, and we can't allow that."); + close_nointr_nofail(fd); + return -EPERM; + } + + return rm_rf_children_dangerous(fd, only_dirs, honour_sticky, root_dev); +} + +static int rm_rf_internal(const char *path, bool only_dirs, bool delete_root, bool honour_sticky, bool dangerous) { + int fd, r; + struct statfs s; assert(path); - if ((fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC)) < 0) { + /* We refuse to clean the root file system with this + * call. This is extra paranoia to never cause a really + * seriously broken system. */ + if (path_equal(path, "/")) { + log_error("Attempted to remove entire root file system, and we can't allow that."); + return -EPERM; + } + + fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); + if (fd < 0) { if (errno != ENOTDIR) return -errno; + if (!dangerous) { + if (statfs(path, &s) < 0) + return -errno; + + if (s.f_type != TMPFS_MAGIC && + s.f_type != RAMFS_MAGIC) { + log_error("Attempted to remove disk file system, and we can't allow that."); + return -EPERM; + } + } + if (delete_root && !only_dirs) - if (unlink(path) < 0) + if (unlink(path) < 0 && errno != ENOENT) return -errno; return 0; } - r = rm_rf_children(fd, only_dirs, honour_sticky); + if (!dangerous) { + if (fstatfs(fd, &s) < 0) { + close_nointr_nofail(fd); + return -errno; + } + if (s.f_type != TMPFS_MAGIC && + s.f_type != RAMFS_MAGIC) { + log_error("Attempted to remove disk file system, and we can't allow that."); + close_nointr_nofail(fd); + return -EPERM; + } + } + + r = rm_rf_children_dangerous(fd, only_dirs, honour_sticky, NULL); if (delete_root) { if (honour_sticky && file_is_priv_sticky(path) > 0) @@ -3265,6 +3448,14 @@ int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky return r; } +int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky) { + return rm_rf_internal(path, only_dirs, delete_root, honour_sticky, false); +} + +int rm_rf_dangerous(const char *path, bool only_dirs, bool delete_root, bool honour_sticky) { + return rm_rf_internal(path, only_dirs, delete_root, honour_sticky, true); +} + int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) { assert(path); @@ -3328,15 +3519,15 @@ cpu_set_t* cpu_set_malloc(unsigned *ncpus) { } 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; + char *s = NULL; + static const char status_indent[] = " "; /* "[" STATUS "] " */ + int fd = -1; struct iovec iovec[5]; int n = 0; assert(format); - /* This independent of logging, as status messages are + /* This is independent of logging, as status messages are * optional and go exclusively to the console. */ if (vasprintf(&s, format, ap) < 0) @@ -3347,15 +3538,19 @@ void status_vprintf(const char *status, bool ellipse, const char *format, va_lis goto finish; if (ellipse) { + char *e; + size_t emax, sl; + int c; + 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; + sl = status ? strlen(status_indent) : 0; + + emax = c - sl - 1; + if (emax < 3) + emax = 3; e = ellipsize(s, emax, 75); if (e) { @@ -3365,34 +3560,23 @@ void status_vprintf(const char *status, bool ellipse, const char *format, va_lis } 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) { + if (!isempty(status)) { + IOVEC_SET_STRING(iovec[n++], "["); + IOVEC_SET_STRING(iovec[n++], status); + IOVEC_SET_STRING(iovec[n++], "] "); + } else + IOVEC_SET_STRING(iovec[n++], status_indent); } - 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"); + IOVEC_SET_STRING(iovec[n++], s); + IOVEC_SET_STRING(iovec[n++], "\n"); writev(fd, iovec, n); finish: free(s); - free(spaces); if (fd >= 0) close_nointr_nofail(fd); @@ -3585,18 +3769,27 @@ int fd_columns(int fd) { return ws.ws_col; } -unsigned columns(void) { - static __thread int parsed_columns = 0; +static unsigned columns_cached(bool cached) { + static __thread int parsed_columns = 0, env_columns = -1; const char *e; - if (_likely_(parsed_columns > 0)) + if (_likely_(parsed_columns > 0 && cached)) return parsed_columns; - e = getenv("COLUMNS"); - if (e) - parsed_columns = atoi(e); + if (_unlikely_(env_columns == -1)) { + e = getenv("COLUMNS"); + if (e) + env_columns = atoi(e); + else + env_columns = 0; + } - if (parsed_columns <= 0) + if (env_columns > 0) { + parsed_columns = env_columns; + return parsed_columns; + } + + if (parsed_columns <= 0 || !cached) parsed_columns = fd_columns(STDOUT_FILENO); if (parsed_columns <= 0) @@ -3605,6 +3798,14 @@ unsigned columns(void) { return parsed_columns; } +unsigned columns(void) { + return columns_cached(true); +} + +unsigned columns_uncached(void) { + return columns_cached(false); +} + int fd_lines(int fd) { struct winsize ws; zero(ws); @@ -3785,7 +3986,8 @@ int wait_for_terminate_and_warn(const char *name, pid_t pid) { assert(name); assert(pid > 1); - if ((r = wait_for_terminate(pid, &status)) < 0) { + r = wait_for_terminate(pid, &status); + if (r < 0) { log_warning("Failed to wait for %s: %s", name, strerror(-r)); return r; } @@ -3808,7 +4010,6 @@ int wait_for_terminate_and_warn(const char *name, pid_t pid) { log_warning("%s failed due to unknown reason.", name); return -EPROTO; - } _noreturn_ void freeze(void) { @@ -3900,52 +4101,44 @@ void dual_timestamp_deserialize(const char *value, dual_timestamp *t) { } } -char *fstab_node_to_udev_node(const char *p) { +static char *tag_to_udev_node(const char *tagvalue, const char *by) { 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; + u = unquote(tagvalue, "\"\'"); + if (u == NULL) + return NULL; - r = asprintf(&dn, "/dev/disk/by-label/%s", t); - free(t); + t = xescape(u, "/ "); + free(u); - if (r < 0) - return NULL; - - return dn; - } + if (t == NULL) + return NULL; - if (startswith(p, "UUID=")) { + r = asprintf(&dn, "/dev/disk/by-%s/%s", by, t); + free(t); - if (!(u = unquote(p+5, "\"\'"))) - return NULL; + if (r < 0) + return NULL; - t = xescape(u, "/ "); - free(u); + return dn; +} - if (!t) - return NULL; +char *fstab_node_to_udev_node(const char *p) { + if (startswith(p, "LABEL=")) + return tag_to_udev_node(p+6, "label"); - r = asprintf(&dn, "/dev/disk/by-uuid/%s", t); - free(t); + if (startswith(p, "UUID=")) + return tag_to_udev_node(p+5, "uuid"); - if (r < 0) - return NULL; + if (startswith(p, "PARTUUID=")) + return tag_to_udev_node(p+9, "partuuid"); - return dn; - } + if (startswith(p, "PARTLABEL=")) + return tag_to_udev_node(p+10, "partlabel"); return strdup(p); } @@ -4044,7 +4237,12 @@ bool dirent_is_file(const struct dirent *de) { bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) { assert(de); - if (!dirent_is_file(de)) + if (de->d_type != DT_REG && + de->d_type != DT_LNK && + de->d_type != DT_UNKNOWN) + return false; + + if (ignore_file_allow_backup(de->d_name)) return false; return endswith(de->d_name, suffix); @@ -4087,7 +4285,7 @@ void execute_directory(const char *directory, DIR *d, char *argv[]) { continue; if (asprintf(&path, "%s/%s", directory, de->d_name) < 0) { - log_error("Out of memory"); + log_oom(); continue; } @@ -4138,7 +4336,7 @@ void execute_directory(const char *directory, DIR *d, char *argv[]) { } if ((path = hashmap_remove(pids, UINT_TO_PTR(si.si_pid)))) { - if (!is_clean_exit(si.si_code, si.si_status)) { + if (!is_clean_exit(si.si_code, si.si_status, NULL)) { if (si.si_code == CLD_EXITED) log_error("%s exited with exit status %i.", path, si.si_status); else @@ -4186,134 +4384,6 @@ 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; -} - -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; -} - char* strshorten(char *s, size_t l) { assert(s); @@ -4714,7 +4784,12 @@ int socket_from_display(const char *display, char **path) { return 0; } -int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **home) { +int get_user_creds( + const char **username, + uid_t *uid, gid_t *gid, + const char **home, + const char **shell) { + struct passwd *p; uid_t u; @@ -4735,6 +4810,10 @@ int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **h if (home) *home = "/root"; + + if (shell) + *shell = "/bin/sh"; + return 0; } @@ -4766,6 +4845,9 @@ int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **h if (home) *home = p->pw_dir; + if (shell) + *shell = p->pw_shell; + return 0; } @@ -4982,7 +5064,7 @@ finish: return r; } -char *join(const char *x, ...) { +char *strjoin(const char *x, ...) { va_list ap; size_t l; char *r, *p; @@ -5590,3 +5672,218 @@ int getenv_for_pid(pid_t pid, const char *field, char **_value) { return r; } + +int can_sleep(const char *type) { + char *p, *w, *state; + size_t l, k; + bool found = false; + int r; + + assert(type); + + r = read_one_line_file("/sys/power/state", &p); + if (r < 0) + return r == -ENOENT ? 0 : r; + + k = strlen(type); + + FOREACH_WORD_SEPARATOR(w, l, p, WHITESPACE, state) { + if (l == k && strncmp(w, type, l) == 0) { + found = true; + break; + } + } + + free(p); + return found; +} + +bool is_valid_documentation_url(const char *url) { + assert(url); + + if (startswith(url, "http://") && url[7]) + return true; + + if (startswith(url, "https://") && url[8]) + return true; + + if (startswith(url, "file:") && url[5]) + return true; + + if (startswith(url, "info:") && url[5]) + return true; + + if (startswith(url, "man:") && url[4]) + return true; + + return false; +} + +bool in_initrd(void) { + static int saved = -1; + struct statfs s; + + if (saved >= 0) + return saved; + + /* We make two checks here: + * + * 1. the flag file /etc/initrd-release must exist + * 2. the root file system must be a memory file system + * + * The second check is extra paranoia, since misdetecting an + * initrd can have bad bad consequences due the initrd + * emptying when transititioning to the main systemd. + */ + + saved = access("/etc/initrd-release", F_OK) >= 0 && + statfs("/", &s) >= 0 && + (s.f_type == TMPFS_MAGIC || s.f_type == RAMFS_MAGIC); + + return saved; +} + +void warn_melody(void) { + int fd; + + fd = open("/dev/console", O_WRONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return; + + /* Yeah, this is synchronous. Kinda sucks. Bute well... */ + + ioctl(fd, KIOCSOUND, (int)(1193180/440)); + usleep(125*USEC_PER_MSEC); + + ioctl(fd, KIOCSOUND, (int)(1193180/220)); + usleep(125*USEC_PER_MSEC); + + ioctl(fd, KIOCSOUND, (int)(1193180/220)); + usleep(125*USEC_PER_MSEC); + + ioctl(fd, KIOCSOUND, 0); + close_nointr_nofail(fd); +} + +int make_console_stdio(void) { + int fd, r; + + /* Make /dev/console the controlling terminal and stdin/stdout/stderr */ + + fd = acquire_terminal("/dev/console", false, true, true, (usec_t) -1); + if (fd < 0) { + log_error("Failed to acquire terminal: %s", strerror(-fd)); + return fd; + } + + r = make_stdio(fd); + if (r < 0) { + log_error("Failed to duplicate terminal fd: %s", strerror(-r)); + return r; + } + + return 0; +} + +int get_home_dir(char **_h) { + char *h; + const char *e; + uid_t u; + struct passwd *p; + + assert(_h); + + /* Take the user specified one */ + e = getenv("HOME"); + if (e) { + h = strdup(e); + if (!h) + return -ENOMEM; + + *_h = h; + return 0; + } + + /* Hardcode home directory for root to avoid NSS */ + u = getuid(); + if (u == 0) { + h = strdup("/root"); + if (!h) + return -ENOMEM; + + *_h = h; + return 0; + } + + /* Check the database... */ + errno = 0; + p = getpwuid(u); + if (!p) + return errno ? -errno : -ENOENT; + + if (!path_is_absolute(p->pw_dir)) + return -EINVAL; + + h = strdup(p->pw_dir); + if (!h) + return -ENOMEM; + + *_h = h; + return 0; +} + +int get_shell(char **_sh) { + char *sh; + const char *e; + uid_t u; + struct passwd *p; + + assert(_sh); + + /* Take the user specified one */ + e = getenv("SHELL"); + if (e) { + sh = strdup(e); + if (!sh) + return -ENOMEM; + + *_sh = sh; + return 0; + } + + /* Hardcode home directory for root to avoid NSS */ + u = getuid(); + if (u == 0) { + sh = strdup("/bin/sh"); + if (!sh) + return -ENOMEM; + + *_sh = sh; + return 0; + } + + /* Check the database... */ + errno = 0; + p = getpwuid(u); + if (!p) + return errno ? -errno : -ESRCH; + + if (!path_is_absolute(p->pw_shell)) + return -EINVAL; + + sh = strdup(p->pw_shell); + if (!sh) + return -ENOMEM; + + *_sh = sh; + return 0; +} + +void freep(void *p) { + free(*(void**) p); +} + +void fclosep(FILE **f) { + if (*f) + fclose(*f); +}