X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fshared%2Futil.c;h=5cbbe8fb7d2886ffbbe36fab7cde07aba50fbe2c;hp=857bb1b72682b93f07fdd2c5593a2d7fa8379477;hb=a88c8750b3f0618036782b31a184c27c80bcb38d;hpb=01b725684f6ba54f0db815669e4e07eb0e02fedb diff --git a/src/shared/util.c b/src/shared/util.c index 857bb1b72..5cbbe8fb7 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,22 +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" @@ -89,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; @@ -506,18 +514,24 @@ int safe_atolli(const char *s, long long int *ret_lli) { int safe_atod(const char *s, double *ret_d) { char *x = NULL; double d = 0; + locale_t loc; assert(s); assert(ret_d); - RUN_WITH_LOCALE(LC_NUMERIC_MASK, "C") { - errno = 0; - d = strtod(s, &x); - } + loc = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0); + if (loc == (locale_t) 0) + return -errno; - if (!x || x == s || *x || errno) + errno = 0; + d = strtod_l(s, &x, loc); + + if (!x || x == s || *x || errno) { + freelocale(loc); return errno ? -errno : -EINVAL; + } + freelocale(loc); *ret_d = (double) d; return 0; } @@ -1532,6 +1546,10 @@ _pure_ static bool hidden_file_allow_backup(const char *filename) { endswith(filename, ".dpkg-old") || endswith(filename, ".dpkg-new") || endswith(filename, ".dpkg-tmp") || + endswith(filename, ".dpkg-dist") || + endswith(filename, ".dpkg-bak") || + endswith(filename, ".dpkg-backup") || + endswith(filename, ".dpkg-remove") || endswith(filename, ".swp"); } @@ -1674,6 +1692,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" @@ -2310,6 +2329,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; @@ -2564,8 +2594,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 @@ -2600,22 +2631,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) { @@ -2903,31 +2926,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; @@ -4099,8 +4121,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) { @@ -4237,6 +4258,11 @@ bool hostname_is_valid(const char *s) { 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) @@ -4303,23 +4329,6 @@ 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, @@ -5468,25 +5477,56 @@ int getenv_for_pid(pid_t pid, const char *field, char **_value) { return r; } -bool is_valid_documentation_url(const char *url) { - assert(url); +bool http_etag_is_valid(const char *etag) { + if (isempty(etag)) + return false; - if (startswith(url, "http://") && url[7]) - return true; + if (!endswith(etag, "\"")) + return false; - if (startswith(url, "https://") && url[8]) - return true; + if (!startswith(etag, "\"") && !startswith(etag, "W/\"")) + return false; - if (startswith(url, "file:") && url[5]) - return true; + return true; +} - if (startswith(url, "info:") && url[5]) - return true; +bool http_url_is_valid(const char *url) { + const char *p; + + if (isempty(url)) + return false; + + p = startswith(url, "http://"); + if (!p) + p = startswith(url, "https://"); + if (!p) + return false; + + if (isempty(p)) + return false; + + return ascii_is_valid(p); +} + +bool documentation_url_is_valid(const char *url) { + const char *p; - if (startswith(url, "man:") && url[4]) + if (isempty(url)) + return false; + + if (http_url_is_valid(url)) return true; - return false; + p = startswith(url, "file:/"); + if (!p) + p = startswith(url, "info:"); + if (!p) + p = startswith(url, "man:"); + + if (isempty(p)) + return false; + + return ascii_is_valid(p); } bool in_initrd(void) { @@ -5748,6 +5788,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; @@ -5959,7 +6004,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; @@ -6422,7 +6467,7 @@ int container_get_leader(const char *machine, pid_t *pid) { assert(machine); assert(pid); - p = strappenda("/run/systemd/machines/", machine); + p = strjoina("/run/systemd/machines/", machine); r = parse_env_file(p, NEWLINE, "LEADER", &s, "CLASS", &class, NULL); if (r == -ENOENT) return -EHOSTDOWN; @@ -6624,7 +6669,7 @@ int getpeersec(int fd, char **ret) { if (isempty(s)) { free(s); - return -ENOTSUP; + return -EOPNOTSUPP; } *ret = s; @@ -6661,7 +6706,7 @@ int open_tmpfile(const char *path, int flags) { #endif /* Fall back to unguessable name + unlinking */ - p = strappenda(path, "/systemd-tmp-XXXXXX"); + p = strjoina(path, "/systemd-tmp-XXXXXX"); fd = mkostemp_safe(p, flags); if (fd < 0) @@ -7190,7 +7235,7 @@ int take_password_lock(const char *root) { * awfully racy, and thus we just won't do them. */ if (root) - path = strappenda(root, "/etc/.pwd.lock"); + path = strjoina(root, "/etc/.pwd.lock"); else path = "/etc/.pwd.lock"; @@ -7819,3 +7864,303 @@ 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; + + while (sz > 0) { + if (*p != 0) + break; + + n++; + p++; + sz--; + } + + return n; +} + +ssize_t sparse_write(int fd, const void *p, size_t sz, size_t run_length) { + const uint8_t *q, *w, *e; + ssize_t l; + + q = w = p; + e = q + sz; + while (q < e) { + size_t n; + + n = nul_length(q, e - q); + + /* If there are more than the specified run length of + * NUL bytes, or if this is the beginning or the end + * of the buffer, then seek instead of write */ + if ((n > run_length) || + (n > 0 && q == p) || + (n > 0 && q + n >= e)) { + if (q > w) { + l = write(fd, w, q - w); + if (l < 0) + return -errno; + if (l != q -w) + return -EIO; + } + + if (lseek(fd, n, SEEK_CUR) == (off_t) -1) + return -errno; + + q += n; + w = q; + } else if (n > 0) + q += n; + else + q ++; + } + + if (q > w) { + l = write(fd, w, q - w); + if (l < 0) + return -errno; + if (l != q - w) + return -EIO; + } + + return q - (const uint8_t*) p; +} + +void sigkill_wait(pid_t *pid) { + if (!pid) + return; + if (*pid <= 1) + return; + + if (kill(*pid, SIGKILL) > 0) + (void) wait_for_terminate(*pid, NULL); +} + +int syslog_parse_priority(const char **p, int *priority, bool with_facility) { + int a = 0, b = 0, c = 0; + int k; + + assert(p); + assert(*p); + assert(priority); + + if ((*p)[0] != '<') + return 0; + + if (!strchr(*p, '>')) + return 0; + + 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 0; + + if (a < 0 || b < 0 || c < 0 || + (!with_facility && (a || b || c > 7))) + return 0; + + if (with_facility) + *priority = a*100 + b*10 + c; + else + *priority = (*priority & LOG_FACMASK) | c; + + *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; +}