diff options
Diffstat (limited to 'shared/util.c')
-rw-r--r-- | shared/util.c | 548 |
1 files changed, 548 insertions, 0 deletions
diff --git a/shared/util.c b/shared/util.c new file mode 100644 index 0000000..e2bab83 --- /dev/null +++ b/shared/util.c @@ -0,0 +1,548 @@ +/* + * kmod - interface to kernel module operations + * + * Copyright (C) 2011-2013 ProFUSION embedded systems + * Copyright (C) 2012 Lucas De Marchi <lucas.de.marchi@gmail.com> + * Copyright (C) 2013-2014 Intel Corporation. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <shared/missing.h> +#include <shared/util.h> + +#define USEC_PER_SEC 1000000ULL +#define NSEC_PER_USEC 1000ULL + +static const struct kmod_ext { + const char *ext; + size_t len; +} kmod_exts[] = { + {KMOD_EXTENSION_UNCOMPRESSED, sizeof(KMOD_EXTENSION_UNCOMPRESSED) - 1}, +#ifdef ENABLE_ZLIB + {".ko.gz", sizeof(".ko.gz") - 1}, +#endif +#ifdef ENABLE_XZ + {".ko.xz", sizeof(".ko.xz") - 1}, +#endif +#ifdef ENABLE_ZSTD + {".ko.zst", sizeof(".ko.zst") - 1}, +#endif + { } +}; + +/* string handling functions and memory allocations */ +/* ************************************************************************ */ + +void *memdup(const void *p, size_t n) +{ + void *r = malloc(n); + + if (r == NULL) + return NULL; + + return memcpy(r, p, n); +} + +char *strchr_replace(char *s, char c, char r) +{ + char *p; + + for (p = s; *p != '\0'; p++) { + if (*p == c) + *p = r; + } + + return s; +} + +/* module-related functions */ +/* ************************************************************************ */ +int alias_normalize(const char *alias, char buf[static PATH_MAX], size_t *len) +{ + size_t i; + + for (i = 0; i < PATH_MAX - 1; i++) { + const char c = alias[i]; + switch (c) { + case '-': + buf[i] = '_'; + break; + case ']': + return -EINVAL; + case '[': + while (alias[i] != ']' && alias[i] != '\0') { + buf[i] = alias[i]; + i++; + } + + if (alias[i] != ']') + return -EINVAL; + + buf[i] = alias[i]; + break; + case '\0': + goto finish; + default: + buf[i] = c; + } + } + +finish: + buf[i] = '\0'; + if (len) + *len = i; + + return 0; +} + +/* + * Replace dashes with underscores. + * Dashes inside character range patterns (e.g. [0-9]) are left unchanged. + * + * For convenience, it returns error if @s is NULL + */ +int underscores(char *s) +{ + unsigned int i; + + if (!s) + return -EINVAL; + + for (i = 0; s[i]; i++) { + switch (s[i]) { + case '-': + s[i] = '_'; + break; + case ']': + return -EINVAL; + case '[': + i += strcspn(&s[i], "]"); + if (!s[i]) + return -EINVAL; + break; + } + } + + return 0; +} + +char *modname_normalize(const char *modname, char buf[static PATH_MAX], size_t *len) +{ + size_t s; + + for (s = 0; s < PATH_MAX - 1; s++) { + const char c = modname[s]; + if (c == '-') + buf[s] = '_'; + else if (c == '\0' || c == '.') + break; + else + buf[s] = c; + } + + buf[s] = '\0'; + + if (len) + *len = s; + + return buf; +} + +char *path_to_modname(const char *path, char buf[static PATH_MAX], size_t *len) +{ + char *modname; + + modname = basename(path); + if (modname == NULL || modname[0] == '\0') + return NULL; + + return modname_normalize(modname, buf, len); +} + +bool path_ends_with_kmod_ext(const char *path, size_t len) +{ + const struct kmod_ext *eitr; + + for (eitr = kmod_exts; eitr->ext != NULL; eitr++) { + if (len <= eitr->len) + continue; + if (streq(path + len - eitr->len, eitr->ext)) + return true; + } + + return false; +} + +/* read-like and fread-like functions */ +/* ************************************************************************ */ +ssize_t read_str_safe(int fd, char *buf, size_t buflen) +{ + size_t todo = buflen - 1; + size_t done = 0; + + assert_cc(EAGAIN == EWOULDBLOCK); + + do { + ssize_t r = read(fd, buf + done, todo); + + if (r == 0) + break; + else if (r > 0) { + todo -= r; + done += r; + } else { + if (errno == EAGAIN || errno == EINTR) + continue; + else + return -errno; + } + } while (todo > 0); + + buf[done] = '\0'; + return done; +} + +ssize_t write_str_safe(int fd, const char *buf, size_t buflen) +{ + size_t todo = buflen; + size_t done = 0; + + assert_cc(EAGAIN == EWOULDBLOCK); + + do { + ssize_t r = write(fd, buf + done, todo); + + if (r == 0) + break; + else if (r > 0) { + todo -= r; + done += r; + } else { + if (errno == EAGAIN || errno == EINTR) + continue; + else + return -errno; + } + } while (todo > 0); + + return done; +} + +int read_str_long(int fd, long *value, int base) +{ + char buf[32], *end; + long v; + int err; + + *value = 0; + err = read_str_safe(fd, buf, sizeof(buf)); + if (err < 0) + return err; + errno = 0; + v = strtol(buf, &end, base); + if (end == buf || !isspace(*end)) + return -EINVAL; + + *value = v; + return 0; +} + +int read_str_ulong(int fd, unsigned long *value, int base) +{ + char buf[32], *end; + long v; + int err; + + *value = 0; + err = read_str_safe(fd, buf, sizeof(buf)); + if (err < 0) + return err; + errno = 0; + v = strtoul(buf, &end, base); + if (end == buf || !isspace(*end)) + return -EINVAL; + *value = v; + return 0; +} + +/* + * Read one logical line from a configuration file. + * + * Line endings may be escaped with backslashes, to form one logical line from + * several physical lines. No end of line character(s) are included in the + * result. + * + * If linenum is not NULL, it is incremented by the number of physical lines + * which have been read. + */ +char *freadline_wrapped(FILE *fp, unsigned int *linenum) +{ + int size = 256; + int i = 0, n = 0; + _cleanup_free_ char *buf = malloc(size); + + if (buf == NULL) + return NULL; + + for(;;) { + int ch = getc_unlocked(fp); + + switch(ch) { + case EOF: + if (i == 0) + return NULL; + /* else fall through */ + + case '\n': + n++; + + { + char *ret = buf; + ret[i] = '\0'; + buf = NULL; + if (linenum) + *linenum += n; + return ret; + } + + case '\\': + ch = getc_unlocked(fp); + + if (ch == '\n') { + n++; + continue; + } + /* else fall through */ + + default: + buf[i++] = ch; + + if (i == size) { + char *tmp; + size *= 2; + tmp = realloc(buf, size); + if (!tmp) + return NULL; + buf = tmp; + } + } + } +} + +/* path handling functions */ +/* ************************************************************************ */ + +static bool path_is_absolute(const char *p) +{ + assert(p != NULL); + + return p[0] == '/'; +} + +char *path_make_absolute_cwd(const char *p) +{ + _cleanup_free_ char *cwd = NULL; + size_t plen, cwdlen; + char *r; + + if (path_is_absolute(p)) + return strdup(p); + + cwd = get_current_dir_name(); + if (!cwd) + return NULL; + + plen = strlen(p); + cwdlen = strlen(cwd); + + /* cwd + '/' + p + '\0' */ + r = realloc(cwd, cwdlen + 1 + plen + 1); + if (r == NULL) + return NULL; + + cwd = NULL; + r[cwdlen] = '/'; + memcpy(&r[cwdlen + 1], p, plen + 1); + + return r; +} + +static inline int is_dir(const char *path) +{ + struct stat st; + + if (stat(path, &st) >= 0) + return S_ISDIR(st.st_mode); + + return -errno; +} + +int mkdir_p(const char *path, int len, mode_t mode) +{ + char *start, *end; + + start = strndupa(path, len); + end = start + len; + + /* + * scan backwards, replacing '/' with '\0' while the component doesn't + * exist + */ + for (;;) { + int r = is_dir(start); + if (r > 0) { + end += strlen(end); + + if (end == start + len) + return 0; + + /* end != start, since it would be caught on the first + * iteration */ + *end = '/'; + break; + } else if (r == 0) + return -ENOTDIR; + + if (end == start) + break; + + *end = '\0'; + + /* Find the next component, backwards, discarding extra '/'*/ + while (end > start && *end != '/') + end--; + + while (end > start && *(end - 1) == '/') + end--; + } + + for (; end < start + len;) { + if (mkdir(start, mode) < 0 && errno != EEXIST) + return -errno; + + end += strlen(end); + *end = '/'; + } + + return 0; +} + +int mkdir_parents(const char *path, mode_t mode) +{ + char *end = strrchr(path, '/'); + + /* no parent directories */ + if (end == NULL) + return 0; + + return mkdir_p(path, end - path, mode); +} + +static unsigned long long ts_usec(const struct timespec *ts) +{ + return (unsigned long long) ts->tv_sec * USEC_PER_SEC + + (unsigned long long) ts->tv_nsec / NSEC_PER_USEC; +} + +static unsigned long long ts_msec(const struct timespec *ts) +{ + return (unsigned long long) ts->tv_sec * MSEC_PER_SEC + + (unsigned long long) ts->tv_nsec / NSEC_PER_MSEC; +} + +static struct timespec msec_ts(unsigned long long msec) +{ + struct timespec ts = { + .tv_sec = msec / MSEC_PER_SEC, + .tv_nsec = (msec % MSEC_PER_SEC) * NSEC_PER_MSEC, + }; + + return ts; +} + +int sleep_until_msec(unsigned long long msec) +{ + struct timespec ts = msec_ts(msec); + + if (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &ts, NULL) < 0 && + errno != EINTR) + return -errno; + + return 0; +} + +/* + * Exponential retry backoff with tail + */ +unsigned long long get_backoff_delta_msec(unsigned long long t0, + unsigned long long tend, + unsigned long long *delta) +{ + unsigned long long t; + + t = now_msec(); + + if (!*delta) + *delta = 1; + else + *delta <<= 1; + + while (t + *delta > tend) + *delta >>= 1; + + if (!*delta && tend > t) + *delta = tend - t; + + return t + *delta; +} + +unsigned long long now_usec(void) +{ + struct timespec ts; + + if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) + return 0; + + return ts_usec(&ts); +} + +unsigned long long now_msec(void) +{ + struct timespec ts; + + if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) + return 0; + + return ts_msec(&ts); +} + +unsigned long long stat_mstamp(const struct stat *st) +{ +#ifdef HAVE_STRUCT_STAT_ST_MTIM + return ts_usec(&st->st_mtim); +#else + return (unsigned long long) st->st_mtime; +#endif +} |