X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fshared%2Futil.c;h=ef3b67b597594597e0c005fa628565158b493076;hp=5d03272619a0831dea4acb2c4a302dc3bdca69a3;hb=daeb71a36a98834664e4d95773a3629b746f4db8;hpb=4826f0b7b5c0aefa08b8cc7ef64d69027f84da2c diff --git a/src/shared/util.c b/src/shared/util.c index 5d0327261..ef3b67b59 100644 --- a/src/shared/util.c +++ b/src/shared/util.c @@ -73,6 +73,10 @@ #include "hashmap.h" #include "env-util.h" #include "fileio.h" +#include "device-nodes.h" +#include "utf8.h" +#include "gunicode.h" +#include "virt.h" int saved_argc = 0; char **saved_argv = NULL; @@ -80,15 +84,6 @@ char **saved_argv = NULL; static volatile unsigned cached_columns = 0; static volatile unsigned cached_lines = 0; -#define procfs_file_alloca(pid, field) \ - ({ \ - pid_t _pid_ = (pid); \ - char *_r_; \ - _r_ = alloca(sizeof("/proc/") -1 + DECIMAL_STR_MAX(pid_t) + 1 + sizeof(field)); \ - sprintf(_r_, "/proc/%lu/" field, (unsigned long) _pid_); \ - _r_; \ - }) - size_t page_size(void) { static __thread size_t pgsz = 0; long r; @@ -137,40 +132,6 @@ char* endswith(const char *s, const char *postfix) { return (char*) s + sl - pl; } -char* startswith(const char *s, const char *prefix) { - const char *a, *b; - - assert(s); - assert(prefix); - - a = s, b = prefix; - for (;;) { - if (*b == 0) - return (char*) a; - if (*a != *b) - return NULL; - - a++, b++; - } -} - -char* startswith_no_case(const char *s, const char *prefix) { - const char *a, *b; - - assert(s); - assert(prefix); - - a = s, b = prefix; - for (;;) { - if (*b == 0) - return (char*) a; - if (tolower(*a) != tolower(*b)) - return NULL; - - a++, b++; - } -} - bool first_word(const char *s, const char *word) { size_t sl, wl; @@ -376,13 +337,15 @@ 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; + double d = 0; assert(s); assert(ret_d); - errno = 0; - d = strtod(s, &x); + RUN_WITH_LOCALE(LC_NUMERIC_MASK, "C") { + errno = 0; + d = strtod(s, &x); + } if (!x || x == s || *x || errno) return errno ? -errno : -EINVAL; @@ -681,7 +644,7 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char * /* Kernel threads have no argv[] */ if (r == NULL || r[0] == 0) { - char *t; + _cleanup_free_ char *t = NULL; int h; free(r); @@ -694,8 +657,6 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char * return h; r = strjoin("[", t, "]", NULL); - free(t); - if (!r) return -ENOMEM; } @@ -733,9 +694,24 @@ int is_kernel_thread(pid_t pid) { return 0; } +int get_process_capeff(pid_t pid, char **capeff) { + const char *p; + + assert(capeff); + assert(pid >= 0); + + if (pid == 0) + p = "/proc/self/status"; + else + p = procfs_file_alloca(pid, "status"); + + return get_status_field(p, "\nCapEff:", capeff); +} int get_process_exe(pid_t pid, char **name) { const char *p; + char *d; + int r; assert(pid >= 0); assert(name); @@ -745,7 +721,15 @@ int get_process_exe(pid_t pid, char **name) { else p = procfs_file_alloca(pid, "exe"); - return readlink_malloc(p, name); + r = readlink_malloc(p, name); + if (r < 0) + return r; + + d = endswith(*name, " (deleted)"); + if (d) + *d = '\0'; + + return 0; } static int get_process_id(pid_t pid, const char *field, uid_t *uid) { @@ -857,18 +841,18 @@ int readlink_malloc(const char *p, char **r) { } int readlink_and_make_absolute(const char *p, char **r) { - char *target, *k; + _cleanup_free_ char *target = NULL; + char *k; int j; assert(p); assert(r); - if ((j = readlink_malloc(p, &target)) < 0) + j = readlink_malloc(p, &target); + if (j < 0) return j; k = file_in_same_dir(p, target); - free(target); - if (!k) return -ENOMEM; @@ -1379,7 +1363,7 @@ char *bus_path_escape(const char *s) { assert(s); /* Escapes all chars that D-Bus' object path cannot deal - * with. Can be reverse with bus_path_unescape(). We special + * with. Can be reversed with bus_path_unescape(). We special * case the empty string. */ if (*s == 0) @@ -1456,7 +1440,7 @@ char *ascii_strlower(char *t) { return t; } -static bool ignore_file_allow_backup(const char *filename) { +_pure_ static bool ignore_file_allow_backup(const char *filename) { assert(filename); return @@ -1519,7 +1503,7 @@ int fd_cloexec(int fd, bool cloexec) { return 0; } -static bool fd_in_set(int fd, const int fdset[], unsigned n_fdset) { +_pure_ static bool fd_in_set(int fd, const int fdset[], unsigned n_fdset) { unsigned i; assert(n_fdset == 0 || fdset); @@ -1607,6 +1591,7 @@ bool fstype_is_network(const char *fstype) { "cifs\0" "smbfs\0" "ncpfs\0" + "ncp\0" "nfs\0" "nfs4\0" "gfs\0" @@ -1837,8 +1822,10 @@ int open_terminal(const char *name, int mode) { * https://bugs.launchpad.net/ubuntu/+source/linux/+bug/554172/comments/245 */ + assert(!(mode & O_CREAT)); + for (;;) { - fd = open(name, mode); + fd = open(name, mode, 0); if (fd >= 0) break; @@ -2200,8 +2187,10 @@ ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) { return n > 0 ? n : -errno; } - if (pollfd.revents != POLLIN) - return n > 0 ? n : -EIO; + /* We knowingly ignore the revents value here, + * and expect that any error/EOF is reported + * via read()/write() + */ continue; } @@ -2248,8 +2237,10 @@ ssize_t loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) { return n > 0 ? n : -errno; } - if (pollfd.revents != POLLOUT) - return n > 0 ? n : -EIO; + /* We knowingly ignore the revents value here, + * and expect that any error/EOF is reported + * via read()/write() + */ continue; } @@ -2268,7 +2259,7 @@ ssize_t loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) { int parse_bytes(const char *t, off_t *bytes) { static const struct { const char *suffix; - off_t factor; + unsigned long long factor; } table[] = { { "B", 1 }, { "K", 1024ULL }, @@ -2281,7 +2272,7 @@ int parse_bytes(const char *t, off_t *bytes) { }; const char *p; - off_t r = 0; + unsigned long long r = 0; assert(t); assert(bytes); @@ -2308,7 +2299,17 @@ int parse_bytes(const char *t, off_t *bytes) { for (i = 0; i < ELEMENTSOF(table); i++) if (startswith(e, table[i].suffix)) { - r += (off_t) l * table[i].factor; + unsigned long long tmp; + if ((unsigned long long) l > ULLONG_MAX / table[i].factor) + return -ERANGE; + tmp = l * table[i].factor; + if (tmp > ULLONG_MAX - r) + return -ERANGE; + + r += tmp; + if ((unsigned long long) (off_t) r != r) + return -ERANGE; + p = e + strlen(table[i].suffix); break; } @@ -2316,7 +2317,7 @@ int parse_bytes(const char *t, off_t *bytes) { if (i >= ELEMENTSOF(table)) return -EINVAL; - } while (*p != 0); + } while (*p); *bytes = r; @@ -2424,6 +2425,25 @@ fallback: return random() * RAND_MAX + random(); } +unsigned random_u(void) { + _cleanup_close_ int fd; + unsigned u; + ssize_t r; + + fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + goto fallback; + + r = loop_read(fd, &u, sizeof(u), true); + if (r != sizeof(u)) + goto fallback; + + return u; + +fallback: + return random() * RAND_MAX + random(); +} + void rename_process(const char name[8]) { assert(name); @@ -2777,10 +2797,11 @@ int rm_rf_children_dangerous(int fd, bool only_dirs, bool honour_sticky, struct return ret; } -static int is_temporary_fs(struct statfs *s) { +_pure_ static int is_temporary_fs(struct statfs *s) { assert(s); - return s->f_type == (__SWORD_TYPE) TMPFS_MAGIC || - s->f_type == (__SWORD_TYPE) RAMFS_MAGIC; + return + F_TYPE_EQUAL(s->f_type, TMPFS_MAGIC) || + F_TYPE_EQUAL(s->f_type, RAMFS_MAGIC); } int rm_rf_children(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev) { @@ -3268,7 +3289,7 @@ int running_in_chroot(void) { a.st_ino != b.st_ino; } -char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) { +static char *ascii_ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) { size_t x; char *r; @@ -3281,7 +3302,7 @@ char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigne r = new0(char, new_length+1); if (!r) - return r; + return NULL; x = (new_length * percent) / 100; @@ -3299,6 +3320,80 @@ char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigne return r; } +char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) { + size_t x; + char *e; + const char *i, *j; + unsigned k, len, len2; + + assert(s); + assert(percent <= 100); + assert(new_length >= 3); + + /* if no multibyte characters use ascii_ellipsize_mem for speed */ + if (ascii_is_valid(s)) + return ascii_ellipsize_mem(s, old_length, new_length, percent); + + if (old_length <= 3 || old_length <= new_length) + return strndup(s, old_length); + + x = (new_length * percent) / 100; + + if (x > new_length - 3) + x = new_length - 3; + + k = 0; + for (i = s; k < x && i < s + old_length; i = utf8_next_char(i)) { + int c; + + c = utf8_encoded_to_unichar(i); + if (c < 0) + return NULL; + k += unichar_iswide(c) ? 2 : 1; + } + + if (k > x) /* last character was wide and went over quota */ + x ++; + + for (j = s + old_length; k < new_length && j > i; ) { + int c; + + j = utf8_prev_char(j); + c = utf8_encoded_to_unichar(j); + if (c < 0) + return NULL; + k += unichar_iswide(c) ? 2 : 1; + } + assert(i <= j); + + /* we don't actually need to ellipsize */ + if (i == j) + return memdup(s, old_length + 1); + + /* make space for ellipsis */ + j = utf8_next_char(j); + + len = i - s; + len2 = s + old_length - j; + e = new(char, len + 3 + len2 + 1); + if (!e) + return NULL; + + /* + printf("old_length=%zu new_length=%zu x=%zu len=%u len2=%u k=%u\n", + old_length, new_length, x, len, len2, k); + */ + + memcpy(e, s, len); + e[len] = 0xe2; /* tri-dot ellipsis: … */ + e[len + 1] = 0x80; + e[len + 2] = 0xa6; + + memcpy(e + len + 3, j, len2 + 1); + + return e; +} + char *ellipsize(const char *s, size_t length, unsigned percent) { return ellipsize_mem(s, strlen(s), length, percent); } @@ -3471,7 +3566,9 @@ DIR *xopendirat(int fd, const char *name, int flags) { int nfd; DIR *d; - nfd = openat(fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|flags); + assert(!(flags & O_CREAT)); + + nfd = openat(fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|flags, 0); if (nfd < 0) return NULL; @@ -3497,26 +3594,23 @@ int signal_from_string_try_harder(const char *s) { } static char *tag_to_udev_node(const char *tagvalue, const char *by) { - char *dn, *t, *u; - int r; - - /* FIXME: to follow udev's logic 100% we need to leave valid - * UTF8 chars unescaped */ + _cleanup_free_ char *t = NULL, *u = NULL; + char *dn; + size_t enc_len; u = unquote(tagvalue, "\"\'"); if (u == NULL) return NULL; - t = xescape(u, "/ "); - free(u); - + enc_len = strlen(u) * 4 + 1; + t = new(char, enc_len); if (t == NULL) return NULL; - r = asprintf(&dn, "/dev/disk/by-%s/%s", by, t); - free(t); + if (encode_devnode_name(u, t, enc_len) < 0) + return NULL; - if (r < 0) + if (asprintf(&dn, "/dev/disk/by-%s/%s", by, t) < 0) return NULL; return dn; @@ -3844,24 +3938,29 @@ bool hostname_is_valid(const char *s) { return true; } -char* hostname_cleanup(char *s) { +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 || p[1] == 0) + if (dot) continue; + *(d++) = '.'; dot = true; - } else + } else if (hostname_valid_char(*p)) { + *(d++) = lowercase ? tolower(*p) : *p; dot = false; + } - if (hostname_valid_char(*p)) - *(d++) = *p; } - *d = 0; + if (dot && d > s) + d[-1] = 0; + else + *d = 0; + strshorten(s, HOST_NAME_MAX); return s; @@ -4032,8 +4131,9 @@ int vt_disallocate(const char *name) { return 0; } -int copy_file(const char *from, const char *to) { - int r, fdf, fdt; +int copy_file(const char *from, const char *to, int flags) { + _cleanup_close_ int fdf = -1; + int r, fdt; assert(from); assert(to); @@ -4042,11 +4142,9 @@ int copy_file(const char *from, const char *to) { if (fdf < 0) return -errno; - fdt = open(to, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY, 0644); - if (fdt < 0) { - close_nointr_nofail(fdf); + fdt = open(to, flags|O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644); + if (fdt < 0) return -errno; - } for (;;) { char buf[PIPE_BUF]; @@ -4056,7 +4154,6 @@ int copy_file(const char *from, const char *to) { if (n < 0) { r = -errno; - close_nointr_nofail(fdf); close_nointr(fdt); unlink(to); @@ -4071,15 +4168,13 @@ int copy_file(const char *from, const char *to) { if (n != k) { r = k < 0 ? k : (errno ? -errno : -EIO); - close_nointr_nofail(fdf); close_nointr(fdt); - unlink(to); + return r; } } - close_nointr_nofail(fdf); r = close_nointr(fdt); if (r < 0) { @@ -4344,7 +4439,7 @@ int in_group(const char *name) { int glob_exists(const char *path) { _cleanup_globfree_ glob_t g = {}; - int r, k; + int k; assert(path); @@ -4352,15 +4447,37 @@ int glob_exists(const char *path) { k = glob(path, GLOB_NOSORT|GLOB_BRACE, NULL, &g); if (k == GLOB_NOMATCH) - r = 0; + return 0; else if (k == GLOB_NOSPACE) - r = -ENOMEM; + return -ENOMEM; else if (k == 0) - r = !strv_isempty(g.gl_pathv); + return !strv_isempty(g.gl_pathv); else - r = errno ? -errno : -EIO; + return errno ? -errno : -EIO; +} - return r; +int glob_extend(char ***strv, const char *path) { + _cleanup_globfree_ glob_t g = {}; + int k; + char **p; + + errno = 0; + k = glob(path, GLOB_NOSORT|GLOB_BRACE, NULL, &g); + + if (k == GLOB_NOMATCH) + return -ENOENT; + else if (k == GLOB_NOSPACE) + return -ENOMEM; + else if (k != 0 || strv_isempty(g.gl_pathv)) + return errno ? -errno : -EIO; + + STRV_FOREACH(p, g.gl_pathv) { + k = strv_extend(strv, *p); + if (k < 0) + break; + } + + return k; } int dirent_ensure_type(DIR *d, struct dirent *de) { @@ -4389,38 +4506,31 @@ int dirent_ensure_type(DIR *d, struct dirent *de) { } int in_search_path(const char *path, char **search) { - char **i, *parent; + char **i; + _cleanup_free_ char *parent = NULL; int r; r = path_get_parent(path, &parent); if (r < 0) return r; - r = 0; - - STRV_FOREACH(i, search) { - if (path_equal(parent, *i)) { - r = 1; - break; - } - } - - free(parent); + STRV_FOREACH(i, search) + if (path_equal(parent, *i)) + return 1; - return r; + return 0; } int get_files_in_directory(const char *path, char ***list) { - DIR *d; - int r = 0; - unsigned n = 0; - char **l = NULL; + _cleanup_closedir_ DIR *d = NULL; + size_t bufsize = 0, n = 0; + _cleanup_strv_free_ char **l = NULL; assert(path); /* Returns all files in a directory in *list, and the number * of files as return value. If list is NULL returns only the - * number */ + * number. */ d = opendir(path); if (!d) @@ -4432,11 +4542,9 @@ int get_files_in_directory(const char *path, char ***list) { int k; k = readdir_r(d, &buf.de, &de); - if (k != 0) { - r = -k; - goto finish; - } - + assert(k >= 0); + if (k > 0) + return -k; if (!de) break; @@ -4446,43 +4554,25 @@ int get_files_in_directory(const char *path, char ***list) { continue; if (list) { - if ((unsigned) r >= n) { - char **t; - - n = MAX(16, 2*r); - t = realloc(l, sizeof(char*) * n); - if (!t) { - r = -ENOMEM; - goto finish; - } - - l = t; - } - - assert((unsigned) r < n); + /* one extra slot is needed for the terminating NULL */ + if (!GREEDY_REALLOC(l, bufsize, n + 2)) + return -ENOMEM; - l[r] = strdup(de->d_name); - if (!l[r]) { - r = -ENOMEM; - goto finish; - } + l[n] = strdup(de->d_name); + if (!l[n]) + return -ENOMEM; - l[++r] = NULL; + l[++n] = NULL; } else - r++; + n++; } -finish: - if (d) - closedir(d); - - if (r >= 0) { - if (list) - *list = l; - } else - strv_free(l); + if (list) { + *list = l; + l = NULL; /* avoid freeing */ + } - return r; + return n; } char *strjoin(const char *x, ...) { @@ -5096,59 +5186,6 @@ int getenv_for_pid(pid_t pid, const char *field, char **_value) { return r; } -int can_sleep(const char *type) { - char *w, *state; - size_t l, k; - int r; - _cleanup_free_ char *p = NULL; - - assert(type); - - /* If /sys is read-only we cannot sleep */ - if (access("/sys/power/state", W_OK) < 0) - return false; - - r = read_one_line_file("/sys/power/state", &p); - if (r < 0) - return false; - - k = strlen(type); - FOREACH_WORD_SEPARATOR(w, l, p, WHITESPACE, state) - if (l == k && memcmp(w, type, l) == 0) - return true; - - return false; -} - -int can_sleep_disk(const char *type) { - char *w, *state; - size_t l, k; - int r; - _cleanup_free_ char *p = NULL; - - assert(type); - - /* If /sys is read-only we cannot sleep */ - if (access("/sys/power/state", W_OK) < 0 || - access("/sys/power/disk", W_OK) < 0) - return false; - - r = read_one_line_file("/sys/power/disk", &p); - if (r < 0) - return false; - - k = strlen(type); - FOREACH_WORD_SEPARATOR(w, l, p, WHITESPACE, state) { - if (l == k && memcmp(w, type, l) == 0) - return true; - - if (l == k + 2 && w[0] == '[' && memcmp(w + 1, type, l - 2) == 0 && w[l-1] == ']') - return true; - } - - return false; -} - bool is_valid_documentation_url(const char *url) { assert(url); @@ -5318,13 +5355,17 @@ bool string_is_safe(const char *p) { return true; } +/** + * Check if a string contains control characters. + * Spaces and tabs are not considered control characters. + */ bool string_has_cc(const char *p) { const char *t; assert(p); for (t = p; *t; t++) - if (*t > 0 && *t < ' ') + if (*t > 0 && *t < ' ' && *t != '\t') return true; return false; @@ -5397,20 +5438,24 @@ bool is_locale_utf8(void) { goto out; } - /* For LC_CTYPE=="C" return true, - * because CTYPE is effectly unset and - * everything defaults to UTF-8 nowadays. */ - + /* For LC_CTYPE=="C" return true, because CTYPE is effectly + * unset and everything can do to UTF-8 nowadays. */ set = setlocale(LC_CTYPE, NULL); if (!set) { cached_answer = true; goto out; } - cached_answer = streq(set, "C"); + /* Check result, but ignore the result if C was set + * explicitly. */ + cached_answer = + streq(set, "C") && + !getenv("LC_ALL") && + !getenv("LC_CTYPE") && + !getenv("LANG"); out: - return (bool)cached_answer; + return (bool) cached_answer; } const char *draw_special_char(DrawSpecialChar ch) { @@ -5725,7 +5770,7 @@ int search_and_fopen_nulstr(const char *path, const char *mode, const char *sear int create_tmp_dir(char template[], char** dir_name) { int r = 0; - char *d, *dt; + char *d = NULL, *dt; assert(dir_name); @@ -5860,3 +5905,111 @@ void* greedy_realloc(void **p, size_t *allocated, size_t need) { *allocated = a; return q; } + +bool id128_is_valid(const char *s) { + size_t i, l; + + l = strlen(s); + if (l == 32) { + + /* Simple formatted 128bit hex string */ + + for (i = 0; i < l; i++) { + char c = s[i]; + + if (!(c >= '0' && c <= '9') && + !(c >= 'a' && c <= 'z') && + !(c >= 'A' && c <= 'Z')) + return false; + } + + } else if (l == 36) { + + /* Formatted UUID */ + + for (i = 0; i < l; i++) { + char c = s[i]; + + if ((i == 8 || i == 13 || i == 18 || i == 23)) { + if (c != '-') + return false; + } else { + if (!(c >= '0' && c <= '9') && + !(c >= 'a' && c <= 'z') && + !(c >= 'A' && c <= 'Z')) + return false; + } + } + + } else + return false; + + return true; +} + +void parse_user_at_host(char *arg, char **user, char **host) { + assert(arg); + assert(user); + assert(host); + + *host = strchr(arg, '@'); + if (*host == NULL) + *host = arg; + else { + *host[0]++ = '\0'; + *user = arg; + } +} + +int split_pair(const char *s, const char *sep, char **l, char **r) { + char *x, *a, *b; + + assert(s); + assert(sep); + assert(l); + assert(r); + + if (isempty(sep)) + return -EINVAL; + + x = strstr(s, sep); + if (!x) + return -EINVAL; + + a = strndup(s, x - s); + if (!a) + return -ENOMEM; + + b = strdup(x + strlen(sep)); + if (!b) { + free(a); + return -ENOMEM; + } + + *l = a; + *r = b; + + return 0; +} + +bool restore_state(void) { + _cleanup_free_ char *line; + char *w, *state; + int r; + size_t l; + + if (detect_container(NULL) > 0) + return true; + + r = read_one_line_file("/proc/cmdline", &line); + if (r < 0) { + log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r)); + return true; /* something is very wrong, let's not make it worse */ + } + + FOREACH_WORD_QUOTED(w, l, line, state) + if (strneq(w, "systemd.restore_state=0", l)) + return false; + + return true; +}