X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fshared%2Futil.c;h=409ccc7eed7df5fa61cd7b2d51b40d756665cd52;hp=3eb8a0f44210ff3935f5176eb79c915480ccf106;hb=a7e07206021c6484a8d33c6ee156ecfb5b90534c;hpb=b5e1fad5b1b9d29694e8c51245821a732fc22f93 diff --git a/src/shared/util.c b/src/shared/util.c index 3eb8a0f44..409ccc7ee 100644 --- a/src/shared/util.c +++ b/src/shared/util.c @@ -39,7 +39,6 @@ #include #include #include -#include #include #include #include @@ -60,7 +59,9 @@ #include #include #include +#include #include +#include #undef basename #ifdef HAVE_SYS_AUXV_H @@ -85,6 +86,7 @@ #include "gunicode.h" #include "virt.h" #include "def.h" +#include "sparse-endian.h" int saved_argc = 0; char **saved_argv = NULL; @@ -174,6 +176,69 @@ char* first_word(const char *s, const char *word) { return (char*) p; } +static size_t cescape_char(char c, char *buf) { + char * buf_old = buf; + + switch (c) { + + case '\a': + *(buf++) = '\\'; + *(buf++) = 'a'; + break; + case '\b': + *(buf++) = '\\'; + *(buf++) = 'b'; + break; + case '\f': + *(buf++) = '\\'; + *(buf++) = 'f'; + break; + case '\n': + *(buf++) = '\\'; + *(buf++) = 'n'; + break; + case '\r': + *(buf++) = '\\'; + *(buf++) = 'r'; + break; + case '\t': + *(buf++) = '\\'; + *(buf++) = 't'; + break; + case '\v': + *(buf++) = '\\'; + *(buf++) = 'v'; + break; + case '\\': + *(buf++) = '\\'; + *(buf++) = '\\'; + break; + case '"': + *(buf++) = '\\'; + *(buf++) = '"'; + break; + case '\'': + *(buf++) = '\\'; + *(buf++) = '\''; + break; + + default: + /* For special chars we prefer octal over + * hexadecimal encoding, simply because glib's + * g_strescape() does the same */ + if ((c < ' ') || (c >= 127)) { + *(buf++) = '\\'; + *(buf++) = octchar((unsigned char) c >> 6); + *(buf++) = octchar((unsigned char) c >> 3); + *(buf++) = octchar((unsigned char) c); + } else + *(buf++) = c; + break; + } + + return buf - buf_old; +} + int close_nointr(int fd) { assert(fd >= 0); @@ -291,7 +356,7 @@ int parse_uid(const char *s, uid_t* ret_uid) { if ((unsigned long) uid != ul) return -ERANGE; - /* Some libc APIs use (uid_t) -1 as special placeholder */ + /* Some libc APIs use UID_INVALID as special placeholder */ if (uid == (uid_t) 0xFFFFFFFF) return -ENXIO; @@ -363,6 +428,46 @@ int safe_atou8(const char *s, uint8_t *ret) { return 0; } +int safe_atou16(const char *s, uint16_t *ret) { + char *x = NULL; + unsigned long l; + + assert(s); + assert(ret); + + errno = 0; + l = strtoul(s, &x, 0); + + if (!x || x == s || *x || errno) + return errno > 0 ? -errno : -EINVAL; + + if ((unsigned long) (uint16_t) l != l) + return -ERANGE; + + *ret = (uint16_t) l; + return 0; +} + +int safe_atoi16(const char *s, int16_t *ret) { + char *x = NULL; + long l; + + assert(s); + assert(ret); + + errno = 0; + l = strtol(s, &x, 0); + + if (!x || x == s || *x || errno) + return errno > 0 ? -errno : -EINVAL; + + if ((long) (int16_t) l != l) + return -ERANGE; + + *ret = (int16_t) l; + return 0; +} + int safe_atollu(const char *s, long long unsigned *ret_llu) { char *x = NULL; unsigned long long l; @@ -418,7 +523,7 @@ int safe_atod(const char *s, double *ret_d) { static size_t strcspn_escaped(const char *s, const char *reject) { bool escaped = false; - size_t n; + int n; for (n=0; s[n]; n++) { if (escaped) @@ -428,6 +533,7 @@ static size_t strcspn_escaped(const char *s, const char *reject) { else if (strchr(reject, s[n])) break; } + /* if s ends in \, return index of previous char */ return n - escaped; } @@ -455,7 +561,7 @@ const char* split(const char **state, size_t *l, const char *separator, bool quo *l = strcspn_escaped(current + 1, quotechars); if (current[*l + 1] == '\0' || (current[*l + 2] && !strchr(separator, current[*l + 2]))) { - /* right quote missing or garbage at the end*/ + /* right quote missing or garbage at the end */ *state = current; return NULL; } @@ -463,6 +569,11 @@ const char* split(const char **state, size_t *l, const char *separator, bool quo *state = current++ + *l + 2; } else if (quoted) { *l = strcspn_escaped(current, separator); + if (current[*l] && !strchr(separator, current[*l])) { + /* unfinished escape */ + *state = current; + return NULL; + } *state = current + *l; } else { *l = strcspn(current, separator); @@ -515,56 +626,6 @@ 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; - _cleanup_free_ char *line = NULL; - const char *p; - - assert(pid >= 0); - assert(st); - - p = procfs_file_alloca(pid, "stat"); - r = read_one_line_file(p, &line); - if (r < 0) - return r; - - /* 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 */ - - p = strrchr(line, ')'); - if (!p) - 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 fchmod_umask(int fd, mode_t m) { mode_t u; int r; @@ -757,19 +818,30 @@ int get_process_capeff(pid_t pid, char **capeff) { return get_status_field(p, "\nCapEff:", capeff); } +static int get_process_link_contents(const char *proc_file, char **name) { + int r; + + assert(proc_file); + assert(name); + + r = readlink_malloc(proc_file, name); + if (r < 0) + return r == -ENOENT ? -ESRCH : r; + + return 0; +} + int get_process_exe(pid_t pid, char **name) { const char *p; char *d; int r; assert(pid >= 0); - assert(name); p = procfs_file_alloca(pid, "exe"); - - r = readlink_malloc(p, name); + r = get_process_link_contents(p, name); if (r < 0) - return r == -ENOENT ? -ESRCH : r; + return r; d = endswith(*name, " (deleted)"); if (d) @@ -821,6 +893,59 @@ int get_process_gid(pid_t pid, gid_t *gid) { return get_process_id(pid, "Gid:", gid); } +int get_process_cwd(pid_t pid, char **cwd) { + const char *p; + + assert(pid >= 0); + + p = procfs_file_alloca(pid, "cwd"); + + return get_process_link_contents(p, cwd); +} + +int get_process_root(pid_t pid, char **root) { + const char *p; + + assert(pid >= 0); + + p = procfs_file_alloca(pid, "root"); + + return get_process_link_contents(p, root); +} + +int get_process_environ(pid_t pid, char **env) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *outcome = NULL; + int c; + const char *p; + size_t allocated = 0, sz = 0; + + assert(pid >= 0); + assert(env); + + p = procfs_file_alloca(pid, "environ"); + + f = fopen(p, "re"); + if (!f) + return -errno; + + while ((c = fgetc(f)) != EOF) { + if (!GREEDY_REALLOC(outcome, allocated, sz + 5)) + return -ENOMEM; + + if (c == '\0') + outcome[sz++] = '\n'; + else + sz += cescape_char(c, outcome + sz); + } + + outcome[sz] = '\0'; + *env = outcome; + outcome = NULL; + + return 0; +} + char *strnappend(const char *s, const char *suffix, size_t b) { size_t a; char *r; @@ -893,6 +1018,28 @@ int readlink_malloc(const char *p, char **ret) { return readlinkat_malloc(AT_FDCWD, p, ret); } +int readlink_value(const char *p, char **ret) { + _cleanup_free_ char *link = NULL; + char *value; + int r; + + r = readlink_malloc(p, &link); + if (r < 0) + return r; + + value = basename(link); + if (!value) + return -ENOENT; + + value = strdup(value); + if (!value) + return -ENOMEM; + + *ret = value; + + return 0; +} + int readlink_and_make_absolute(const char *p, char **r) { _cleanup_free_ char *target = NULL; char *k; @@ -1006,7 +1153,7 @@ char *delete_chars(char *s, const char *bad) { } char *file_in_same_dir(const char *path, const char *filename) { - char *e, *r; + char *e, *ret; size_t k; assert(path); @@ -1019,17 +1166,17 @@ char *file_in_same_dir(const char *path, const char *filename) { if (path_is_absolute(filename)) return strdup(filename); - if (!(e = strrchr(path, '/'))) + e = strrchr(path, '/'); + if (!e) return strdup(filename); k = strlen(filename); - if (!(r = new(char, e-path+1+k+1))) + ret = new(char, (e + 1 - path) + k + 1); + if (!ret) return NULL; - memcpy(r, path, e-path+1); - memcpy(r+(e-path)+1, filename, k+1); - - return r; + memcpy(mempcpy(ret, path, e + 1 - path), filename, k + 1); + return ret; } int rmdir_parents(const char *path, const char *stop) { @@ -1178,63 +1325,7 @@ char *cescape(const char *s) { return NULL; for (f = s, t = r; *f; f++) - - switch (*f) { - - case '\a': - *(t++) = '\\'; - *(t++) = 'a'; - break; - case '\b': - *(t++) = '\\'; - *(t++) = 'b'; - break; - case '\f': - *(t++) = '\\'; - *(t++) = 'f'; - break; - case '\n': - *(t++) = '\\'; - *(t++) = 'n'; - break; - case '\r': - *(t++) = '\\'; - *(t++) = 'r'; - break; - case '\t': - *(t++) = '\\'; - *(t++) = 't'; - break; - case '\v': - *(t++) = '\\'; - *(t++) = 'v'; - break; - case '\\': - *(t++) = '\\'; - *(t++) = '\\'; - break; - case '"': - *(t++) = '\\'; - *(t++) = '"'; - break; - case '\'': - *(t++) = '\\'; - *(t++) = '\''; - break; - - default: - /* For special chars we prefer octal over - * hexadecimal encoding, simply because glib's - * g_strescape() does the same */ - if ((*f < ' ') || (*f >= 127)) { - *(t++) = '\\'; - *(t++) = octchar((unsigned char) *f >> 6); - *(t++) = octchar((unsigned char) *f >> 3); - *(t++) = octchar((unsigned char) *f); - } else - *(t++) = *f; - break; - } + t += cescape_char(*f, t); *t = 0; @@ -1353,7 +1444,7 @@ char *cunescape_length_with_prefix(const char *s, size_t length, const char *pre } case 0: - /* premature end of string.*/ + /* premature end of string. */ *(t++) = '\\'; goto finish; @@ -1421,7 +1512,7 @@ char *ascii_strlower(char *t) { return t; } -_pure_ static bool ignore_file_allow_backup(const char *filename) { +_pure_ static bool hidden_file_allow_backup(const char *filename) { assert(filename); return @@ -1438,13 +1529,13 @@ _pure_ static bool ignore_file_allow_backup(const char *filename) { endswith(filename, ".swp"); } -bool ignore_file(const char *filename) { +bool hidden_file(const char *filename) { assert(filename); if (endswith(filename, "~")) return true; - return ignore_file_allow_backup(filename); + return hidden_file_allow_backup(filename); } int fd_nonblock(int fd, bool nonblock) { @@ -1538,7 +1629,7 @@ int close_all_fds(const int except[], unsigned n_except) { while ((de = readdir(d))) { int fd = -1; - if (ignore_file(de->d_name)) + if (hidden_file(de->d_name)) continue; if (safe_atoi(de->d_name, &fd) < 0) @@ -2017,9 +2108,9 @@ int acquire_terminal( assert(notify >= 0); for (;;) { - uint8_t inotify_buffer[sizeof(struct inotify_event) + FILENAME_MAX]; - ssize_t l; + union inotify_event_buffer buffer; struct inotify_event *e; + ssize_t l; if (timeout != USEC_INFINITY) { usec_t n; @@ -2040,9 +2131,8 @@ int acquire_terminal( } } - l = read(notify, inotify_buffer, sizeof(inotify_buffer)); + l = read(notify, &buffer, sizeof(buffer)); if (l < 0) { - if (errno == EINTR || errno == EAGAIN) continue; @@ -2050,21 +2140,11 @@ int acquire_terminal( goto fail; } - e = (struct inotify_event*) inotify_buffer; - - while (l > 0) { - size_t step; - + FOREACH_INOTIFY_EVENT(e, buffer, l) { if (e->wd != wd || !(e->mask & IN_CLOSE)) { r = -EIO; goto fail; } - - step = sizeof(struct inotify_event) + e->len; - assert(step <= (size_t) l); - - e = (struct inotify_event*) ((uint8_t*) e + step); - l -= step; } break; @@ -2081,7 +2161,7 @@ int acquire_terminal( r = reset_terminal_fd(fd, true); if (r < 0) - log_warning("Failed to reset terminal: %s", strerror(-r)); + log_warning_errno(r, "Failed to reset terminal: %m"); return fd; @@ -2196,21 +2276,25 @@ ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) { ssize_t k; k = read(fd, p, nbytes); - if (k < 0 && errno == EINTR) - continue; + if (k < 0) { + if (errno == EINTR) + continue; - if (k < 0 && errno == EAGAIN && do_poll) { + if (errno == EAGAIN && do_poll) { - /* We knowingly ignore any return value here, - * and expect that any error/EOF is reported - * via read() */ + /* We knowingly ignore any return value here, + * and expect that any error/EOF is reported + * via read() */ - fd_wait_for_event(fd, POLLIN, USEC_INFINITY); - continue; + fd_wait_for_event(fd, POLLIN, USEC_INFINITY); + continue; + } + + return n > 0 ? n : -errno; } - if (k <= 0) - return n > 0 ? n : (k < 0 ? -errno : 0); + if (k == 0) + return n; p += k; nbytes -= k; @@ -2220,39 +2304,42 @@ ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) { return n; } -ssize_t loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) { +int loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) { const uint8_t *p = buf; - ssize_t n = 0; assert(fd >= 0); assert(buf); + errno = 0; + while (nbytes > 0) { ssize_t k; k = write(fd, p, nbytes); - if (k < 0 && errno == EINTR) - continue; + if (k < 0) { + if (errno == EINTR) + continue; - if (k < 0 && errno == EAGAIN && do_poll) { + if (errno == EAGAIN && do_poll) { + /* We knowingly ignore any return value here, + * and expect that any error/EOF is reported + * via write() */ - /* We knowingly ignore any return value here, - * and expect that any error/EOF is reported - * via write() */ + fd_wait_for_event(fd, POLLOUT, USEC_INFINITY); + continue; + } - fd_wait_for_event(fd, POLLOUT, USEC_INFINITY); - continue; + return -errno; } - if (k <= 0) - return n > 0 ? n : (k < 0 ? -errno : 0); + if (k == 0) /* Can't really happen */ + return -EIO; p += k; nbytes -= k; - n += k; } - return n; + return 0; } int parse_size(const char *t, off_t base, off_t *size) { @@ -2389,9 +2476,9 @@ int make_stdio(int fd) { assert(fd >= 0); - r = dup3(fd, STDIN_FILENO, 0); - s = dup3(fd, STDOUT_FILENO, 0); - t = dup3(fd, STDERR_FILENO, 0); + r = dup2(fd, STDIN_FILENO); + s = dup2(fd, STDOUT_FILENO); + t = dup2(fd, STDERR_FILENO); if (fd >= 3) safe_close(fd); @@ -2399,7 +2486,11 @@ int make_stdio(int fd) { if (r < 0 || s < 0 || t < 0) return -errno; - /* We rely here that the new fd has O_CLOEXEC not set */ + /* Explicitly unset O_CLOEXEC, since if fd was < 3, then + * dup2() was a NOP and the bit hence possibly set. */ + fd_cloexec(STDIN_FILENO, false); + fd_cloexec(STDOUT_FILENO, false); + fd_cloexec(STDERR_FILENO, false); return 0; } @@ -2442,7 +2533,7 @@ int dir_is_empty(const char *path) { if (!de) return 1; - if (!ignore_file(de->d_name)) + if (!hidden_file(de->d_name)) return 0; } } @@ -2696,24 +2787,37 @@ char *getusername_malloc(void) { return lookup_uid(getuid()); } -int getttyname_malloc(int fd, char **r) { - char path[PATH_MAX], *c; - int k; +int getttyname_malloc(int fd, char **ret) { + size_t l = 100; + int r; - assert(r); + assert(fd >= 0); + assert(ret); - k = ttyname_r(fd, path, sizeof(path)); - if (k > 0) - return -k; + for (;;) { + char path[l]; - char_array_0(path); + r = ttyname_r(fd, path, sizeof(path)); + if (r == 0) { + const char *p; + char *c; - c = strdup(startswith(path, "/dev/") ? path + 5 : path); - if (!c) - return -ENOMEM; + p = startswith(path, "/dev/"); + c = strdup(p ?: path); + if (!c) + return -ENOMEM; - *r = c; - return 0; + *ret = c; + return 0; + } + + if (r != ERANGE) + return -r; + + l *= 2; + } + + return 0; } int getttyname_harder(int fd, char **r) { @@ -2783,7 +2887,7 @@ int get_ctty(pid_t pid, dev_t *_devnr, char **r) { if (k < 0) return k; - snprintf(fn, sizeof(fn), "/dev/char/%u:%u", major(devnr), minor(devnr)); + sprintf(fn, "/dev/char/%u:%u", major(devnr), minor(devnr)); k = readlink_malloc(fn, &s); if (k < 0) { @@ -2919,6 +3023,15 @@ _pure_ static int is_temporary_fs(struct statfs *s) { F_TYPE_EQUAL(s->f_type, RAMFS_MAGIC); } +int is_fd_on_temporary_fs(int fd) { + struct statfs s; + + if (fstatfs(fd, &s) < 0) + return -errno; + + return is_temporary_fs(&s); +} + int rm_rf_children(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev) { struct statfs s; @@ -2971,7 +3084,7 @@ static int rm_rf_internal(const char *path, bool only_dirs, bool delete_root, bo fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); if (fd < 0) { - if (errno != ENOTDIR) + if (errno != ENOTDIR && errno != ELOOP) return -errno; if (!dangerous) { @@ -3034,11 +3147,11 @@ 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 (mode != (mode_t) -1) + if (mode != MODE_INVALID) if (chmod(path, mode) < 0) return -errno; - if (uid != (uid_t) -1 || gid != (gid_t) -1) + if (uid != UID_INVALID || gid != GID_INVALID) if (chown(path, uid, gid) < 0) return -errno; @@ -3052,11 +3165,11 @@ int fchmod_and_fchown(int fd, 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 (mode != (mode_t) -1) + if (mode != MODE_INVALID) if (fchmod(fd, mode) < 0) return -errno; - if (uid != (uid_t) -1 || gid != (gid_t) -1) + if (uid != UID_INVALID || gid != GID_INVALID) if (fchown(fd, uid, gid) < 0) return -errno; @@ -3270,7 +3383,7 @@ char **replace_env_argv(char **argv, char **env) { if (e) { int r; - r = strv_split_quoted(&m, e); + r = strv_split_quoted(&m, e, true); if (r < 0) { ret[k] = NULL; strv_free(ret); @@ -3344,7 +3457,7 @@ unsigned columns(void) { c = 80; cached_columns = c; - return c; + return cached_columns; } int fd_lines(int fd) { @@ -3361,7 +3474,7 @@ int fd_lines(int fd) { unsigned lines(void) { const char *e; - unsigned l; + int l; if (_likely_(cached_lines > 0)) return cached_lines; @@ -3369,7 +3482,7 @@ unsigned lines(void) { l = 0; e = getenv("LINES"); if (e) - (void) safe_atou(e, &l); + (void) safe_atoi(e, &l); if (l <= 0) l = fd_lines(STDOUT_FILENO); @@ -3547,7 +3660,7 @@ int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gi return -errno; } - if (uid != (uid_t) -1 || gid != (gid_t) -1) { + if (uid != UID_INVALID || gid != GID_INVALID) { r = fchown(fd, uid, gid); if (r < 0) return -errno; @@ -3568,7 +3681,7 @@ int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gi } int touch(const char *path) { - return touch_file(path, false, USEC_INFINITY, (uid_t) -1, (gid_t) -1, 0); + return touch_file(path, false, USEC_INFINITY, UID_INVALID, GID_INVALID, 0); } char *unquote(const char *s, const char* quotes) { @@ -3578,7 +3691,7 @@ char *unquote(const char *s, const char* quotes) { /* This is rather stupid, simply removes the heading and * trailing quotes if there is one. Doesn't care about * escaping or anything. We should make this smarter one - * day...*/ + * day... */ l = strlen(s); if (l < 2) @@ -3591,41 +3704,33 @@ char *unquote(const char *s, const char* quotes) { } char *normalize_env_assignment(const char *s) { - _cleanup_free_ char *name = NULL, *value = NULL, *p = NULL; - char *eq, *r; + _cleanup_free_ char *value = NULL; + const char *eq; + char *p, *name; eq = strchr(s, '='); if (!eq) { - char *t; + char *r, *t; r = strdup(s); if (!r) return NULL; t = strstrip(r); - if (t == r) - return r; + if (t != r) + memmove(r, t, strlen(t) + 1); - memmove(r, t, strlen(t) + 1); return r; } - name = strndup(s, eq - s); - if (!name) - return NULL; - - p = strdup(eq + 1); - if (!p) - return NULL; + name = strndupa(s, eq - s); + p = strdupa(eq + 1); value = unquote(strstrip(p), QUOTES); if (!value) return NULL; - if (asprintf(&r, "%s=%s", strstrip(name), value) < 0) - r = NULL; - - return r; + return strjoin(strstrip(name), "=", value, NULL); } int wait_for_terminate(pid_t pid, siginfo_t *status) { @@ -3661,8 +3766,11 @@ int wait_for_terminate(pid_t pid, siginfo_t *status) { * * That is, success is indicated by a return value of zero, and an * error is indicated by a non-zero value. + * + * A warning is emitted if the process terminates abnormally, + * and also if it returns non-zero unless check_exit_code is true. */ -int wait_for_terminate_and_warn(const char *name, pid_t pid) { +int wait_for_terminate_and_warn(const char *name, pid_t pid, bool check_exit_code) { int r; siginfo_t status; @@ -3670,20 +3778,17 @@ int wait_for_terminate_and_warn(const char *name, pid_t pid) { assert(pid > 1); r = wait_for_terminate(pid, &status); - if (r < 0) { - log_warning("Failed to wait for %s: %s", name, strerror(-r)); - return r; - } + if (r < 0) + return log_warning_errno(r, "Failed to wait for %s: %m", name); if (status.si_code == CLD_EXITED) { - if (status.si_status != 0) { - log_warning("%s failed with error code %i.", name, status.si_status); - return status.si_status; - } - - log_debug("%s succeeded.", name); - return 0; + if (status.si_status != 0) + log_full(check_exit_code ? LOG_WARNING : LOG_DEBUG, + "%s failed with error code %i.", name, status.si_status); + else + log_debug("%s succeeded.", name); + return status.si_status; } else if (status.si_code == CLD_KILLED || status.si_code == CLD_DUMPED) { @@ -3906,7 +4011,7 @@ const char *default_term_for_tty(const char *tty) { bool dirent_is_file(const struct dirent *de) { assert(de); - if (ignore_file(de->d_name)) + if (hidden_file(de->d_name)) return false; if (de->d_type != DT_REG && @@ -3925,7 +4030,7 @@ bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) { de->d_type != DT_UNKNOWN) return false; - if (ignore_file_allow_backup(de->d_name)) + if (hidden_file_allow_backup(de->d_name)) return false; return endswith(de->d_name, suffix); @@ -3942,7 +4047,7 @@ void execute_directory(const char *directory, DIR *d, usec_t timeout, char *argv executor_pid = fork(); if (executor_pid < 0) { - log_error("Failed to fork: %m"); + log_error_errno(errno, "Failed to fork: %m"); return; } else if (executor_pid == 0) { @@ -3965,7 +4070,7 @@ void execute_directory(const char *directory, DIR *d, usec_t timeout, char *argv if (errno == ENOENT) _exit(EXIT_SUCCESS); - log_error("Failed to enumerate directory %s: %m", directory); + log_error_errno(errno, "Failed to enumerate directory %s: %m", directory); _exit(EXIT_FAILURE); } } @@ -3991,7 +4096,7 @@ void execute_directory(const char *directory, DIR *d, usec_t timeout, char *argv pid = fork(); if (pid < 0) { - log_error("Failed to fork: %m"); + log_error_errno(errno, "Failed to fork: %m"); continue; } else if (pid == 0) { char *_argv[2]; @@ -4006,7 +4111,7 @@ void execute_directory(const char *directory, DIR *d, usec_t timeout, char *argv argv[0] = path; execv(path, argv); - log_error("Failed to execute %s: %m", path); + log_error_errno(errno, "Failed to execute %s: %m", path); _exit(EXIT_FAILURE); } @@ -4038,13 +4143,13 @@ void execute_directory(const char *directory, DIR *d, usec_t timeout, char *argv path = hashmap_remove(pids, UINT_TO_PTR(pid)); assert(path); - wait_for_terminate_and_warn(path, pid); + wait_for_terminate_and_warn(path, pid, true); } _exit(EXIT_SUCCESS); } - wait_for_terminate_and_warn(directory, executor_pid); + wait_for_terminate_and_warn(directory, executor_pid, true); } int kill_and_sigcont(pid_t pid, int sig) { @@ -4167,6 +4272,23 @@ bool machine_name_is_valid(const char *s) { return true; } +bool image_name_is_valid(const char *s) { + if (!filename_is_valid(s)) + return false; + + if (string_has_cc(s, NULL)) + return false; + + if (!utf8_is_valid(s)) + return false; + + /* Temporary files for atomically creating new files */ + if (startswith(s, ".#")) + return false; + + return true; +} + int pipe_eof(int fd) { struct pollfd pollfd = { .fd = fd, @@ -4208,15 +4330,15 @@ int fd_wait_for_event(int fd, int event, usec_t t) { int fopen_temporary(const char *path, FILE **_f, char **_temp_path) { FILE *f; char *t; - int fd; + int r, fd; assert(path); assert(_f); assert(_temp_path); - t = tempfn_xxxxxx(path); - if (!t) - return -ENOMEM; + r = tempfn_xxxxxx(path, &t); + if (r < 0) + return r; fd = mkostemp_safe(t, O_WRONLY|O_CLOEXEC); if (fd < 0) { @@ -4327,13 +4449,14 @@ int vt_disallocate(const char *name) { int symlink_atomic(const char *from, const char *to) { _cleanup_free_ char *t = NULL; + int r; assert(from); assert(to); - t = tempfn_random(to); - if (!t) - return -ENOMEM; + r = tempfn_random(to, &t); + if (r < 0) + return r; if (symlink(from, t) < 0) return -errno; @@ -4348,12 +4471,13 @@ int symlink_atomic(const char *from, const char *to) { int mknod_atomic(const char *path, mode_t mode, dev_t dev) { _cleanup_free_ char *t = NULL; + int r; assert(path); - t = tempfn_random(path); - if (!t) - return -ENOMEM; + r = tempfn_random(path, &t); + if (r < 0) + return r; if (mknod(t, mode, dev) < 0) return -errno; @@ -4368,12 +4492,13 @@ int mknod_atomic(const char *path, mode_t mode, dev_t dev) { int mkfifo_atomic(const char *path, mode_t mode) { _cleanup_free_ char *t = NULL; + int r; assert(path); - t = tempfn_random(path); - if (!t) - return -ENOMEM; + r = tempfn_random(path, &t); + if (r < 0) + return r; if (mkfifo(t, mode) < 0) return -errno; @@ -5060,6 +5185,9 @@ char *format_bytes(char *buf, size_t l, off_t t) { { "K", 1024ULL }, }; + if (t == (off_t) -1) + return NULL; + for (i = 0; i < ELEMENTSOF(table); i++) { if (t >= table[i].factor) { @@ -5198,7 +5326,7 @@ int fork_agent(pid_t *pid, const int except[], unsigned n_except, const char *pa * keep an unused copy of stdin around. */ fd = open("/dev/tty", O_WRONLY); if (fd < 0) { - log_error("Failed to open /dev/tty: %m"); + log_error_errno(errno, "Failed to open /dev/tty: %m"); _exit(EXIT_FAILURE); } @@ -5381,16 +5509,12 @@ int make_console_stdio(void) { /* Make /dev/console the controlling terminal and stdin/stdout/stderr */ fd = acquire_terminal("/dev/console", false, true, true, USEC_INFINITY); - if (fd < 0) { - log_error("Failed to acquire terminal: %s", strerror(-fd)); - return fd; - } + if (fd < 0) + return log_error_errno(fd, "Failed to acquire terminal: %m"); r = make_stdio(fd); - if (r < 0) { - log_error("Failed to duplicate terminal fd: %s", strerror(-r)); - return r; - } + if (r < 0) + return log_error_errno(r, "Failed to duplicate terminal fd: %m"); return 0; } @@ -5489,7 +5613,7 @@ int get_shell(char **_s) { return 0; } -bool filename_is_safe(const char *p) { +bool filename_is_valid(const char *p) { if (isempty(p)) return false; @@ -5820,7 +5944,7 @@ int on_ac_power(void) { if (!de) break; - if (ignore_file(de->d_name)) + if (hidden_file(de->d_name)) continue; device = openat(dirfd(d), de->d_name, O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_NOCTTY); @@ -6145,38 +6269,16 @@ int split_pair(const char *s, const char *sep, char **l, char **r) { } int shall_restore_state(void) { - _cleanup_free_ char *line = NULL; - const char *p; + _cleanup_free_ char *value = NULL; int r; - r = proc_cmdline(&line); + r = get_proc_cmdline_key("systemd.restore_state=", &value); if (r < 0) return r; + if (r == 0) + return true; - r = 1; - p = line; - - for (;;) { - _cleanup_free_ char *word = NULL; - const char *e; - int k; - - k = unquote_first_word(&p, &word, true); - if (k < 0) - return k; - if (k == 0) - break; - - e = startswith(word, "systemd.restore_state="); - if (!e) - continue; - - k = parse_boolean(e); - if (k >= 0) - r = k; - } - - return r; + return parse_boolean(value) != 0; } int proc_cmdline(char **ret) { @@ -6227,6 +6329,59 @@ int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value)) { return 0; } +int get_proc_cmdline_key(const char *key, char **value) { + _cleanup_free_ char *line = NULL, *ret = NULL; + bool found = false; + const char *p; + int r; + + assert(key); + + r = proc_cmdline(&line); + if (r < 0) + return r; + + p = line; + for (;;) { + _cleanup_free_ char *word = NULL; + const char *e; + + r = unquote_first_word(&p, &word, true); + if (r < 0) + return r; + if (r == 0) + break; + + /* Filter out arguments that are intended only for the + * initrd */ + if (!in_initrd() && startswith(word, "rd.")) + continue; + + if (value) { + e = startswith(word, key); + if (!e) + continue; + + r = free_and_strdup(&ret, e); + if (r < 0) + return r; + + found = true; + } else { + if (streq(word, key)) + found = true; + } + } + + if (value) { + *value = ret; + ret = NULL; + } + + return found; + +} + int container_get_leader(const char *machine, pid_t *pid) { _cleanup_free_ char *s = NULL, *class = NULL; const char *p; @@ -6397,6 +6552,10 @@ int getpeercred(int fd, struct ucred *ucred) { * to namespacing issues */ if (u.pid <= 0) return -ENODATA; + if (u.uid == UID_INVALID) + return -ENODATA; + if (u.gid == GID_INVALID) + return -ENODATA; *ucred = u; return 0; @@ -6700,6 +6859,15 @@ int umount_recursive(const char *prefix, int flags) { return r ? r : n; } +static int get_mount_flags(const char *path, unsigned long *flags) { + struct statvfs buf; + + if (statvfs(path, &buf) < 0) + return -errno; + *flags = buf.f_flag; + return 0; +} + int bind_remount_recursive(const char *prefix, bool ro) { _cleanup_set_free_free_ Set *done = NULL; _cleanup_free_ char *cleaned = NULL; @@ -6734,6 +6902,7 @@ int bind_remount_recursive(const char *prefix, bool ro) { _cleanup_set_free_free_ Set *todo = NULL; bool top_autofs = false; char *x; + unsigned long orig_flags; todo = set_new(&string_hash_ops); if (!todo) @@ -6811,7 +6980,11 @@ int bind_remount_recursive(const char *prefix, bool ro) { if (mount(cleaned, cleaned, NULL, MS_BIND|MS_REC, NULL) < 0) return -errno; - if (mount(NULL, prefix, NULL, MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0) + orig_flags = 0; + (void) get_mount_flags(cleaned, &orig_flags); + orig_flags &= ~MS_RDONLY; + + if (mount(NULL, prefix, NULL, orig_flags|MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0) return -errno; x = strdup(cleaned); @@ -6831,7 +7004,14 @@ int bind_remount_recursive(const char *prefix, bool ro) { if (r < 0) return r; - if (mount(NULL, x, NULL, MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0) { + /* Try to reuse the original flag set, but + * don't care for errors, in case of + * obstructed mounts */ + orig_flags = 0; + (void) get_mount_flags(x, &orig_flags); + orig_flags &= ~MS_RDONLY; + + if (mount(NULL, x, NULL, orig_flags|MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0) { /* Deal with mount points that are * obstructed by a later mount */ @@ -6856,42 +7036,61 @@ int fflush_and_check(FILE *f) { return 0; } -char *tempfn_xxxxxx(const char *p) { +int tempfn_xxxxxx(const char *p, char **ret) { const char *fn; char *t; - size_t k; assert(p); + assert(ret); - t = new(char, strlen(p) + 1 + 6 + 1); - if (!t) - return NULL; + /* + * Turns this: + * /foo/bar/waldo + * + * Into this: + * /foo/bar/.#waldoXXXXXX + */ fn = basename(p); - k = fn - p; + if (!filename_is_valid(fn)) + return -EINVAL; + + t = new(char, strlen(p) + 2 + 6 + 1); + if (!t) + return -ENOMEM; - strcpy(stpcpy(stpcpy(mempcpy(t, p, k), "."), fn), "XXXXXX"); + strcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), fn), "XXXXXX"); - return t; + *ret = path_kill_slashes(t); + return 0; } -char *tempfn_random(const char *p) { +int tempfn_random(const char *p, char **ret) { const char *fn; char *t, *x; uint64_t u; - size_t k; unsigned i; assert(p); + assert(ret); - t = new(char, strlen(p) + 1 + 16 + 1); - if (!t) - return NULL; + /* + * Turns this: + * /foo/bar/waldo + * + * Into this: + * /foo/bar/.#waldobaa2a261115984a9 + */ fn = basename(p); - k = fn - p; + if (!filename_is_valid(fn)) + return -EINVAL; + + t = new(char, strlen(p) + 2 + 16 + 1); + if (!t) + return -ENOMEM; - x = stpcpy(stpcpy(mempcpy(t, p, k), "."), fn); + x = stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), fn); u = random_u64(); for (i = 0; i < 16; i++) { @@ -6901,7 +7100,40 @@ char *tempfn_random(const char *p) { *x = 0; - return t; + *ret = path_kill_slashes(t); + return 0; +} + +int tempfn_random_child(const char *p, char **ret) { + char *t, *x; + uint64_t u; + unsigned i; + + assert(p); + assert(ret); + + /* Turns this: + * /foo/bar/waldo + * Into this: + * /foo/bar/waldo/.#3c2b6219aa75d7d0 + */ + + t = new(char, strlen(p) + 3 + 16 + 1); + if (!t) + return -ENOMEM; + + x = stpcpy(stpcpy(t, p), "/.#"); + + u = random_u64(); + for (i = 0; i < 16; i++) { + *(x++) = hexchar(u & 0xF); + u >>= 4; + } + + *x = 0; + + *ret = path_kill_slashes(t); + return 0; } /* make sure the hostname is not "localhost" */ @@ -7240,3 +7472,231 @@ int sethostname_idempotent(const char *s) { return 1; } + +int ptsname_malloc(int fd, char **ret) { + size_t l = 100; + + assert(fd >= 0); + assert(ret); + + for (;;) { + char *c; + + c = new(char, l); + if (!c) + return -ENOMEM; + + if (ptsname_r(fd, c, l) == 0) { + *ret = c; + return 0; + } + if (errno != ERANGE) { + free(c); + return -errno; + } + + free(c); + l *= 2; + } +} + +int openpt_in_namespace(pid_t pid, int flags) { + _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, rootfd = -1; + _cleanup_close_pair_ int pair[2] = { -1, -1 }; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int))]; + } control = {}; + struct msghdr mh = { + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + struct cmsghdr *cmsg; + siginfo_t si; + pid_t child; + int r; + + assert(pid > 0); + + r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, &rootfd); + if (r < 0) + return r; + + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) + return -errno; + + child = fork(); + if (child < 0) + return -errno; + + if (child == 0) { + int master; + + pair[0] = safe_close(pair[0]); + + r = namespace_enter(pidnsfd, mntnsfd, -1, rootfd); + if (r < 0) + _exit(EXIT_FAILURE); + + master = posix_openpt(flags); + if (master < 0) + _exit(EXIT_FAILURE); + + cmsg = CMSG_FIRSTHDR(&mh); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + memcpy(CMSG_DATA(cmsg), &master, sizeof(int)); + + mh.msg_controllen = cmsg->cmsg_len; + + if (sendmsg(pair[1], &mh, MSG_NOSIGNAL) < 0) + _exit(EXIT_FAILURE); + + _exit(EXIT_SUCCESS); + } + + pair[1] = safe_close(pair[1]); + + r = wait_for_terminate(child, &si); + if (r < 0) + return r; + if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) + return -EIO; + + if (recvmsg(pair[0], &mh, MSG_NOSIGNAL|MSG_CMSG_CLOEXEC) < 0) + return -errno; + + for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg)) + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { + int *fds; + unsigned n_fds; + + fds = (int*) CMSG_DATA(cmsg); + n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + + if (n_fds != 1) { + close_many(fds, n_fds); + return -EIO; + } + + return fds[0]; + } + + return -EIO; +} + +ssize_t fgetxattrat_fake(int dirfd, const char *filename, const char *attribute, void *value, size_t size, int flags) { + _cleanup_close_ int fd = -1; + ssize_t l; + + /* The kernel doesn't have a fgetxattrat() command, hence let's emulate one */ + + fd = openat(dirfd, filename, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOATIME|(flags & AT_SYMLINK_NOFOLLOW ? O_NOFOLLOW : 0)); + if (fd < 0) + return -errno; + + l = fgetxattr(fd, attribute, value, size); + if (l < 0) + return -errno; + + return l; +} + +static int parse_crtime(le64_t le, usec_t *usec) { + uint64_t u; + + assert(usec); + + u = le64toh(le); + if (u == 0 || u == (uint64_t) -1) + return -EIO; + + *usec = (usec_t) u; + return 0; +} + +int fd_getcrtime(int fd, usec_t *usec) { + le64_t le; + ssize_t n; + + assert(fd >= 0); + assert(usec); + + /* Until Linux gets a real concept of birthtime/creation time, + * let's fake one with xattrs */ + + n = fgetxattr(fd, "user.crtime_usec", &le, sizeof(le)); + if (n < 0) + return -errno; + if (n != sizeof(le)) + return -EIO; + + return parse_crtime(le, usec); +} + +int fd_getcrtime_at(int dirfd, const char *name, usec_t *usec, int flags) { + le64_t le; + ssize_t n; + + n = fgetxattrat_fake(dirfd, name, "user.crtime_usec", &le, sizeof(le), flags); + if (n < 0) + return -errno; + if (n != sizeof(le)) + return -EIO; + + return parse_crtime(le, usec); +} + +int path_getcrtime(const char *p, usec_t *usec) { + le64_t le; + ssize_t n; + + assert(p); + assert(usec); + + n = getxattr(p, "user.crtime_usec", &le, sizeof(le)); + if (n < 0) + return -errno; + if (n != sizeof(le)) + return -EIO; + + return parse_crtime(le, usec); +} + +int fd_setcrtime(int fd, usec_t usec) { + le64_t le; + + assert(fd >= 0); + + le = htole64((uint64_t) usec); + if (fsetxattr(fd, "user.crtime_usec", &le, sizeof(le), 0) < 0) + return -errno; + + return 0; +} + +int same_fd(int a, int b) { + struct stat sta, stb; + + assert(a >= 0); + assert(b >= 0); + + if (a == b) + return true; + + if (fstat(a, &sta) < 0) + return -errno; + + if (fstat(b, &stb) < 0) + return -errno; + + if ((sta.st_mode & S_IFMT) != (stb.st_mode & S_IFMT)) + return false; + + if (S_ISREG(sta.st_mode) || S_ISDIR(sta.st_mode) || S_ISFIFO(sta.st_mode) || S_ISSOCK(sta.st_mode) || S_ISLNK(sta.st_mode)) + return (sta.st_dev == stb.st_dev) && (sta.st_ino == stb.st_ino); + + /* We consider all device fds different... */ + return false; +}