X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fshared%2Futil.c;h=950519ed06ddfe96578ff98454c52580c24cbe73;hp=ce23b4790a72719497cef05c1f7db7911eea3445;hb=86e97d599f8b1ca379dce64fadac9b8f6b002ac5;hpb=b87df1e2ab57146e68e95964a3618b6ebf5ae3f9 diff --git a/src/shared/util.c b/src/shared/util.c index ce23b4790..950519ed0 100644 --- a/src/shared/util.c +++ b/src/shared/util.c @@ -60,8 +60,8 @@ #include /* When we include libgen.h because we need dirname() we immediately - * undefine basename() since libgen.h defines it as a macro to the XDG - * version which is really broken. */ + * undefine basename() since libgen.h defines it as a macro to the POSIX + * version which is really broken. We prefer GNU basename(). */ #include #undef basename @@ -92,6 +92,8 @@ #include "process-util.h" #include "random-util.h" #include "terminal-util.h" +#include "hostname-util.h" +#include "signal-util.h" /* Put this test here for a lack of better place */ assert_cc(EAGAIN == EWOULDBLOCK); @@ -147,6 +149,27 @@ char* endswith(const char *s, const char *postfix) { return (char*) s + sl - pl; } +char* endswith_no_case(const char *s, const char *postfix) { + size_t sl, pl; + + assert(s); + assert(postfix); + + sl = strlen(s); + pl = strlen(postfix); + + if (pl == 0) + return (char*) s + sl; + + if (sl < pl) + return NULL; + + if (strcasecmp(s + sl - pl, postfix) != 0) + return NULL; + + return (char*) s + sl - pl; +} + char* first_word(const char *s, const char *word) { size_t sl, wl; const char *p; @@ -350,7 +373,6 @@ int parse_uid(const char *s, uid_t* ret_uid) { int r; assert(s); - assert(ret_uid); r = safe_atolu(s, &ul); if (r < 0) @@ -369,7 +391,9 @@ int parse_uid(const char *s, uid_t* ret_uid) { if (uid == (uid_t) 0xFFFF) return -ENXIO; - *ret_uid = uid; + if (ret_uid) + *ret_uid = uid; + return 0; } @@ -570,13 +594,12 @@ const char* split(const char **state, size_t *l, const char *separator, bool quo char quotechars[2] = {*current, '\0'}; *l = strcspn_escaped(current + 1, quotechars); - if (current[*l + 1] == '\0' || + if (current[*l + 1] == '\0' || current[*l + 1] != quotechars[0] || (current[*l + 2] && !strchr(separator, current[*l + 2]))) { /* right quote missing or garbage at the end */ *state = current; return NULL; } - assert(current[*l + 1] == quotechars[0]); *state = current++ + *l + 2; } else if (quoted) { *l = strcspn_escaped(current, separator); @@ -749,41 +772,6 @@ int readlink_and_canonicalize(const char *p, char **r) { return 0; } -int reset_all_signal_handlers(void) { - int sig, r = 0; - - for (sig = 1; sig < _NSIG; sig++) { - struct sigaction sa = { - .sa_handler = SIG_DFL, - .sa_flags = SA_RESTART, - }; - - /* These two cannot be caught... */ - if (sig == SIGKILL || sig == SIGSTOP) - continue; - - /* On Linux the first two RT signals are reserved by - * glibc, and sigaction() will return EINVAL for them. */ - if ((sigaction(sig, &sa, NULL) < 0)) - if (errno != EINVAL && r == 0) - r = -errno; - } - - return r; -} - -int reset_signal_mask(void) { - sigset_t ss; - - if (sigemptyset(&ss) < 0) - return -errno; - - if (sigprocmask(SIG_SETMASK, &ss, NULL) < 0) - return -errno; - - return 0; -} - char *strstrip(char *s) { char *e; @@ -1164,7 +1152,7 @@ static int cunescape_one(const char *p, size_t length, char *ret, uint32_t *ret_ int a, b, c; uint32_t m; - if (length != (size_t) -1 && length < 4) + if (length != (size_t) -1 && length < 3) return -EINVAL; a = unoctchar(p[0]); @@ -1539,59 +1527,6 @@ int flush_fd(int fd) { } } -int sigaction_many(const struct sigaction *sa, ...) { - va_list ap; - int r = 0, sig; - - va_start(ap, sa); - while ((sig = va_arg(ap, int)) > 0) - if (sigaction(sig, sa, NULL) < 0) - r = -errno; - va_end(ap); - - return r; -} - -int ignore_signals(int sig, ...) { - struct sigaction sa = { - .sa_handler = SIG_IGN, - .sa_flags = SA_RESTART, - }; - va_list ap; - int r = 0; - - if (sigaction(sig, &sa, NULL) < 0) - r = -errno; - - va_start(ap, sig); - while ((sig = va_arg(ap, int)) > 0) - if (sigaction(sig, &sa, NULL) < 0) - r = -errno; - va_end(ap); - - return r; -} - -int default_signals(int sig, ...) { - struct sigaction sa = { - .sa_handler = SIG_DFL, - .sa_flags = SA_RESTART, - }; - va_list ap; - int r = 0; - - if (sigaction(sig, &sa, NULL) < 0) - r = -errno; - - va_start(ap, sig); - while ((sig = va_arg(ap, int)) > 0) - if (sigaction(sig, &sa, NULL) < 0) - r = -errno; - va_end(ap); - - return r; -} - void safe_close_pair(int p[]) { assert(p); @@ -1664,7 +1599,7 @@ int loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) { errno = 0; - while (nbytes > 0) { + do { ssize_t k; k = write(fd, p, nbytes); @@ -1684,19 +1619,19 @@ int loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) { return -errno; } - if (k == 0) /* Can't really happen */ + if (nbytes > 0 && k == 0) /* Can't really happen */ return -EIO; p += k; nbytes -= k; - } + } while (nbytes > 0); return 0; } int parse_size(const char *t, off_t base, off_t *size) { - /* Soo, sometimes we want to parse IEC binary suffxies, and + /* Soo, sometimes we want to parse IEC binary suffixes, and * sometimes SI decimal suffixes. This function can parse * both. Which one is the right way depends on the * context. Wikipedia suggests that SI is customary for @@ -1905,55 +1840,6 @@ void rename_process(const char name[8]) { } } -void sigset_add_many(sigset_t *ss, ...) { - va_list ap; - int sig; - - assert(ss); - - va_start(ap, ss); - while ((sig = va_arg(ap, int)) > 0) - assert_se(sigaddset(ss, sig) == 0); - va_end(ap); -} - -int sigprocmask_many(int how, ...) { - va_list ap; - sigset_t ss; - int sig; - - assert_se(sigemptyset(&ss) == 0); - - va_start(ap, how); - while ((sig = va_arg(ap, int)) > 0) - assert_se(sigaddset(&ss, sig) == 0); - va_end(ap); - - if (sigprocmask(how, &ss, NULL) < 0) - return -errno; - - return 0; -} - -char* gethostname_malloc(void) { - struct utsname u; - - assert_se(uname(&u) >= 0); - - if (!isempty(u.nodename) && !streq(u.nodename, "(none)")) - return strdup(u.nodename); - - return strdup(u.sysname); -} - -bool hostname_is_set(void) { - struct utsname u; - - assert_se(uname(&u) >= 0); - - return !isempty(u.nodename) && !streq(u.nodename, "(none)"); -} - char *lookup_uid(uid_t uid) { long bufsize; char *name; @@ -2342,18 +2228,6 @@ DIR *xopendirat(int fd, const char *name, int flags) { return d; } -int signal_from_string_try_harder(const char *s) { - int signo; - assert(s); - - signo = signal_from_string(s); - if (signo <= 0) - if (startswith(s, "SIG")) - return signal_from_string(s+3); - - return signo; -} - static char *tag_to_udev_node(const char *tagvalue, const char *by) { _cleanup_free_ char *t = NULL, *u = NULL; size_t enc_len; @@ -2427,8 +2301,8 @@ static int do_execute(char **directories, usec_t timeout, char *argv[]) { /* We fork this all off from a child process so that we can * somewhat cleanly make use of SIGALRM to set a time limit */ - reset_all_signal_handlers(); - reset_signal_mask(); + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); @@ -2585,79 +2459,6 @@ char* strshorten(char *s, size_t l) { return s; } -static bool hostname_valid_char(char c) { - return - (c >= 'a' && c <= 'z') || - (c >= 'A' && c <= 'Z') || - (c >= '0' && c <= '9') || - c == '-' || - c == '_' || - c == '.'; -} - -bool hostname_is_valid(const char *s) { - const char *p; - bool dot; - - if (isempty(s)) - return false; - - /* Doesn't accept empty hostnames, hostnames with trailing or - * leading dots, and hostnames with multiple dots in a - * sequence. Also ensures that the length stays below - * HOST_NAME_MAX. */ - - for (p = s, dot = true; *p; p++) { - if (*p == '.') { - if (dot) - return false; - - dot = true; - } else { - if (!hostname_valid_char(*p)) - return false; - - dot = false; - } - } - - if (dot) - return false; - - if (p-s > HOST_NAME_MAX) - return false; - - return true; -} - -char* hostname_cleanup(char *s, bool lowercase) { - char *p, *d; - bool dot; - - for (p = s, d = s, dot = true; *p; p++) { - if (*p == '.') { - if (dot) - continue; - - *(d++) = '.'; - dot = true; - } else if (hostname_valid_char(*p)) { - *(d++) = lowercase ? tolower(*p) : *p; - dot = false; - } - - } - - if (dot && d > s) - d[-1] = 0; - else - *d = 0; - - strshorten(s, HOST_NAME_MAX); - - return s; -} - bool machine_name_is_valid(const char *s) { if (!hostname_is_valid(s)) @@ -2720,7 +2521,7 @@ int fopen_temporary(const char *path, FILE **_f, char **_temp_path) { assert(_f); assert(_temp_path); - r = tempfn_xxxxxx(path, &t); + r = tempfn_xxxxxx(path, NULL, &t); if (r < 0) return r; @@ -2750,7 +2551,7 @@ int symlink_atomic(const char *from, const char *to) { assert(from); assert(to); - r = tempfn_random(to, &t); + r = tempfn_random(to, NULL, &t); if (r < 0) return r; @@ -2765,13 +2566,35 @@ int symlink_atomic(const char *from, const char *to) { return 0; } +int symlink_idempotent(const char *from, const char *to) { + _cleanup_free_ char *p = NULL; + int r; + + assert(from); + assert(to); + + if (symlink(from, to) < 0) { + if (errno != EEXIST) + return -errno; + + r = readlink_malloc(to, &p); + if (r < 0) + return r; + + if (!streq(p, from)) + return -EINVAL; + } + + return 0; +} + int mknod_atomic(const char *path, mode_t mode, dev_t dev) { _cleanup_free_ char *t = NULL; int r; assert(path); - r = tempfn_random(path, &t); + r = tempfn_random(path, NULL, &t); if (r < 0) return r; @@ -2792,7 +2615,7 @@ int mkfifo_atomic(const char *path, mode_t mode) { assert(path); - r = tempfn_random(path, &t); + r = tempfn_random(path, NULL, &t); if (r < 0) return r; @@ -3361,81 +3184,6 @@ static const char* const ip_tos_table[] = { DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ip_tos, int, 0xff); -static const char *const __signal_table[] = { - [SIGHUP] = "HUP", - [SIGINT] = "INT", - [SIGQUIT] = "QUIT", - [SIGILL] = "ILL", - [SIGTRAP] = "TRAP", - [SIGABRT] = "ABRT", - [SIGBUS] = "BUS", - [SIGFPE] = "FPE", - [SIGKILL] = "KILL", - [SIGUSR1] = "USR1", - [SIGSEGV] = "SEGV", - [SIGUSR2] = "USR2", - [SIGPIPE] = "PIPE", - [SIGALRM] = "ALRM", - [SIGTERM] = "TERM", -#ifdef SIGSTKFLT - [SIGSTKFLT] = "STKFLT", /* Linux on SPARC doesn't know SIGSTKFLT */ -#endif - [SIGCHLD] = "CHLD", - [SIGCONT] = "CONT", - [SIGSTOP] = "STOP", - [SIGTSTP] = "TSTP", - [SIGTTIN] = "TTIN", - [SIGTTOU] = "TTOU", - [SIGURG] = "URG", - [SIGXCPU] = "XCPU", - [SIGXFSZ] = "XFSZ", - [SIGVTALRM] = "VTALRM", - [SIGPROF] = "PROF", - [SIGWINCH] = "WINCH", - [SIGIO] = "IO", - [SIGPWR] = "PWR", - [SIGSYS] = "SYS" -}; - -DEFINE_PRIVATE_STRING_TABLE_LOOKUP(__signal, int); - -const char *signal_to_string(int signo) { - static thread_local char buf[sizeof("RTMIN+")-1 + DECIMAL_STR_MAX(int) + 1]; - const char *name; - - name = __signal_to_string(signo); - if (name) - return name; - - if (signo >= SIGRTMIN && signo <= SIGRTMAX) - snprintf(buf, sizeof(buf), "RTMIN+%d", signo - SIGRTMIN); - else - snprintf(buf, sizeof(buf), "%d", signo); - - return buf; -} - -int signal_from_string(const char *s) { - int signo; - int offset = 0; - unsigned u; - - signo = __signal_from_string(s); - if (signo > 0) - return signo; - - if (startswith(s, "RTMIN+")) { - s += 6; - offset = SIGRTMIN; - } - if (safe_atou(s, &u) >= 0) { - signo = (int) u + offset; - if (signo > 0 && signo < _NSIG) - return signo; - } - return -EINVAL; -} - bool kexec_loaded(void) { bool loaded = false; char *s; @@ -3596,8 +3344,8 @@ int fork_agent(pid_t *pid, const int except[], unsigned n_except, const char *pa /* Make sure we actually can kill the agent, if we need to, in * case somebody invoked us from a shell script that trapped * SIGTERM or so... */ - reset_all_signal_handlers(); - reset_signal_mask(); + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); /* Check whether our parent died before we were able * to set the death signal and unblock the signals */ @@ -3917,7 +3665,7 @@ bool path_is_safe(const char *p) { if (streq(p, "..") || startswith(p, "../") || endswith(p, "/..") || strstr(p, "/../")) return false; - if (strlen(p) > PATH_MAX) + if (strlen(p)+1 > PATH_MAX) return false; /* The following two checks are not really dangerous, but hey, they still are confusing */ @@ -4735,16 +4483,7 @@ int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int root_fd) { return -errno; } - if (setresgid(0, 0, 0) < 0) - return -errno; - - if (setgroups(0, NULL) < 0) - return -errno; - - if (setresuid(0, 0, 0) < 0) - return -errno; - - return 0; + return reset_uid_gid(); } int getpeercred(int fd, struct ucred *ucred) { @@ -4838,7 +4577,7 @@ int open_tmpfile(const char *path, int flags) { #ifdef O_TMPFILE /* Try O_TMPFILE first, if it is supported */ - fd = open(path, flags|O_TMPFILE, S_IRUSR|S_IWUSR); + fd = open(path, flags|O_TMPFILE|O_EXCL, S_IRUSR|S_IWUSR); if (fd >= 0) return fd; #endif @@ -4895,10 +4634,7 @@ unsigned long personality_from_string(const char *p) { return PER_LINUX; #endif - /* personality(7) documents that 0xffffffffUL is used for - * querying the current personality, hence let's use that here - * as error indicator. */ - return 0xffffffffUL; + return PERSONALITY_INVALID; } const char* personality_to_string(unsigned long p) { @@ -4980,7 +4716,7 @@ int update_reboot_param_file(const char *param) { if (param) { - r = write_string_file(REBOOT_PARAM_FILE, param); + r = write_string_file(REBOOT_PARAM_FILE, param, WRITE_STRING_FILE_CREATE); if (r < 0) log_error("Failed to write reboot param to " REBOOT_PARAM_FILE": %s", strerror(-r)); @@ -5196,7 +4932,7 @@ int bind_remount_recursive(const char *prefix, bool ro) { while ((x = set_steal_first(todo))) { r = set_consume(done, x); - if (r == -EEXIST) + if (r == -EEXIST || r == 0) continue; if (r < 0) return r; @@ -5233,7 +4969,7 @@ int fflush_and_check(FILE *f) { return 0; } -int tempfn_xxxxxx(const char *p, char **ret) { +int tempfn_xxxxxx(const char *p, const char *extra, char **ret) { const char *fn; char *t; @@ -5245,24 +4981,27 @@ int tempfn_xxxxxx(const char *p, char **ret) { * /foo/bar/waldo * * Into this: - * /foo/bar/.#waldoXXXXXX + * /foo/bar/.#waldoXXXXXX */ fn = basename(p); if (!filename_is_valid(fn)) return -EINVAL; - t = new(char, strlen(p) + 2 + 6 + 1); + if (extra == NULL) + extra = ""; + + t = new(char, strlen(p) + 2 + strlen(extra) + 6 + 1); if (!t) return -ENOMEM; - strcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), fn), "XXXXXX"); + strcpy(stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), extra), fn), "XXXXXX"); *ret = path_kill_slashes(t); return 0; } -int tempfn_random(const char *p, char **ret) { +int tempfn_random(const char *p, const char *extra, char **ret) { const char *fn; char *t, *x; uint64_t u; @@ -5276,18 +5015,21 @@ int tempfn_random(const char *p, char **ret) { * /foo/bar/waldo * * Into this: - * /foo/bar/.#waldobaa2a261115984a9 + * /foo/bar/.#waldobaa2a261115984a9 */ fn = basename(p); if (!filename_is_valid(fn)) return -EINVAL; - t = new(char, strlen(p) + 2 + 16 + 1); + if (!extra) + extra = ""; + + t = new(char, strlen(p) + 2 + strlen(extra) + 16 + 1); if (!t) return -ENOMEM; - x = stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), fn); + x = stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), extra), fn); u = random_u64(); for (i = 0; i < 16; i++) { @@ -5301,7 +5043,7 @@ int tempfn_random(const char *p, char **ret) { return 0; } -int tempfn_random_child(const char *p, char **ret) { +int tempfn_random_child(const char *p, const char *extra, char **ret) { char *t, *x; uint64_t u; unsigned i; @@ -5312,14 +5054,17 @@ int tempfn_random_child(const char *p, char **ret) { /* Turns this: * /foo/bar/waldo * Into this: - * /foo/bar/waldo/.#3c2b6219aa75d7d0 + * /foo/bar/waldo/.#3c2b6219aa75d7d0 */ - t = new(char, strlen(p) + 3 + 16 + 1); + if (!extra) + extra = ""; + + t = new(char, strlen(p) + 3 + strlen(extra) + 16 + 1); if (!t) return -ENOMEM; - x = stpcpy(stpcpy(t, p), "/.#"); + x = stpcpy(stpcpy(stpcpy(t, p), "/.#"), extra); u = random_u64(); for (i = 0; i < 16; i++) { @@ -5333,23 +5078,6 @@ int tempfn_random_child(const char *p, char **ret) { return 0; } -/* make sure the hostname is not "localhost" */ -bool is_localhost(const char *hostname) { - assert(hostname); - - /* This tries to identify local host and domain names - * described in RFC6761 plus the redhatism of .localdomain */ - - return streq(hostname, "localhost") || - streq(hostname, "localhost.") || - streq(hostname, "localdomain.") || - streq(hostname, "localdomain") || - endswith(hostname, ".localhost") || - endswith(hostname, ".localhost.") || - endswith(hostname, ".localdomain") || - endswith(hostname, ".localdomain."); -} - int take_password_lock(const char *root) { struct flock flock = { @@ -5413,6 +5141,15 @@ int is_dir(const char* path, bool follow) { return !!S_ISDIR(st.st_mode); } +int is_device_node(const char *path) { + struct stat info; + + if (lstat(path, &info) < 0) + return -errno; + + return !!(S_ISBLK(info.st_mode) || S_ISCHR(info.st_mode)); +} + int unquote_first_word(const char **p, char **ret, UnquoteFlags flags) { _cleanup_free_ char *s = NULL; size_t allocated = 0, sz = 0; @@ -5472,35 +5209,6 @@ int unquote_first_word(const char **p, char **ret, UnquoteFlags flags) { break; - case VALUE_ESCAPE: - if (c == 0) { - if (flags & UNQUOTE_RELAX) - goto finish; - return -EINVAL; - } - - if (!GREEDY_REALLOC(s, allocated, sz+7)) - return -ENOMEM; - - if (flags & UNQUOTE_CUNESCAPE) { - uint32_t u; - - r = cunescape_one(*p, (size_t) -1, &c, &u); - if (r < 0) - return -EINVAL; - - (*p) += r - 1; - - if (c != 0) - s[sz++] = c; /* normal explicit char */ - else - sz += utf8_encode_unichar(s + sz, u); /* unicode chars we'll encode as utf8 */ - } else - s[sz++] = c; - - state = VALUE; - break; - case SINGLE_QUOTE: if (c == 0) { if (flags & UNQUOTE_RELAX) @@ -5519,35 +5227,6 @@ int unquote_first_word(const char **p, char **ret, UnquoteFlags flags) { break; - case SINGLE_QUOTE_ESCAPE: - if (c == 0) { - if (flags & UNQUOTE_RELAX) - goto finish; - return -EINVAL; - } - - if (!GREEDY_REALLOC(s, allocated, sz+7)) - return -ENOMEM; - - if (flags & UNQUOTE_CUNESCAPE) { - uint32_t u; - - r = cunescape_one(*p, (size_t) -1, &c, &u); - if (r < 0) - return -EINVAL; - - (*p) += r - 1; - - if (c != 0) - s[sz++] = c; - else - sz += utf8_encode_unichar(s + sz, u); - } else - s[sz++] = c; - - state = SINGLE_QUOTE; - break; - case DOUBLE_QUOTE: if (c == 0) return -EINVAL; @@ -5564,33 +5243,56 @@ int unquote_first_word(const char **p, char **ret, UnquoteFlags flags) { break; + case SINGLE_QUOTE_ESCAPE: case DOUBLE_QUOTE_ESCAPE: + case VALUE_ESCAPE: + if (!GREEDY_REALLOC(s, allocated, sz+7)) + return -ENOMEM; + if (c == 0) { + if ((flags & UNQUOTE_CUNESCAPE_RELAX) && + (state == VALUE_ESCAPE || flags & UNQUOTE_RELAX)) { + /* If we find an unquoted trailing backslash and we're in + * UNQUOTE_CUNESCAPE_RELAX mode, keep it verbatim in the + * output. + * + * Unbalanced quotes will only be allowed in UNQUOTE_RELAX + * mode, UNQUOTE_CUNESCAP_RELAX mode does not allow them. + */ + s[sz++] = '\\'; + goto finish; + } if (flags & UNQUOTE_RELAX) goto finish; return -EINVAL; } - if (!GREEDY_REALLOC(s, allocated, sz+7)) - return -ENOMEM; - if (flags & UNQUOTE_CUNESCAPE) { uint32_t u; r = cunescape_one(*p, (size_t) -1, &c, &u); - if (r < 0) + if (r < 0) { + if (flags & UNQUOTE_CUNESCAPE_RELAX) { + s[sz++] = '\\'; + s[sz++] = c; + goto end_escape; + } return -EINVAL; + } (*p) += r - 1; if (c != 0) - s[sz++] = c; + s[sz++] = c; /* normal explicit char */ else - sz += utf8_encode_unichar(s + sz, u); + sz += utf8_encode_unichar(s + sz, u); /* unicode chars we'll encode as utf8 */ } else s[sz++] = c; - state = DOUBLE_QUOTE; +end_escape: + state = (state == SINGLE_QUOTE_ESCAPE) ? SINGLE_QUOTE : + (state == DOUBLE_QUOTE_ESCAPE) ? DOUBLE_QUOTE : + VALUE; break; case SPACE: @@ -5618,6 +5320,36 @@ finish: return 1; } +int unquote_first_word_and_warn( + const char **p, + char **ret, + UnquoteFlags flags, + const char *unit, + const char *filename, + unsigned line, + const char *rvalue) { + /* Try to unquote it, if it fails, warn about it and try again but this + * time using UNQUOTE_CUNESCAPE_RELAX to keep the backslashes verbatim + * in invalid escape sequences. */ + const char *save; + int r; + + save = *p; + r = unquote_first_word(p, ret, flags); + if (r < 0 && !(flags&UNQUOTE_CUNESCAPE_RELAX)) { + /* Retry it with UNQUOTE_CUNESCAPE_RELAX. */ + *p = save; + r = unquote_first_word(p, ret, flags|UNQUOTE_CUNESCAPE_RELAX); + if (r < 0) + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "Unbalanced quoting in command line, ignoring: \"%s\"", rvalue); + else + log_syntax(unit, LOG_WARNING, filename, line, EINVAL, + "Invalid escape sequences in command line: \"%s\"", rvalue); + } + return r; +} + int unquote_many_words(const char **p, UnquoteFlags flags, ...) { va_list ap; char **l; @@ -5682,6 +5414,9 @@ int free_and_strdup(char **p, const char *s) { /* Replaces a string pointer with an strdup()ed new string, * possibly freeing the old one. */ + if (streq_ptr(*p, s)) + return 0; + if (s) { t = strdup(s); if (!t) @@ -5692,26 +5427,6 @@ int free_and_strdup(char **p, const char *s) { free(*p); *p = t; - return 0; -} - -int sethostname_idempotent(const char *s) { - int r; - char buf[HOST_NAME_MAX + 1] = {}; - - assert(s); - - r = gethostname(buf, sizeof(buf)); - if (r < 0) - return -errno; - - if (streq(buf, s)) - return 0; - - r = sethostname(s, strlen(s)); - if (r < 0) - return -errno; - return 1; } @@ -5809,7 +5524,7 @@ int openpt_in_namespace(pid_t pid, int flags) { if (recvmsg(pair[0], &mh, MSG_NOSIGNAL|MSG_CMSG_CLOEXEC) < 0) return -errno; - for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg)) + CMSG_FOREACH(cmsg, &mh) if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { int *fds; unsigned n_fds; @@ -5921,11 +5636,89 @@ int fd_setcrtime(int fd, usec_t usec) { return 0; } +int same_fd(int a, int b) { + struct stat sta, stb; + pid_t pid; + int r, fa, fb; + + assert(a >= 0); + assert(b >= 0); + + /* Compares two file descriptors. Note that semantics are + * quite different depending on whether we have kcmp() or we + * don't. If we have kcmp() this will only return true for + * dup()ed file descriptors, but not otherwise. If we don't + * have kcmp() this will also return true for two fds of the same + * file, created by separate open() calls. Since we use this + * call mostly for filtering out duplicates in the fd store + * this difference hopefully doesn't matter too much. */ + + if (a == b) + return true; + + /* Try to use kcmp() if we have it. */ + pid = getpid(); + r = kcmp(pid, pid, KCMP_FILE, a, b); + if (r == 0) + return true; + if (r > 0) + return false; + if (errno != ENOSYS) + return -errno; + + /* We don't have kcmp(), use fstat() instead. */ + 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; + + /* We consider all device fds different, since two device fds + * might refer to quite different device contexts even though + * they share the same inode and backing dev_t. */ + + if (S_ISCHR(sta.st_mode) || S_ISBLK(sta.st_mode)) + return false; + + if (sta.st_dev != stb.st_dev || sta.st_ino != stb.st_ino) + return false; + + /* The fds refer to the same inode on disk, let's also check + * if they have the same fd flags. This is useful to + * distinguish the read and write side of a pipe created with + * pipe(). */ + fa = fcntl(a, F_GETFL); + if (fa < 0) + return -errno; + + fb = fcntl(b, F_GETFL); + if (fb < 0) + return -errno; + + return fa == fb; +} + int chattr_fd(int fd, unsigned value, unsigned mask) { unsigned old_attr, new_attr; + struct stat st; assert(fd >= 0); + if (fstat(fd, &st) < 0) + return -errno; + + /* Explicitly check whether this is a regular file or + * directory. If it is anything else (such as a device node or + * fifo), then the ioctl will not hit the file systems but + * possibly drivers, where the ioctl might have different + * effects. Notably, DRM is using the same ioctl() number. */ + + if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) + return -ENOTTY; + if (mask == 0) return 0; @@ -5958,8 +5751,16 @@ int chattr_path(const char *p, unsigned value, unsigned mask) { } int read_attr_fd(int fd, unsigned *ret) { + struct stat st; + assert(fd >= 0); + if (fstat(fd, &st) < 0) + return -errno; + + if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) + return -ENOTTY; + if (ioctl(fd, FS_IOC_GETFLAGS, ret) < 0) return -errno; @@ -5979,128 +5780,6 @@ int read_attr_path(const char *p, unsigned *ret) { return read_attr_fd(fd, ret); } -int make_lock_file(const char *p, int operation, LockFile *ret) { - _cleanup_close_ int fd = -1; - _cleanup_free_ char *t = NULL; - int r; - - /* - * We use UNPOSIX locks if they are available. They have nice - * semantics, and are mostly compatible with NFS. However, - * they are only available on new kernels. When we detect we - * are running on an older kernel, then we fall back to good - * old BSD locks. They also have nice semantics, but are - * slightly problematic on NFS, where they are upgraded to - * POSIX locks, even though locally they are orthogonal to - * POSIX locks. - */ - - t = strdup(p); - if (!t) - return -ENOMEM; - - for (;;) { - struct flock fl = { - .l_type = (operation & ~LOCK_NB) == LOCK_EX ? F_WRLCK : F_RDLCK, - .l_whence = SEEK_SET, - }; - struct stat st; - - fd = open(p, O_CREAT|O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NOCTTY, 0600); - if (fd < 0) - return -errno; - - r = fcntl(fd, (operation & LOCK_NB) ? F_OFD_SETLK : F_OFD_SETLKW, &fl); - if (r < 0) { - - /* If the kernel is too old, use good old BSD locks */ - if (errno == EINVAL) - r = flock(fd, operation); - - if (r < 0) - return errno == EAGAIN ? -EBUSY : -errno; - } - - /* If we acquired the lock, let's check if the file - * still exists in the file system. If not, then the - * previous exclusive owner removed it and then closed - * it. In such a case our acquired lock is worthless, - * hence try again. */ - - r = fstat(fd, &st); - if (r < 0) - return -errno; - if (st.st_nlink > 0) - break; - - fd = safe_close(fd); - } - - ret->path = t; - ret->fd = fd; - ret->operation = operation; - - fd = -1; - t = NULL; - - return r; -} - -int make_lock_file_for(const char *p, int operation, LockFile *ret) { - const char *fn; - char *t; - - assert(p); - assert(ret); - - fn = basename(p); - if (!filename_is_valid(fn)) - return -EINVAL; - - t = newa(char, strlen(p) + 2 + 4 + 1); - stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), fn), ".lck"); - - return make_lock_file(t, operation, ret); -} - -void release_lock_file(LockFile *f) { - int r; - - if (!f) - return; - - if (f->path) { - - /* If we are the exclusive owner we can safely delete - * the lock file itself. If we are not the exclusive - * owner, we can try becoming it. */ - - if (f->fd >= 0 && - (f->operation & ~LOCK_NB) == LOCK_SH) { - static const struct flock fl = { - .l_type = F_WRLCK, - .l_whence = SEEK_SET, - }; - - r = fcntl(f->fd, F_OFD_SETLK, &fl); - if (r < 0 && errno == EINVAL) - r = flock(f->fd, LOCK_EX|LOCK_NB); - - if (r >= 0) - f->operation = LOCK_EX|LOCK_NB; - } - - if ((f->operation & ~LOCK_NB) == LOCK_EX) - unlink_noerrno(f->path); - - free(f->path); - f->path = NULL; - } - - f->fd = safe_close(f->fd); - f->operation = 0; -} - static size_t nul_length(const uint8_t *p, size_t sz) { size_t n = 0; @@ -6233,7 +5912,7 @@ void cmsg_close_all(struct msghdr *mh) { assert(mh); - for (cmsg = CMSG_FIRSTHDR(mh); cmsg; cmsg = CMSG_NXTHDR(mh, cmsg)) + CMSG_FOREACH(cmsg, mh) if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) close_many((int*) CMSG_DATA(cmsg), (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int)); } @@ -6339,3 +6018,35 @@ int parse_mode(const char *s, mode_t *ret) { *ret = (mode_t) l; return 0; } + +int mount_move_root(const char *path) { + assert(path); + + if (chdir(path) < 0) + return -errno; + + if (mount(path, "/", NULL, MS_MOVE, NULL) < 0) + return -errno; + + if (chroot(".") < 0) + return -errno; + + if (chdir("/") < 0) + return -errno; + + return 0; +} + +int reset_uid_gid(void) { + + if (setgroups(0, NULL) < 0) + return -errno; + + if (setresgid(0, 0, 0) < 0) + return -errno; + + if (setresuid(0, 0, 0) < 0) + return -errno; + + return 0; +}