X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fshared%2Futil.c;h=72984735ced828e33c2eca18df7828fdf1db58c9;hp=c53f9cdb6acb1c3cd055bd7698d18b03a97c7082;hb=2f07de3b6cacf44462635ab0fff56391b491e454;hpb=63c372cb9df3bee01e3bf8cd7f96f336bddda846 diff --git a/src/shared/util.c b/src/shared/util.c index c53f9cdb6..72984735c 100644 --- a/src/shared/util.c +++ b/src/shared/util.c @@ -19,12 +19,13 @@ along with systemd; If not, see . ***/ -#include #include #include #include #include #include +#include +#include #include #include #include @@ -39,14 +40,13 @@ #include #include #include -#include +#include #include #include #include #include #include #include -#include #include #include #include @@ -60,23 +60,27 @@ #include #include #include -#include #include #include #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. */ +#include #undef basename #ifdef HAVE_SYS_AUXV_H #include #endif +#include "config.h" #include "macro.h" #include "util.h" #include "ioprio.h" #include "missing.h" #include "log.h" #include "strv.h" -#include "label.h" #include "mkdir.h" #include "path-util.h" #include "exit-status.h" @@ -90,6 +94,9 @@ #include "def.h" #include "sparse-endian.h" +/* Put this test here for a lack of better place */ +assert_cc(EAGAIN == EWOULDBLOCK); + int saved_argc = 0; char **saved_argv = NULL; @@ -1340,6 +1347,125 @@ char *cescape(const char *s) { return r; } +static int cunescape_one(const char *p, size_t length, char *ret) { + int r = 1; + + assert(p); + assert(*p); + assert(ret); + + if (length != (size_t) -1 && length < 1) + return -EINVAL; + + switch (p[0]) { + + case 'a': + *ret = '\a'; + break; + case 'b': + *ret = '\b'; + break; + case 'f': + *ret = '\f'; + break; + case 'n': + *ret = '\n'; + break; + case 'r': + *ret = '\r'; + break; + case 't': + *ret = '\t'; + break; + case 'v': + *ret = '\v'; + break; + case '\\': + *ret = '\\'; + break; + case '"': + *ret = '"'; + break; + case '\'': + *ret = '\''; + break; + + case 's': + /* This is an extension of the XDG syntax files */ + *ret = ' '; + break; + + case 'x': { + /* hexadecimal encoding */ + int a, b; + + if (length != (size_t) -1 && length < 3) + return -EINVAL; + + a = unhexchar(p[1]); + if (a < 0) + return -EINVAL; + + b = unhexchar(p[2]); + if (b < 0) + return -EINVAL; + + /* don't allow NUL bytes */ + if (a == 0 && b == 0) + return -EINVAL; + + *ret = (char) ((a << 4) | b); + r = 3; + break; + } + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': { + /* octal encoding */ + int a, b, c, m; + + if (length != (size_t) -1 && length < 4) + return -EINVAL; + + a = unoctchar(p[0]); + if (a < 0) + return -EINVAL; + + b = unoctchar(p[1]); + if (b < 0) + return -EINVAL; + + c = unoctchar(p[2]); + if (c < 0) + return -EINVAL; + + /* don't allow NUL bytes */ + if (a == 0 && b == 0 && c == 0) + return -EINVAL; + + /* Don't allow bytes above 255 */ + m = (a << 6) | (b << 3) | c; + if (m > 255) + return -EINVAL; + + *ret = (char) m; + r = 3; + break; + } + + default: + return -EINVAL; + } + + return r; +} + char *cunescape_length_with_prefix(const char *s, size_t length, const char *prefix) { char *r, *t; const char *f; @@ -1359,115 +1485,27 @@ char *cunescape_length_with_prefix(const char *s, size_t length, const char *pre memcpy(r, prefix, pl); for (f = s, t = r + pl; f < s + length; f++) { - size_t remaining = s + length - f; + size_t remaining; + int k; + + remaining = s + length - f; assert(remaining > 0); - if (*f != '\\') { /* a literal literal */ + if (*f != '\\' || remaining == 1) { + /* a literal literal, or a trailing backslash, copy verbatim */ *(t++) = *f; continue; } - if (--remaining == 0) { /* copy trailing backslash verbatim */ - *(t++) = *f; - break; - } - - f++; - - switch (*f) { - - case 'a': - *(t++) = '\a'; - break; - case 'b': - *(t++) = '\b'; - break; - case 'f': - *(t++) = '\f'; - break; - case 'n': - *(t++) = '\n'; - break; - case 'r': - *(t++) = '\r'; - break; - case 't': - *(t++) = '\t'; - break; - case 'v': - *(t++) = '\v'; - break; - case '\\': - *(t++) = '\\'; - break; - case '"': - *(t++) = '"'; - break; - case '\'': - *(t++) = '\''; - break; - - case 's': - /* This is an extension of the XDG syntax files */ - *(t++) = ' '; - break; - - case 'x': { - /* hexadecimal encoding */ - int a = -1, b = -1; - - if (remaining >= 2) { - a = unhexchar(f[1]); - b = unhexchar(f[2]); - } - - if (a < 0 || b < 0 || (a == 0 && b == 0)) { - /* Invalid escape code, let's take it literal then */ - *(t++) = '\\'; - *(t++) = 'x'; - } else { - *(t++) = (char) ((a << 4) | b); - f += 2; - } - - break; - } - - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': { - /* octal encoding */ - int a = -1, b = -1, c = -1; - - if (remaining >= 3) { - a = unoctchar(f[0]); - b = unoctchar(f[1]); - c = unoctchar(f[2]); - } - - if (a < 0 || b < 0 || c < 0 || (a == 0 && b == 0 && c == 0)) { - /* Invalid escape code, let's take it literal then */ - *(t++) = '\\'; - *(t++) = f[0]; - } else { - *(t++) = (char) ((a << 6) | (b << 3) | c); - f += 2; - } - - break; - } - - default: + k = cunescape_one(f + 1, remaining - 1, t); + if (k < 0) { /* Invalid escape code, let's take it literal then */ *(t++) = '\\'; - *(t++) = *f; - break; + continue; } + + f += k; + t++; } *t = 0; @@ -1685,6 +1723,7 @@ bool chars_intersect(const char *a, const char *b) { bool fstype_is_network(const char *fstype) { static const char table[] = + "afs\0" "cifs\0" "smbfs\0" "sshfs\0" @@ -2321,6 +2360,17 @@ ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) { return n; } +int loop_read_exact(int fd, void *buf, size_t nbytes, bool do_poll) { + ssize_t n; + + n = loop_read(fd, buf, nbytes, do_poll); + if (n < 0) + return n; + if ((size_t) n != nbytes) + return -EIO; + return 0; +} + int loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) { const uint8_t *p = buf; @@ -2575,8 +2625,9 @@ char* dirname_malloc(const char *path) { int dev_urandom(void *p, size_t n) { static int have_syscall = -1; - int r, fd; - ssize_t k; + + _cleanup_close_ int fd = -1; + int r; /* Gathers some randomness from the kernel. This call will * never block, and will always return some data from the @@ -2611,22 +2662,14 @@ int dev_urandom(void *p, size_t n) { return -errno; } else /* too short read? */ - return -EIO; + return -ENODATA; } fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY); if (fd < 0) return errno == ENOENT ? -ENOSYS : -errno; - k = loop_read(fd, p, n, true); - safe_close(fd); - - if (k < 0) - return (int) k; - if ((size_t) k != n) - return -EIO; - - return 0; + return loop_read_exact(fd, p, n, true); } void initialize_srand(void) { @@ -2839,7 +2882,7 @@ int getttyname_malloc(int fd, char **ret) { int getttyname_harder(int fd, char **r) { int k; - char *s; + char *s = NULL; k = getttyname_malloc(fd, &s); if (k < 0) @@ -2914,31 +2957,30 @@ int get_ctty(pid_t pid, dev_t *_devnr, char **r) { /* This is an ugly hack */ if (major(devnr) == 136) { - asprintf(&b, "pts/%u", minor(devnr)); - goto finish; - } + if (asprintf(&b, "pts/%u", minor(devnr)) < 0) + return -ENOMEM; + } else { + /* Probably something like the ptys which have no + * symlink in /dev/char. Let's return something + * vaguely useful. */ - /* Probably something like the ptys which have no - * symlink in /dev/char. Let's return something - * vaguely useful. */ + b = strdup(fn + 5); + if (!b) + return -ENOMEM; + } + } else { + if (startswith(s, "/dev/")) + p = s + 5; + else if (startswith(s, "../")) + p = s + 3; + else + p = s; - b = strdup(fn + 5); - goto finish; + b = strdup(p); + if (!b) + return -ENOMEM; } - if (startswith(s, "/dev/")) - p = s + 5; - else if (startswith(s, "../")) - p = s + 3; - else - p = s; - - b = strdup(p); - -finish: - if (!b) - return -ENOMEM; - *r = b; if (_devnr) *_devnr = devnr; @@ -3393,14 +3435,14 @@ char **replace_env_argv(char **argv, char **env) { /* If $FOO appears as single word, replace it by the split up variable */ if ((*i)[0] == '$' && (*i)[1] != '{') { char *e; - char **w, **m; + char **w, **m = NULL; unsigned q; e = strv_env_get(env, *i+1); if (e) { int r; - r = strv_split_quoted(&m, e, true); + r = strv_split_quoted(&m, e, UNQUOTE_RELAX); if (r < 0) { ret[k] = NULL; strv_free(ret); @@ -4110,8 +4152,7 @@ static int do_execute(char **directories, usec_t timeout, char *argv[]) { if (null_or_empty_path(path)) { log_debug("%s is empty (a mask).", path); continue; - } else - log_debug("%s will be executed.", path); + } pid = fork(); if (pid < 0) { @@ -5778,6 +5819,11 @@ void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size, return NULL; } +void init_gettext(void) { + setlocale(LC_ALL, ""); + textdomain(GETTEXT_PACKAGE); +} + bool is_locale_utf8(void) { const char *set; static int cached_answer = -1; @@ -5989,7 +6035,7 @@ int on_ac_power(void) { d = opendir("/sys/class/power_supply"); if (!d) - return -errno; + return errno == ENOENT ? true : -errno; for (;;) { struct dirent *de; @@ -6367,7 +6413,7 @@ int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value)) { _cleanup_free_ char *word = NULL; char *value = NULL; - r = unquote_first_word(&p, &word, true); + r = unquote_first_word(&p, &word, UNQUOTE_RELAX); if (r < 0) return r; if (r == 0) @@ -6407,7 +6453,7 @@ int get_proc_cmdline_key(const char *key, char **value) { _cleanup_free_ char *word = NULL; const char *e; - r = unquote_first_word(&p, &word, true); + r = unquote_first_word(&p, &word, UNQUOTE_RELAX); if (r < 0) return r; if (r == 0) @@ -6654,7 +6700,7 @@ int getpeersec(int fd, char **ret) { if (isempty(s)) { free(s); - return -ENOTSUP; + return -EOPNOTSUPP; } *ret = s; @@ -7260,9 +7306,10 @@ int is_dir(const char* path, bool follow) { return !!S_ISDIR(st.st_mode); } -int unquote_first_word(const char **p, char **ret, bool relax) { +int unquote_first_word(const char **p, char **ret, UnquoteFlags flags) { _cleanup_free_ char *s = NULL; size_t allocated = 0, sz = 0; + int r; enum { START, @@ -7320,7 +7367,7 @@ int unquote_first_word(const char **p, char **ret, bool relax) { case VALUE_ESCAPE: if (c == 0) { - if (relax) + if (flags & UNQUOTE_RELAX) goto finish; return -EINVAL; } @@ -7328,6 +7375,14 @@ int unquote_first_word(const char **p, char **ret, bool relax) { if (!GREEDY_REALLOC(s, allocated, sz+2)) return -ENOMEM; + if (flags & UNQUOTE_CUNESCAPE) { + r = cunescape_one(*p, (size_t) -1, &c); + if (r < 0) + return -EINVAL; + + (*p) += r - 1; + } + s[sz++] = c; state = VALUE; @@ -7335,7 +7390,7 @@ int unquote_first_word(const char **p, char **ret, bool relax) { case SINGLE_QUOTE: if (c == 0) { - if (relax) + if (flags & UNQUOTE_RELAX) goto finish; return -EINVAL; } else if (c == '\'') @@ -7353,7 +7408,7 @@ int unquote_first_word(const char **p, char **ret, bool relax) { case SINGLE_QUOTE_ESCAPE: if (c == 0) { - if (relax) + if (flags & UNQUOTE_RELAX) goto finish; return -EINVAL; } @@ -7361,6 +7416,14 @@ int unquote_first_word(const char **p, char **ret, bool relax) { if (!GREEDY_REALLOC(s, allocated, sz+2)) return -ENOMEM; + if (flags & UNQUOTE_CUNESCAPE) { + r = cunescape_one(*p, (size_t) -1, &c); + if (r < 0) + return -EINVAL; + + (*p) += r - 1; + } + s[sz++] = c; state = SINGLE_QUOTE; break; @@ -7383,7 +7446,7 @@ int unquote_first_word(const char **p, char **ret, bool relax) { case DOUBLE_QUOTE_ESCAPE: if (c == 0) { - if (relax) + if (flags & UNQUOTE_RELAX) goto finish; return -EINVAL; } @@ -7391,6 +7454,14 @@ int unquote_first_word(const char **p, char **ret, bool relax) { if (!GREEDY_REALLOC(s, allocated, sz+2)) return -ENOMEM; + if (flags & UNQUOTE_CUNESCAPE) { + r = cunescape_one(*p, (size_t) -1, &c); + if (r < 0) + return -EINVAL; + + (*p) += r - 1; + } + s[sz++] = c; state = DOUBLE_QUOTE; break; @@ -7420,7 +7491,7 @@ finish: return 1; } -int unquote_many_words(const char **p, ...) { +int unquote_many_words(const char **p, UnquoteFlags flags, ...) { va_list ap; char **l; int n = 0, i, c, r; @@ -7431,7 +7502,7 @@ int unquote_many_words(const char **p, ...) { assert(p); /* Count how many words are expected */ - va_start(ap, p); + va_start(ap, flags); for (;;) { if (!va_arg(ap, char **)) break; @@ -7446,7 +7517,7 @@ int unquote_many_words(const char **p, ...) { l = newa0(char*, n); for (c = 0; c < n; c++) { - r = unquote_first_word(p, &l[c], false); + r = unquote_first_word(p, &l[c], flags); if (r < 0) { int j; @@ -7462,7 +7533,7 @@ int unquote_many_words(const char **p, ...) { /* If we managed to parse all words, return them in the passed * in parameters */ - va_start(ap, p); + va_start(ap, flags); for (i = 0; i < n; i++) { char **v; @@ -7723,71 +7794,6 @@ 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 - * distuingish 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, bool b, unsigned mask) { unsigned old_attr, new_attr; @@ -7828,6 +7834,28 @@ int chattr_path(const char *p, bool b, unsigned mask) { return chattr_fd(fd, b, mask); } +int change_attr_fd(int fd, unsigned value, unsigned mask) { + unsigned old_attr, new_attr; + + assert(fd >= 0); + + if (mask == 0) + return 0; + + if (ioctl(fd, FS_IOC_GETFLAGS, &old_attr) < 0) + return -errno; + + new_attr = (old_attr & ~mask) |(value & mask); + + if (new_attr == old_attr) + return 0; + + if (ioctl(fd, FS_IOC_SETFLAGS, &new_attr) < 0) + return -errno; + + return 0; +} + int read_attr_fd(int fd, unsigned *ret) { assert(fd >= 0); @@ -8085,3 +8113,67 @@ int syslog_parse_priority(const char **p, int *priority, bool with_facility) { *p += k; return 1; } + +ssize_t string_table_lookup(const char * const *table, size_t len, const char *key) { + size_t i; + + if (!key) + return -1; + + for (i = 0; i < len; ++i) + if (streq_ptr(table[i], key)) + return (ssize_t)i; + + return -1; +} + +void cmsg_close_all(struct msghdr *mh) { + struct cmsghdr *cmsg; + + assert(mh); + + for (cmsg = CMSG_FIRSTHDR(mh); cmsg; cmsg = CMSG_NXTHDR(mh, cmsg)) + 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)); +} + +int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) { + struct stat buf; + int ret; + + ret = renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE); + if (ret >= 0) + return 0; + + /* Even though renameat2() exists since Linux 3.15, btrfs added + * support for it later. If it is not implemented, fallback to another + * method. */ + if (errno != EINVAL) + return -errno; + + /* The link()/unlink() fallback does not work on directories. But + * renameat() without RENAME_NOREPLACE gives the same semantics on + * directories, except when newpath is an *empty* directory. This is + * good enough. */ + ret = fstatat(olddirfd, oldpath, &buf, AT_SYMLINK_NOFOLLOW); + if (ret >= 0 && S_ISDIR(buf.st_mode)) { + ret = renameat(olddirfd, oldpath, newdirfd, newpath); + return ret >= 0 ? 0 : -errno; + } + + /* If it is not a directory, use the link()/unlink() fallback. */ + ret = linkat(olddirfd, oldpath, newdirfd, newpath, 0); + if (ret < 0) + return -errno; + + ret = unlinkat(olddirfd, oldpath, 0); + if (ret < 0) { + /* backup errno before the following unlinkat() alters it */ + ret = errno; + (void) unlinkat(newdirfd, newpath, 0); + errno = ret; + return -errno; + } + + return 0; +}