X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=blobdiff_plain;f=src%2Fshared%2Futil.c;h=72984735ced828e33c2eca18df7828fdf1db58c9;hb=7b061de2d0601a33f7246c4b909f30ddc44d7ab6;hp=dfaf7f7f45b07416bcd365b869c7b05b4618d752;hpb=f02ca52281315cb6067a3798450bc814837c85ef;p=elogind.git
diff --git a/src/shared/util.c b/src/shared/util.c
index dfaf7f7f4..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,6 +60,13 @@
#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
@@ -67,13 +74,13 @@
#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"
@@ -87,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;
@@ -504,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;
}
@@ -1331,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;
@@ -1350,112 +1485,29 @@ 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;
+ int k;
- if (*f != '\\') {
+ remaining = s + length - f;
+ assert(remaining > 0);
+
+ if (*f != '\\' || remaining == 1) {
+ /* a literal literal, or a trailing backslash, copy verbatim */
*(t++) = *f;
continue;
}
- 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, b;
-
- 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, b, c;
-
- 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;
- }
-
- case 0:
- /* premature end of string. */
- *(t++) = '\\';
- goto finish;
-
- 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++;
}
-finish:
*t = 0;
return r;
}
@@ -1525,6 +1577,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");
}
@@ -1667,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"
@@ -2303,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;
@@ -2557,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
@@ -2593,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) {
@@ -2821,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)
@@ -2896,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;
@@ -3375,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);
@@ -3456,7 +3516,7 @@ unsigned columns(void) {
c = 80;
cached_columns = c;
- return c;
+ return cached_columns;
}
int fd_lines(int fd) {
@@ -3473,7 +3533,7 @@ int fd_lines(int fd) {
unsigned lines(void) {
const char *e;
- unsigned l;
+ int l;
if (_likely_(cached_lines > 0))
return cached_lines;
@@ -3481,7 +3541,7 @@ unsigned lines(void) {
l = 0;
e = getenv("LINES");
if (e)
- (void) safe_atou(e, &l);
+ (void) safe_atoi(e, &l);
if (l <= 0)
l = fd_lines(STDOUT_FILENO);
@@ -4004,7 +4064,7 @@ bool tty_is_vc_resolve(const char *tty) {
const char *default_term_for_tty(const char *tty) {
assert(tty);
- return tty_is_vc_resolve(tty) ? "TERM=linux" : "TERM=vt102";
+ return tty_is_vc_resolve(tty) ? "TERM=linux" : "TERM=vt220";
}
bool dirent_is_file(const struct dirent *de) {
@@ -4035,62 +4095,63 @@ bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) {
return endswith(de->d_name, suffix);
}
-void execute_directory(const char *directory, DIR *d, usec_t timeout, char *argv[]) {
- pid_t executor_pid;
- int r;
+static int do_execute(char **directories, usec_t timeout, char *argv[]) {
+ _cleanup_hashmap_free_free_ Hashmap *pids = NULL;
+ _cleanup_set_free_free_ Set *seen = NULL;
+ char **directory;
- assert(directory);
+ /* We fork this all off from a child process so that we can
+ * somewhat cleanly make use of SIGALRM to set a time limit */
- /* Executes all binaries in a directory in parallel and waits
- * for them to finish. Optionally a timeout is applied. */
-
- executor_pid = fork();
- if (executor_pid < 0) {
- log_error_errno(errno, "Failed to fork: %m");
- return;
+ reset_all_signal_handlers();
+ reset_signal_mask();
- } else if (executor_pid == 0) {
- _cleanup_hashmap_free_free_ Hashmap *pids = NULL;
- _cleanup_closedir_ DIR *_d = NULL;
- struct dirent *de;
+ assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
- /* We fork this all off from a child process so that
- * we can somewhat cleanly make use of SIGALRM to set
- * a time limit */
+ pids = hashmap_new(NULL);
+ if (!pids)
+ return log_oom();
- reset_all_signal_handlers();
- reset_signal_mask();
+ seen = set_new(&string_hash_ops);
+ if (!seen)
+ return log_oom();
- assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
+ STRV_FOREACH(directory, directories) {
+ _cleanup_closedir_ DIR *d;
+ struct dirent *de;
+ d = opendir(*directory);
if (!d) {
- d = _d = opendir(directory);
- if (!d) {
- if (errno == ENOENT)
- _exit(EXIT_SUCCESS);
-
- log_error_errno(errno, "Failed to enumerate directory %s: %m", directory);
- _exit(EXIT_FAILURE);
- }
- }
+ if (errno == ENOENT)
+ continue;
- pids = hashmap_new(NULL);
- if (!pids) {
- log_oom();
- _exit(EXIT_FAILURE);
+ return log_error_errno(errno, "Failed to open directory %s: %m", *directory);
}
FOREACH_DIRENT(de, d, break) {
_cleanup_free_ char *path = NULL;
pid_t pid;
+ int r;
if (!dirent_is_file(de))
continue;
- path = strjoin(directory, "/", de->d_name, NULL);
- if (!path) {
- log_oom();
- _exit(EXIT_FAILURE);
+ if (set_contains(seen, de->d_name)) {
+ log_debug("%1$s/%2$s skipped (%2$s was already seen).", *directory, de->d_name);
+ continue;
+ }
+
+ r = set_put_strdup(seen, de->d_name);
+ if (r < 0)
+ return log_oom();
+
+ path = strjoin(*directory, "/", de->d_name, NULL);
+ if (!path)
+ return log_oom();
+
+ if (null_or_empty_path(path)) {
+ log_debug("%s is empty (a mask).", path);
+ continue;
}
pid = fork();
@@ -4110,45 +4171,68 @@ void execute_directory(const char *directory, DIR *d, usec_t timeout, char *argv
argv[0] = path;
execv(path, argv);
- log_error_errno(errno, "Failed to execute %s: %m", path);
- _exit(EXIT_FAILURE);
+ return log_error_errno(errno, "Failed to execute %s: %m", path);
}
log_debug("Spawned %s as " PID_FMT ".", path, pid);
r = hashmap_put(pids, UINT_TO_PTR(pid), path);
- if (r < 0) {
- log_oom();
- _exit(EXIT_FAILURE);
- }
-
+ if (r < 0)
+ return log_oom();
path = NULL;
}
+ }
- /* Abort execution of this process after the
- * timout. We simply rely on SIGALRM as default action
- * terminating the process, and turn on alarm(). */
+ /* Abort execution of this process after the timout. We simply
+ * rely on SIGALRM as default action terminating the process,
+ * and turn on alarm(). */
- if (timeout != USEC_INFINITY)
- alarm((timeout + USEC_PER_SEC - 1) / USEC_PER_SEC);
+ if (timeout != USEC_INFINITY)
+ alarm((timeout + USEC_PER_SEC - 1) / USEC_PER_SEC);
- while (!hashmap_isempty(pids)) {
- _cleanup_free_ char *path = NULL;
- pid_t pid;
+ while (!hashmap_isempty(pids)) {
+ _cleanup_free_ char *path = NULL;
+ pid_t pid;
- pid = PTR_TO_UINT(hashmap_first_key(pids));
- assert(pid > 0);
+ pid = PTR_TO_UINT(hashmap_first_key(pids));
+ assert(pid > 0);
- path = hashmap_remove(pids, UINT_TO_PTR(pid));
- assert(path);
+ path = hashmap_remove(pids, UINT_TO_PTR(pid));
+ assert(path);
- wait_for_terminate_and_warn(path, pid, true);
- }
+ wait_for_terminate_and_warn(path, pid, true);
+ }
- _exit(EXIT_SUCCESS);
+ return 0;
+}
+
+void execute_directories(const char* const* directories, usec_t timeout, char *argv[]) {
+ pid_t executor_pid;
+ int r;
+ char *name;
+ char **dirs = (char**) directories;
+
+ assert(!strv_isempty(dirs));
+
+ name = basename(dirs[0]);
+ assert(!isempty(name));
+
+ /* Executes all binaries in the directories in parallel and waits
+ * for them to finish. Optionally a timeout is applied. If a file
+ * with the same name exists in more than one directory, the
+ * earliest one wins. */
+
+ executor_pid = fork();
+ if (executor_pid < 0) {
+ log_error_errno(errno, "Failed to fork: %m");
+ return;
+
+ } else if (executor_pid == 0) {
+ r = do_execute(dirs, timeout, argv);
+ _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
}
- wait_for_terminate_and_warn(directory, executor_pid, true);
+ wait_for_terminate_and_warn(name, executor_pid, true);
}
int kill_and_sigcont(pid_t pid, int sig) {
@@ -4205,6 +4289,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)
@@ -4271,23 +4360,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,
@@ -5436,25 +5508,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;
- if (startswith(url, "man:") && url[4])
+ 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 (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) {
@@ -5716,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;
@@ -5927,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;
@@ -6305,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)
@@ -6345,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)
@@ -6390,7 +6498,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;
@@ -6592,7 +6700,7 @@ int getpeersec(int fd, char **ret) {
if (isempty(s)) {
free(s);
- return -ENOTSUP;
+ return -EOPNOTSUPP;
}
*ret = s;
@@ -6629,7 +6737,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)
@@ -6717,23 +6825,6 @@ uint64_t physical_memory(void) {
return (uint64_t) mem * (uint64_t) page_size();
}
-char* mount_test_option(const char *haystack, const char *needle) {
-
- struct mntent me = {
- .mnt_opts = (char*) haystack
- };
-
- assert(needle);
-
- /* Like glibc's hasmntopt(), but works on a string, not a
- * struct mntent */
-
- if (!haystack)
- return NULL;
-
- return hasmntopt(&me, needle);
-}
-
void hexdump(FILE *f, const void *p, size_t s) {
const uint8_t *b = p;
unsigned n = 0;
@@ -6858,6 +6949,15 @@ int umount_recursive(const char *prefix, int flags) {
return r ? r : n;
}
+static int get_mount_flags(const char *path, unsigned long *flags) {
+ struct statvfs buf;
+
+ if (statvfs(path, &buf) < 0)
+ return -errno;
+ *flags = buf.f_flag;
+ return 0;
+}
+
int bind_remount_recursive(const char *prefix, bool ro) {
_cleanup_set_free_free_ Set *done = NULL;
_cleanup_free_ char *cleaned = NULL;
@@ -6892,6 +6992,7 @@ int bind_remount_recursive(const char *prefix, bool ro) {
_cleanup_set_free_free_ Set *todo = NULL;
bool top_autofs = false;
char *x;
+ unsigned long orig_flags;
todo = set_new(&string_hash_ops);
if (!todo)
@@ -6969,7 +7070,11 @@ int bind_remount_recursive(const char *prefix, bool ro) {
if (mount(cleaned, cleaned, NULL, MS_BIND|MS_REC, NULL) < 0)
return -errno;
- if (mount(NULL, prefix, NULL, MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0)
+ orig_flags = 0;
+ (void) get_mount_flags(cleaned, &orig_flags);
+ orig_flags &= ~MS_RDONLY;
+
+ if (mount(NULL, prefix, NULL, orig_flags|MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0)
return -errno;
x = strdup(cleaned);
@@ -6989,7 +7094,14 @@ int bind_remount_recursive(const char *prefix, bool ro) {
if (r < 0)
return r;
- if (mount(NULL, x, NULL, MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0) {
+ /* Try to reuse the original flag set, but
+ * don't care for errors, in case of
+ * obstructed mounts */
+ orig_flags = 0;
+ (void) get_mount_flags(x, &orig_flags);
+ orig_flags &= ~MS_RDONLY;
+
+ if (mount(NULL, x, NULL, orig_flags|MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0) {
/* Deal with mount points that are
* obstructed by a later mount */
@@ -7154,7 +7266,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";
@@ -7194,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,
@@ -7254,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;
}
@@ -7262,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;
@@ -7269,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 == '\'')
@@ -7287,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;
}
@@ -7295,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;
@@ -7317,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;
}
@@ -7325,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;
@@ -7354,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;
@@ -7365,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;
@@ -7380,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;
@@ -7396,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;
@@ -7647,9 +7784,396 @@ int fd_setcrtime(int fd, usec_t usec) {
assert(fd >= 0);
+ if (usec <= 0)
+ usec = now(CLOCK_REALTIME);
+
le = htole64((uint64_t) usec);
if (fsetxattr(fd, "user.crtime_usec", &le, sizeof(le), 0) < 0)
return -errno;
return 0;
}
+
+int chattr_fd(int fd, bool b, 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;
+
+ if (b)
+ new_attr = old_attr | mask;
+ else
+ new_attr = old_attr & ~mask;
+
+ if (new_attr == old_attr)
+ return 0;
+
+ if (ioctl(fd, FS_IOC_SETFLAGS, &new_attr) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int chattr_path(const char *p, bool b, unsigned mask) {
+ _cleanup_close_ int fd = -1;
+
+ assert(p);
+
+ if (mask == 0)
+ return 0;
+
+ fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
+ if (fd < 0)
+ return -errno;
+
+ 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);
+
+ if (ioctl(fd, FS_IOC_GETFLAGS, ret) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int read_attr_path(const char *p, unsigned *ret) {
+ _cleanup_close_ int fd = -1;
+
+ assert(p);
+ assert(ret);
+
+ fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
+ if (fd < 0)
+ return -errno;
+
+ 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;
+}