| /* | |
| * 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 */ | |
| /* ************************************************************************ */ | |
| 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); | |
| } | |
| 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; | |
| } | |
| 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 | |
| } |