#include <grp.h>
#include <sys/mman.h>
#include <sys/vfs.h>
+#include <sys/mount.h>
#include <linux/magic.h>
#include <limits.h>
#include <langinfo.h>
#include "log.h"
#include "strv.h"
#include "label.h"
+#include "mkdir.h"
#include "path-util.h"
#include "exit-status.h"
#include "hashmap.h"
#include "gunicode.h"
#include "virt.h"
#include "def.h"
-#include "missing.h"
int saved_argc = 0;
char **saved_argv = NULL;
return (char*) s + sl - pl;
}
-bool first_word(const char *s, const char *word) {
+char* first_word(const char *s, const char *word) {
size_t sl, wl;
+ const char *p;
assert(s);
assert(word);
+ /* Checks if the string starts with the specified word, either
+ * followed by NUL or by whitespace. Returns a pointer to the
+ * NUL or the first character after the whitespace. */
+
sl = strlen(s);
wl = strlen(word);
if (sl < wl)
- return false;
+ return NULL;
if (wl == 0)
- return true;
+ return (char*) s;
if (memcmp(s, word, wl) != 0)
- return false;
+ return NULL;
- return s[wl] == 0 ||
- strchr(WHITESPACE, s[wl]);
+ p = s + wl;
+ if (*p == 0)
+ return (char*) p;
+
+ if (!strchr(WHITESPACE, *p))
+ return NULL;
+
+ p += strspn(p, WHITESPACE);
+ return (char*) p;
}
int close_nointr(int fd) {
- int r;
-
assert(fd >= 0);
- r = close(fd);
- /* Just ignore EINTR; a retry loop is the wrong
- * thing to do on Linux.
+ if (close(fd) >= 0)
+ return 0;
+
+ /*
+ * Just ignore EINTR; a retry loop is the wrong thing to do on
+ * Linux.
*
* http://lkml.indiana.edu/hypermail/linux/kernel/0509.1/0877.html
* https://bugzilla.gnome.org/show_bug.cgi?id=682819
* http://utcc.utoronto.ca/~cks/space/blog/unix/CloseEINTR
* https://sites.google.com/site/michaelsafyan/software-engineering/checkforeintrwheninvokingclosethinkagain
*/
- if (_unlikely_(r < 0 && errno == EINTR))
+ if (errno == EINTR)
return 0;
- else if (r >= 0)
- return r;
- else
- return -errno;
+
+ return -errno;
}
-void close_nointr_nofail(int fd) {
- PROTECT_ERRNO;
+int safe_close(int fd) {
+
+ /*
+ * Like close_nointr() but cannot fail. Guarantees errno is
+ * unchanged. Is a NOP with negative fds passed, and returns
+ * -1, so that it can be used in this syntax:
+ *
+ * fd = safe_close(fd);
+ */
+
+ if (fd >= 0) {
+ PROTECT_ERRNO;
- /* like close_nointr() but cannot fail, and guarantees errno
- * is unchanged */
+ /* The kernel might return pretty much any error code
+ * via close(), but the fd will be closed anyway. The
+ * only condition we want to check for here is whether
+ * the fd was invalid at all... */
+
+ assert_se(close_nointr(fd) != -EBADF);
+ }
- assert_se(close_nointr(fd) == 0);
+ return -1;
}
void close_many(const int fds[], unsigned n_fd) {
assert(fds || n_fd <= 0);
for (i = 0; i < n_fd; i++)
- close_nointr_nofail(fds[i]);
+ safe_close(fds[i]);
}
int unlink_noerrno(const char *path) {
int parse_boolean(const char *v) {
assert(v);
- if (streq(v, "1") || v[0] == 'y' || v[0] == 'Y' || v[0] == 't' || v[0] == 'T' || strcaseeq(v, "on"))
+ if (streq(v, "1") || strcaseeq(v, "yes") || strcaseeq(v, "y") || strcaseeq(v, "true") || strcaseeq(v, "t") || strcaseeq(v, "on"))
return 1;
- else if (streq(v, "0") || v[0] == 'n' || v[0] == 'N' || v[0] == 'f' || v[0] == 'F' || strcaseeq(v, "off"))
+ else if (streq(v, "0") || strcaseeq(v, "no") || strcaseeq(v, "n") || strcaseeq(v, "false") || strcaseeq(v, "f") || strcaseeq(v, "off"))
return 0;
return -EINVAL;
if ((unsigned long) uid != ul)
return -ERANGE;
+ /* Some libc APIs use (uid_t) -1 as special placeholder */
+ if (uid == (uid_t) 0xFFFFFFFF)
+ return -ENXIO;
+
+ /* A long time ago UIDs where 16bit, hence explicitly avoid the 16bit -1 too */
+ if (uid == (uid_t) 0xFFFF)
+ return -ENXIO;
+
*ret_uid = uid;
return 0;
}
return 0;
}
+int safe_atou8(const char *s, uint8_t *ret) {
+ char *x = NULL;
+ unsigned long l;
+
+ assert(s);
+ assert(ret);
+
+ errno = 0;
+ l = strtoul(s, &x, 0);
+
+ if (!x || x == s || *x || errno)
+ return errno > 0 ? -errno : -EINVAL;
+
+ if ((unsigned long) (uint8_t) l != l)
+ return -ERANGE;
+
+ *ret = (uint8_t) l;
+ return 0;
+}
+
int safe_atollu(const char *s, long long unsigned *ret_llu) {
char *x = NULL;
unsigned long long l;
else if (s[n] == '\\')
escaped = true;
else if (strchr(reject, s[n]))
- return n;
+ break;
}
- return n;
+ /* if s ends in \, return index of previous char */
+ return n - escaped;
}
/* Split a string into words. */
-char *split(const char *c, size_t *l, const char *separator, bool quoted, char **state) {
- char *current;
+const char* split(const char **state, size_t *l, const char *separator, bool quoted) {
+ const char *current;
- current = *state ? *state : (char*) c;
+ current = *state;
- if (!*current || *c == 0)
+ if (!*current) {
+ assert(**state == '\0');
return NULL;
+ }
current += strspn(current, separator);
- if (!*current)
+ if (!*current) {
+ *state = current;
return NULL;
+ }
if (quoted && strchr("\'\"", *current)) {
- char quotechar = *(current++);
- *l = strcspn_escaped(current, (char[]){quotechar, '\0'});
- *state = current+*l+1;
+ char quotechars[2] = {*current, '\0'};
+
+ *l = strcspn_escaped(current + 1, quotechars);
+ if (current[*l + 1] == '\0' ||
+ (current[*l + 2] && !strchr(separator, current[*l + 2]))) {
+ /* right quote missing or garbage at the end*/
+ *state = current;
+ return NULL;
+ }
+ assert(current[*l + 1] == quotechars[0]);
+ *state = current++ + *l + 2;
} else if (quoted) {
*l = strcspn_escaped(current, separator);
- *state = current+*l;
+ *state = current + *l;
} else {
*l = strcspn(current, separator);
- *state = current+*l;
+ *state = current + *l;
}
- return (char*) current;
+ return current;
}
int get_parent_of_pid(pid_t pid, pid_t *_ppid) {
return strnappend(s, suffix, suffix ? strlen(suffix) : 0);
}
-int readlink_malloc(const char *p, char **ret) {
+int readlinkat_malloc(int fd, const char *p, char **ret) {
size_t l = 100;
int r;
if (!c)
return -ENOMEM;
- n = readlink(p, c, l-1);
+ n = readlinkat(fd, p, c, l-1);
if (n < 0) {
r = -errno;
free(c);
}
}
+int readlink_malloc(const char *p, char **ret) {
+ return readlinkat_malloc(AT_FDCWD, p, ret);
+}
+
int readlink_and_make_absolute(const char *p, char **r) {
_cleanup_free_ char *target = NULL;
char *k;
}
int reset_all_signal_handlers(void) {
- int sig;
+ int sig, r = 0;
for (sig = 1; sig < _NSIG; sig++) {
struct sigaction sa = {
.sa_flags = SA_RESTART,
};
+ /* These two cannot be caught... */
if (sig == SIGKILL || sig == SIGSTOP)
continue;
/* On Linux the first two RT signals are reserved by
* glibc, and sigaction() will return EINVAL for them. */
if ((sigaction(sig, &sa, NULL) < 0))
- if (errno != EINVAL)
- return -errno;
+ if (errno != EINVAL && r == 0)
+ r = -errno;
}
+ return r;
+}
+
+int reset_signal_mask(void) {
+ sigset_t ss;
+
+ if (sigemptyset(&ss) < 0)
+ return -errno;
+
+ if (sigprocmask(SIG_SETMASK, &ss, NULL) < 0)
+ return -errno;
+
return 0;
}
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
- return -1;
+ return -EINVAL;
}
char *hexmem(const void *p, size_t l) {
if (c >= '0' && c <= '7')
return c - '0';
- return -1;
+ return -EINVAL;
}
char decchar(int x) {
if (c >= '0' && c <= '9')
return c - '0';
- return -1;
+ return -EINVAL;
}
char *cescape(const char *s) {
r = new(char, pl+length+1);
if (!r)
- return r;
+ return NULL;
if (prefix)
memcpy(r, prefix, pl);
a = unhexchar(f[1]);
b = unhexchar(f[2]);
- if (a < 0 || b < 0) {
+ if (a < 0 || b < 0 || (a == 0 && b == 0)) {
/* Invalid escape code, let's take it literal then */
*(t++) = '\\';
*(t++) = 'x';
b = unoctchar(f[1]);
c = unoctchar(f[2]);
- if (a < 0 || b < 0 || c < 0) {
+ 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];
endswith(filename, ".rpmorig") ||
endswith(filename, ".dpkg-old") ||
endswith(filename, ".dpkg-new") ||
+ endswith(filename, ".dpkg-tmp") ||
endswith(filename, ".swp");
}
assert(filename);
if (endswith(filename, "~"))
- return false;
+ return true;
return ignore_file_allow_backup(filename);
}
int fd_nonblock(int fd, bool nonblock) {
- int flags;
+ int flags, nflags;
assert(fd >= 0);
- if ((flags = fcntl(fd, F_GETFL, 0)) < 0)
+ flags = fcntl(fd, F_GETFL, 0);
+ if (flags < 0)
return -errno;
if (nonblock)
- flags |= O_NONBLOCK;
+ nflags = flags | O_NONBLOCK;
else
- flags &= ~O_NONBLOCK;
+ nflags = flags & ~O_NONBLOCK;
- if (fcntl(fd, F_SETFL, flags) < 0)
+ if (nflags == flags)
+ return 0;
+
+ if (fcntl(fd, F_SETFL, nflags) < 0)
return -errno;
return 0;
}
int fd_cloexec(int fd, bool cloexec) {
- int flags;
+ int flags, nflags;
assert(fd >= 0);
- if ((flags = fcntl(fd, F_GETFD, 0)) < 0)
+ flags = fcntl(fd, F_GETFD, 0);
+ if (flags < 0)
return -errno;
if (cloexec)
- flags |= FD_CLOEXEC;
+ nflags = flags | FD_CLOEXEC;
else
- flags &= ~FD_CLOEXEC;
+ nflags = flags & ~FD_CLOEXEC;
+
+ if (nflags == flags)
+ return 0;
- if (fcntl(fd, F_SETFD, flags) < 0)
+ if (fcntl(fd, F_SETFD, nflags) < 0)
return -errno;
return 0;
}
int close_all_fds(const int except[], unsigned n_except) {
- DIR *d;
+ _cleanup_closedir_ DIR *d = NULL;
struct dirent *de;
int r = 0;
}
}
- closedir(d);
return r;
}
static const char table[] =
"cifs\0"
"smbfs\0"
+ "sshfs\0"
"ncpfs\0"
"ncp\0"
"nfs\0"
"nfs4\0"
"gfs\0"
- "gfs2\0";
+ "gfs2\0"
+ "glusterfs\0";
+
+ const char *x;
+
+ x = startswith(fstype, "fuse.");
+ if (x)
+ fstype = x;
return nulstr_contains(table, fstype);
}
int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) {
struct termios old_termios, new_termios;
- char c;
- char line[LINE_MAX];
+ char c, line[LINE_MAX];
assert(f);
assert(ret);
if (tcsetattr(fileno(f), TCSADRAIN, &new_termios) >= 0) {
size_t k;
- if (t != (usec_t) -1) {
+ if (t != USEC_INFINITY) {
if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0) {
tcsetattr(fileno(f), TCSADRAIN, &old_termios);
return -ETIMEDOUT;
}
}
- if (t != (usec_t) -1)
+ if (t != USEC_INFINITY) {
if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0)
return -ETIMEDOUT;
+ }
+ errno = 0;
if (!fgets(line, sizeof(line), f))
- return -EIO;
+ return errno ? -errno : -EIO;
truncate_nl(line);
return 0;
}
-int ask(char *ret, const char *replies, const char *text, ...) {
+int ask_char(char *ret, const char *replies, const char *text, ...) {
+ int r;
assert(ret);
assert(replies);
for (;;) {
va_list ap;
char c;
- int r;
bool need_nl = true;
if (on_tty())
fflush(stdout);
- r = read_one_char(stdin, &c, (usec_t) -1, &need_nl);
+ r = read_one_char(stdin, &c, USEC_INFINITY, &need_nl);
if (r < 0) {
if (r == -EBADMSG) {
}
}
+int ask_string(char **ret, const char *text, ...) {
+ assert(ret);
+ assert(text);
+
+ for (;;) {
+ char line[LINE_MAX];
+ va_list ap;
+
+ if (on_tty())
+ fputs(ANSI_HIGHLIGHT_ON, stdout);
+
+ va_start(ap, text);
+ vprintf(text, ap);
+ va_end(ap);
+
+ if (on_tty())
+ fputs(ANSI_HIGHLIGHT_OFF, stdout);
+
+ fflush(stdout);
+
+ errno = 0;
+ if (!fgets(line, sizeof(line), stdin))
+ return errno ? -errno : -EIO;
+
+ if (!endswith(line, "\n"))
+ putchar('\n');
+ else {
+ char *s;
+
+ if (isempty(line))
+ continue;
+
+ truncate_nl(line);
+ s = strdup(line);
+ if (!s)
+ return -ENOMEM;
+
+ *ret = s;
+ return 0;
+ }
+ }
+}
+
int reset_terminal_fd(int fd, bool switch_to_text) {
struct termios termios;
int r = 0;
}
int reset_terminal(const char *name) {
- int fd, r;
+ _cleanup_close_ int fd = -1;
fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
if (fd < 0)
return fd;
- r = reset_terminal_fd(fd, true);
- close_nointr_nofail(fd);
-
- return r;
+ return reset_terminal_fd(fd, true);
}
int open_terminal(const char *name, int mode) {
c++;
}
- if (fd < 0)
- return -errno;
-
r = isatty(fd);
if (r < 0) {
- close_nointr_nofail(fd);
+ safe_close(fd);
return -errno;
}
if (!r) {
- close_nointr_nofail(fd);
+ safe_close(fd);
return -ENOTTY;
}
* on the same tty as an untrusted user this should not be a
* problem. (Which he probably should not do anyway.) */
- if (timeout != (usec_t) -1)
+ if (timeout != USEC_INFINITY)
ts = now(CLOCK_MONOTONIC);
if (!fail && !force) {
- notify = inotify_init1(IN_CLOEXEC | (timeout != (usec_t) -1 ? IN_NONBLOCK : 0));
+ notify = inotify_init1(IN_CLOEXEC | (timeout != USEC_INFINITY ? IN_NONBLOCK : 0));
if (notify < 0) {
r = -errno;
goto fail;
ssize_t l;
struct inotify_event *e;
- if (timeout != (usec_t) -1) {
+ if (timeout != USEC_INFINITY) {
usec_t n;
n = now(CLOCK_MONOTONIC);
* ended our handle will be dead. It's important that
* we do this after sleeping, so that we don't enter
* an endless loop. */
- close_nointr_nofail(fd);
+ fd = safe_close(fd);
}
- if (notify >= 0)
- close_nointr_nofail(notify);
+ safe_close(notify);
r = reset_terminal_fd(fd, true);
if (r < 0)
return fd;
fail:
- if (fd >= 0)
- close_nointr_nofail(fd);
-
- if (notify >= 0)
- close_nointr_nofail(notify);
+ safe_close(fd);
+ safe_close(notify);
return r;
}
int release_terminal(void) {
- int r = 0;
- struct sigaction sa_old, sa_new = {
+ static const struct sigaction sa_new = {
.sa_handler = SIG_IGN,
.sa_flags = SA_RESTART,
};
- _cleanup_close_ int fd;
+
+ _cleanup_close_ int fd = -1;
+ struct sigaction sa_old;
+ int r = 0;
fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_NDELAY|O_CLOEXEC);
if (fd < 0)
va_list ap;
int r = 0;
-
if (sigaction(sig, &sa, NULL) < 0)
r = -errno;
return r;
}
-int close_pipe(int p[]) {
- int a = 0, b = 0;
-
+void safe_close_pair(int p[]) {
assert(p);
- if (p[0] >= 0) {
- a = close_nointr(p[0]);
- p[0] = -1;
- }
-
- if (p[1] >= 0) {
- b = close_nointr(p[1]);
- p[1] = -1;
+ if (p[0] == p[1]) {
+ /* Special case pairs which use the same fd in both
+ * directions... */
+ p[0] = p[1] = safe_close(p[0]);
+ return;
}
- return a < 0 ? a : b;
+ p[0] = safe_close(p[0]);
+ p[1] = safe_close(p[1]);
}
ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) {
* and expect that any error/EOF is reported
* via read() */
- fd_wait_for_event(fd, POLLIN, (usec_t) -1);
+ fd_wait_for_event(fd, POLLIN, USEC_INFINITY);
continue;
}
* and expect that any error/EOF is reported
* via write() */
- fd_wait_for_event(fd, POLLOUT, (usec_t) -1);
+ fd_wait_for_event(fd, POLLOUT, USEC_INFINITY);
continue;
}
t = dup3(fd, STDERR_FILENO, 0);
if (fd >= 3)
- close_nointr_nofail(fd);
+ safe_close(fd);
if (r < 0 || s < 0 || t < 0)
return -errno;
va_end(ap);
}
+int sigprocmask_many(int how, ...) {
+ va_list ap;
+ sigset_t ss;
+ int sig;
+
+ assert_se(sigemptyset(&ss) == 0);
+
+ va_start(ap, how);
+ while ((sig = va_arg(ap, int)) > 0)
+ assert_se(sigaddset(&ss, sig) == 0);
+ va_end(ap);
+
+ if (sigprocmask(how, &ss, NULL) < 0)
+ return -errno;
+
+ return 0;
+}
+
char* gethostname_malloc(void) {
struct utsname u;
return !isempty(u.nodename) && !streq(u.nodename, "(none)");
}
-static char *lookup_uid(uid_t uid) {
+char *lookup_uid(uid_t uid) {
long bufsize;
char *name;
_cleanup_free_ char *buf = NULL;
if (getpwuid_r(uid, &pwbuf, buf, bufsize, &pw) == 0 && pw)
return strdup(pw->pw_name);
- if (asprintf(&name, "%lu", (unsigned long) uid) < 0)
+ if (asprintf(&name, UID_FMT, uid) < 0)
return NULL;
return name;
/* This is an ugly hack */
if (major(devnr) == 136) {
- asprintf(&b, "pts/%lu", (unsigned long) minor(devnr));
+ asprintf(&b, "pts/%u", minor(devnr));
goto finish;
}
}
int rm_rf_children_dangerous(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev) {
- DIR *d;
+ _cleanup_closedir_ DIR *d = NULL;
int ret = 0;
assert(fd >= 0);
d = fdopendir(fd);
if (!d) {
- close_nointr_nofail(fd);
+ safe_close(fd);
return errno == ENOENT ? 0 : -errno;
}
errno = 0;
de = readdir(d);
- if (!de && errno != 0) {
- if (ret == 0)
+ if (!de) {
+ if (errno != 0 && ret == 0)
ret = -errno;
- break;
+ return ret;
}
- if (!de)
- break;
-
if (streq(de->d_name, ".") || streq(de->d_name, ".."))
continue;
}
}
}
-
- closedir(d);
-
- return ret;
}
_pure_ static int is_temporary_fs(struct statfs *s) {
assert(fd >= 0);
if (fstatfs(fd, &s) < 0) {
- close_nointr_nofail(fd);
+ safe_close(fd);
return -errno;
}
* non-state data */
if (!is_temporary_fs(&s)) {
log_error("Attempted to remove disk file system, and we can't allow that.");
- close_nointr_nofail(fd);
+ safe_close(fd);
return -EPERM;
}
if (!dangerous) {
if (fstatfs(fd, &s) < 0) {
- close_nointr_nofail(fd);
+ safe_close(fd);
return -errno;
}
if (!is_temporary_fs(&s)) {
log_error("Attempted to remove disk file system, and we can't allow that.");
- close_nointr_nofail(fd);
+ safe_close(fd);
return -EPERM;
}
}
if (emax < 3)
emax = 3;
- e = ellipsize(s, emax, 75);
+ e = ellipsize(s, emax, 50);
if (e) {
free(s);
s = e;
}
char **replace_env_argv(char **argv, char **env) {
- char **r, **i;
+ char **ret, **i;
unsigned k = 0, l = 0;
l = strv_length(argv);
- if (!(r = new(char*, l+1)))
+ ret = new(char*, l+1);
+ if (!ret)
return NULL;
STRV_FOREACH(i, argv) {
e = strv_env_get(env, *i+1);
if (e) {
+ int r;
- if (!(m = strv_split_quoted(e))) {
- r[k] = NULL;
- strv_free(r);
+ r = strv_split_quoted(&m, e);
+ if (r < 0) {
+ ret[k] = NULL;
+ strv_free(ret);
return NULL;
}
} else
q = strv_length(m);
l = l + q - 1;
- if (!(w = realloc(r, sizeof(char*) * (l+1)))) {
- r[k] = NULL;
- strv_free(r);
+ w = realloc(ret, sizeof(char*) * (l+1));
+ if (!w) {
+ ret[k] = NULL;
+ strv_free(ret);
strv_free(m);
return NULL;
}
- r = w;
+ ret = w;
if (m) {
- memcpy(r + k, m, q * sizeof(char*));
+ memcpy(ret + k, m, q * sizeof(char*));
free(m);
}
}
/* If ${FOO} appears as part of a word, replace it by the variable as-is */
- if (!(r[k++] = replace_env(*i, env))) {
- strv_free(r);
+ ret[k] = replace_env(*i, env);
+ if (!ret[k]) {
+ strv_free(ret);
return NULL;
}
+ k++;
}
- r[k] = NULL;
- return r;
+ ret[k] = NULL;
+ return ret;
}
int fd_columns(int fd) {
c = 0;
e = getenv("COLUMNS");
if (e)
- safe_atoi(e, &c);
+ (void) safe_atoi(e, &c);
if (c <= 0)
c = fd_columns(STDOUT_FILENO);
l = 0;
e = getenv("LINES");
if (e)
- safe_atou(e, &l);
+ (void) safe_atou(e, &l);
if (l <= 0)
l = fd_lines(STDOUT_FILENO);
return ellipsize_mem(s, strlen(s), length, percent);
}
-int touch(const char *path) {
- int fd;
+int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode) {
+ _cleanup_close_ int fd;
+ int r;
assert(path);
- /* This just opens the file for writing, ensuring it
- * exists. It doesn't call utimensat() the way /usr/bin/touch
- * does it. */
+ if (parents)
+ mkdir_parents(path, 0755);
- fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644);
+ fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, mode > 0 ? mode : 0644);
if (fd < 0)
return -errno;
- close_nointr_nofail(fd);
+ if (mode > 0) {
+ r = fchmod(fd, mode);
+ if (r < 0)
+ return -errno;
+ }
+
+ if (uid != (uid_t) -1 || gid != (gid_t) -1) {
+ r = fchown(fd, uid, gid);
+ if (r < 0)
+ return -errno;
+ }
+
+ if (stamp != USEC_INFINITY) {
+ struct timespec ts[2];
+
+ timespec_store(&ts[0], stamp);
+ ts[1] = ts[0];
+ r = futimens(fd, ts);
+ } else
+ r = futimens(fd, NULL);
+ if (r < 0)
+ return -errno;
+
return 0;
}
+int touch(const char *path) {
+ return touch_file(path, false, USEC_INFINITY, (uid_t) -1, (gid_t) -1, 0);
+}
+
char *unquote(const char *s, const char* quotes) {
size_t l;
assert(s);
}
}
+/*
+ * Return values:
+ * < 0 : wait_for_terminate() failed to get the state of the
+ * process, the process was terminated by a signal, or
+ * failed for an unknown reason.
+ * >=0 : The process terminated normally, and its exit code is
+ * returned.
+ *
+ * That is, success is indicated by a return value of zero, and an
+ * error is indicated by a non-zero value.
+ */
int wait_for_terminate_and_warn(const char *name, pid_t pid) {
int r;
siginfo_t status;
return null_or_empty(&st);
}
+int null_or_empty_fd(int fd) {
+ struct stat st;
+
+ assert(fd >= 0);
+
+ if (fstat(fd, &st) < 0)
+ return -errno;
+
+ return null_or_empty(&st);
+}
+
DIR *xopendirat(int fd, const char *name, int flags) {
int nfd;
DIR *d;
d = fdopendir(nfd);
if (!d) {
- close_nointr_nofail(nfd);
+ safe_close(nfd);
return NULL;
}
bool tty_is_vc(const char *tty) {
assert(tty);
- if (startswith(tty, "/dev/"))
- tty += 5;
-
return vtnr_from_tty(tty) >= 0;
}
_cleanup_hashmap_free_free_ Hashmap *pids = NULL;
_cleanup_closedir_ DIR *_d = NULL;
struct dirent *de;
- sigset_t ss;
/* We fork this all off from a child process so that
* we can somewhat cleanly make use of SIGALRM to set
* a time limit */
reset_all_signal_handlers();
-
- assert_se(sigemptyset(&ss) == 0);
- assert_se(sigprocmask(SIG_SETMASK, &ss, NULL) == 0);
+ reset_signal_mask();
assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
}
}
- pids = hashmap_new(NULL, NULL);
+ pids = hashmap_new(NULL);
if (!pids) {
log_oom();
_exit(EXIT_FAILURE);
if (!dirent_is_file(de))
continue;
- if (asprintf(&path, "%s/%s", directory, de->d_name) < 0) {
+ path = strjoin(directory, "/", de->d_name, NULL);
+ if (!path) {
log_oom();
_exit(EXIT_FAILURE);
}
_exit(EXIT_FAILURE);
}
-
log_debug("Spawned %s as " PID_FMT ".", path, pid);
r = hashmap_put(pids, UINT_TO_PTR(pid), path);
* timout. We simply rely on SIGALRM as default action
* terminating the process, and turn on alarm(). */
- if (timeout != (usec_t) -1)
+ if (timeout != USEC_INFINITY)
alarm((timeout + USEC_PER_SEC - 1) / USEC_PER_SEC);
while (!hashmap_isempty(pids)) {
return s;
}
+bool machine_name_is_valid(const char *s) {
+
+ if (!hostname_is_valid(s))
+ return false;
+
+ /* Machine names should be useful hostnames, but also be
+ * useful in unit names, hence we enforce a stricter length
+ * limitation. */
+
+ if (strlen(s) > 64)
+ return false;
+
+ return true;
+}
+
int pipe_eof(int fd) {
struct pollfd pollfd = {
.fd = fd,
struct timespec ts;
int r;
- r = ppoll(&pollfd, 1, t == (usec_t) -1 ? NULL : timespec_store(&ts, t), NULL);
+ r = ppoll(&pollfd, 1, t == USEC_INFINITY ? NULL : timespec_store(&ts, t), NULL);
if (r < 0)
return -errno;
int fopen_temporary(const char *path, FILE **_f, char **_temp_path) {
FILE *f;
char *t;
- const char *fn;
- size_t k;
int fd;
assert(path);
assert(_f);
assert(_temp_path);
- t = new(char, strlen(path) + 1 + 6 + 1);
+ t = tempfn_xxxxxx(path);
if (!t)
return -ENOMEM;
- fn = basename(path);
- k = fn - path;
- memcpy(t, path, k);
- t[k] = '.';
- stpcpy(stpcpy(t+k+1, fn), "XXXXXX");
-
fd = mkostemp_safe(t, O_WRONLY|O_CLOEXEC);
if (fd < 0) {
free(t);
}
int terminal_vhangup(const char *name) {
- int fd, r;
+ _cleanup_close_ int fd;
fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
if (fd < 0)
return fd;
- r = terminal_vhangup_fd(fd);
- close_nointr_nofail(fd);
-
- return r;
+ return terminal_vhangup_fd(fd);
}
int vt_disallocate(const char *name) {
"\033[H" /* move home */
"\033[2J", /* clear screen */
10, false);
- close_nointr_nofail(fd);
+ safe_close(fd);
return 0;
}
return fd;
r = ioctl(fd, VT_DISALLOCATE, u);
- close_nointr_nofail(fd);
+ safe_close(fd);
if (r >= 0)
return 0;
"\033[H" /* move home */
"\033[3J", /* clear screen including scrollback, requires Linux 2.6.40 */
10, false);
- close_nointr_nofail(fd);
+ safe_close(fd);
return 0;
}
-int copy_file(const char *from, const char *to, int flags) {
- _cleanup_close_ int fdf = -1;
- int r, fdt;
+int symlink_atomic(const char *from, const char *to) {
+ _cleanup_free_ char *t = NULL;
assert(from);
assert(to);
- fdf = open(from, O_RDONLY|O_CLOEXEC|O_NOCTTY);
- if (fdf < 0)
- return -errno;
+ t = tempfn_random(to);
+ if (!t)
+ return -ENOMEM;
- fdt = open(to, flags|O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644);
- if (fdt < 0)
+ if (symlink(from, t) < 0)
return -errno;
- for (;;) {
- char buf[PIPE_BUF];
- ssize_t n, k;
-
- n = read(fdf, buf, sizeof(buf));
- if (n < 0) {
- r = -errno;
-
- close_nointr(fdt);
- unlink(to);
-
- return r;
- }
+ if (rename(t, to) < 0) {
+ unlink_noerrno(t);
+ return -errno;
+ }
- if (n == 0)
- break;
+ return 0;
+}
- errno = 0;
- k = loop_write(fdt, buf, n, false);
- if (n != k) {
- r = k < 0 ? k : (errno ? -errno : -EIO);
+int mknod_atomic(const char *path, mode_t mode, dev_t dev) {
+ _cleanup_free_ char *t = NULL;
- close_nointr(fdt);
- unlink(to);
+ assert(path);
- return r;
- }
- }
+ t = tempfn_random(path);
+ if (!t)
+ return -ENOMEM;
- r = close_nointr(fdt);
+ if (mknod(t, mode, dev) < 0)
+ return -errno;
- if (r < 0) {
- unlink(to);
- return r;
+ if (rename(t, path) < 0) {
+ unlink_noerrno(t);
+ return -errno;
}
return 0;
}
-int symlink_atomic(const char *from, const char *to) {
- char *x;
- _cleanup_free_ char *t;
- const char *fn;
- size_t k;
- uint64_t u;
- unsigned i;
- int r;
+int mkfifo_atomic(const char *path, mode_t mode) {
+ _cleanup_free_ char *t = NULL;
- assert(from);
- assert(to);
+ assert(path);
- t = new(char, strlen(to) + 1 + 16 + 1);
+ t = tempfn_random(path);
if (!t)
return -ENOMEM;
- fn = basename(to);
- k = fn-to;
- memcpy(t, to, k);
- t[k] = '.';
- x = stpcpy(t+k+1, fn);
-
- u = random_u64();
- for (i = 0; i < 16; i++) {
- *(x++) = hexchar(u & 0xF);
- u >>= 4;
- }
-
- *x = 0;
-
- if (symlink(from, t) < 0)
+ if (mkfifo(t, mode) < 0)
return -errno;
- if (rename(t, to) < 0) {
- r = -errno;
- unlink(t);
- return r;
+ if (rename(t, path) < 0) {
+ unlink_noerrno(t);
+ return -errno;
}
return 0;
k = strspn(display+1, "0123456789");
- f = new(char, sizeof("/tmp/.X11-unix/X") + k);
+ f = new(char, strlen("/tmp/.X11-unix/X") + k + 1);
if (!f)
return -ENOMEM;
if (p)
return strdup(p->pw_name);
- if (asprintf(&r, "%lu", (unsigned long) uid) < 0)
+ if (asprintf(&r, UID_FMT, uid) < 0)
return NULL;
return r;
if (p)
return strdup(p->gr_name);
- if (asprintf(&r, "%lu", (unsigned long) gid) < 0)
+ if (asprintf(&r, GID_FMT, gid) < 0)
return NULL;
return r;
return 0;
}
-int in_search_path(const char *path, char **search) {
- char **i;
- _cleanup_free_ char *parent = NULL;
- int r;
-
- r = path_get_parent(path, &parent);
- if (r < 0)
- return r;
-
- STRV_FOREACH(i, search)
- if (path_equal(parent, *i))
- return 1;
-
- return 0;
-}
-
int get_files_in_directory(const char *path, char ***list) {
_cleanup_closedir_ DIR *d = NULL;
size_t bufsize = 0, n = 0;
if (signo > 0 && signo < _NSIG)
return signo;
}
- return -1;
+ return -EINVAL;
}
bool kexec_loaded(void) {
return loaded;
}
-int strdup_or_null(const char *a, char **b) {
- char *c;
-
- assert(b);
-
- if (!a) {
- *b = NULL;
- return 0;
- }
-
- c = strdup(a);
- if (!c)
- return -ENOMEM;
-
- *b = c;
- return 0;
-}
-
int prot_from_flags(int flags) {
switch (flags & O_ACCMODE) {
}
int fork_agent(pid_t *pid, const int except[], unsigned n_except, const char *path, ...) {
- pid_t parent_pid, agent_pid;
- int fd;
bool stdout_is_tty, stderr_is_tty;
+ pid_t parent_pid, agent_pid;
+ sigset_t ss, saved_ss;
unsigned n, i;
va_list ap;
char **l;
assert(pid);
assert(path);
- parent_pid = getpid();
-
/* Spawns a temporary TTY agent, making sure it goes away when
* we go away */
+ parent_pid = getpid();
+
+ /* First we temporarily block all signals, so that the new
+ * child has them blocked initially. This way, we can be sure
+ * that SIGTERMs are not lost we might send to the agent. */
+ assert_se(sigfillset(&ss) >= 0);
+ assert_se(sigprocmask(SIG_SETMASK, &ss, &saved_ss) >= 0);
+
agent_pid = fork();
- if (agent_pid < 0)
+ if (agent_pid < 0) {
+ assert_se(sigprocmask(SIG_SETMASK, &saved_ss, NULL) >= 0);
return -errno;
+ }
if (agent_pid != 0) {
+ assert_se(sigprocmask(SIG_SETMASK, &saved_ss, NULL) >= 0);
*pid = agent_pid;
return 0;
}
if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
_exit(EXIT_FAILURE);
+ /* Make sure we actually can kill the agent, if we need to, in
+ * case somebody invoked us from a shell script that trapped
+ * SIGTERM or so... */
+ reset_all_signal_handlers();
+ reset_signal_mask();
+
/* Check whether our parent died before we were able
- * to set the death signal */
+ * to set the death signal and unblock the signals */
if (getppid() != parent_pid)
_exit(EXIT_SUCCESS);
stderr_is_tty = isatty(STDERR_FILENO);
if (!stdout_is_tty || !stderr_is_tty) {
+ int fd;
+
/* Detach from stdout/stderr. and reopen
* /dev/tty for them. This is important to
* ensure that when systemctl is started via
/* Make /dev/console the controlling terminal and stdin/stdout/stderr */
- fd = acquire_terminal("/dev/console", false, true, true, (usec_t) -1);
+ fd = acquire_terminal("/dev/console", false, true, true, USEC_INFINITY);
if (fd < 0) {
log_error("Failed to acquire terminal: %s", strerror(-fd));
return fd;
assert(_h);
/* Take the user specified one */
- e = getenv("HOME");
- if (e) {
+ e = secure_getenv("HOME");
+ if (e && path_is_absolute(e)) {
h = strdup(e);
if (!h)
return -ENOMEM;
bool string_is_safe(const char *p) {
const char *t;
- assert(p);
+ if (!p)
+ return false;
for (t = p; *t; t++) {
if (*t > 0 && *t < ' ')
return false;
- if (strchr("\\\"\'", *t))
+ if (strchr("\\\"\'\0x7f", *t))
return false;
}
}
/**
- * Check if a string contains control characters.
- * Spaces and tabs are not considered control characters.
+ * Check if a string contains control characters. If 'ok' is non-NULL
+ * it may be a string containing additional CCs to be considered OK.
*/
-bool string_has_cc(const char *p) {
+bool string_has_cc(const char *p, const char *ok) {
const char *t;
assert(p);
- for (t = p; *t; t++)
- if (*t > 0 && *t < ' ' && *t != '\t')
+ for (t = p; *t; t++) {
+ if (ok && strchr(ok, *t))
+ continue;
+
+ if (*t > 0 && *t < ' ')
+ return true;
+
+ if (*t == 127)
return true;
+ }
return false;
}
const char *draw_special_char(DrawSpecialChar ch) {
static const char *draw_table[2][_DRAW_SPECIAL_CHAR_MAX] = {
+
/* UTF-8 */ {
- [DRAW_TREE_VERT] = "\342\224\202 ", /* │ */
+ [DRAW_TREE_VERTICAL] = "\342\224\202 ", /* │ */
[DRAW_TREE_BRANCH] = "\342\224\234\342\224\200", /* ├─ */
[DRAW_TREE_RIGHT] = "\342\224\224\342\224\200", /* └─ */
[DRAW_TREE_SPACE] = " ", /* */
- [DRAW_TRIANGULAR_BULLET] = "\342\200\243 ", /* ‣ */
- [DRAW_BLACK_CIRCLE] = "\342\227\217 ", /* ● */
+ [DRAW_TRIANGULAR_BULLET] = "\342\200\243", /* ‣ */
+ [DRAW_BLACK_CIRCLE] = "\342\227\217", /* ● */
+ [DRAW_ARROW] = "\342\206\222", /* → */
+ [DRAW_DASH] = "\342\200\223", /* – */
},
+
/* ASCII fallback */ {
- [DRAW_TREE_VERT] = "| ",
+ [DRAW_TREE_VERTICAL] = "| ",
[DRAW_TREE_BRANCH] = "|-",
[DRAW_TREE_RIGHT] = "`-",
[DRAW_TREE_SPACE] = " ",
- [DRAW_TRIANGULAR_BULLET] = "> ",
- [DRAW_BLACK_CIRCLE] = "* ",
+ [DRAW_TRIANGULAR_BULLET] = ">",
+ [DRAW_BLACK_CIRCLE] = "*",
+ [DRAW_ARROW] = "->",
+ [DRAW_DASH] = "-",
}
};
if (n != 6 || memcmp(contents, "Mains\n", 6))
continue;
- close_nointr_nofail(fd);
+ safe_close(fd);
fd = openat(device, "online", O_RDONLY|O_CLOEXEC|O_NOCTTY);
if (fd < 0) {
if (errno == ENOENT)
assert(mode);
assert(_f);
- if (!path_strv_canonicalize_absolute_uniq(search, root))
+ if (!path_strv_resolve_uniq(search, root))
return -ENOMEM;
STRV_FOREACH(i, search) {
_cleanup_free_ char *p = NULL;
FILE *f;
- p = strjoin(*i, "/", path, NULL);
+ if (root)
+ p = strjoin(root, *i, "/", path, NULL);
+ else
+ p = strjoin(*i, "/", path, NULL);
if (!p)
return -ENOMEM;
return r;
}
-void* greedy_realloc(void **p, size_t *allocated, size_t need) {
- size_t a;
+void* greedy_realloc(void **p, size_t *allocated, size_t need, size_t size) {
+ size_t a, newalloc;
void *q;
assert(p);
if (*allocated >= need)
return *p;
- a = MAX(64u, need * 2);
+ newalloc = MAX(need * 2, 64u / size);
+ a = newalloc * size;
/* check for overflows */
- if (a < need)
+ if (a < size * need)
return NULL;
q = realloc(*p, a);
return NULL;
*p = q;
- *allocated = a;
+ *allocated = newalloc;
return q;
}
-void* greedy_realloc0(void **p, size_t *allocated, size_t need) {
+void* greedy_realloc0(void **p, size_t *allocated, size_t need, size_t size) {
size_t prev;
uint8_t *q;
prev = *allocated;
- q = greedy_realloc(p, allocated, need);
+ q = greedy_realloc(p, allocated, need, size);
if (!q)
return NULL;
if (*allocated > prev)
- memzero(&q[prev], *allocated - prev);
+ memzero(q + prev * size, (*allocated - prev) * size);
return q;
}
int shall_restore_state(void) {
_cleanup_free_ char *line = NULL;
- char *w, *state;
+ const char *word, *state;
size_t l;
int r;
r = 1;
- FOREACH_WORD_QUOTED(w, l, line, state) {
+ FOREACH_WORD_QUOTED(word, l, line, state) {
const char *e;
char n[l+1];
int k;
- memcpy(n, w, l);
+ memcpy(n, word, l);
n[l] = 0;
e = startswith(n, "systemd.restore_state=");
int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value)) {
_cleanup_free_ char *line = NULL;
- char *w, *state;
+ const char *w, *state;
size_t l;
int r;
return 0;
}
-int namespace_open(pid_t pid, int *pidns_fd, int *mntns_fd, int *root_fd) {
- _cleanup_close_ int pidnsfd = -1, mntnsfd = -1;
- const char *pidns, *mntns, *root;
- int rfd;
+int namespace_open(pid_t pid, int *pidns_fd, int *mntns_fd, int *netns_fd, int *root_fd) {
+ _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, netnsfd = -1;
+ int rfd = -1;
assert(pid >= 0);
- assert(pidns_fd);
- assert(mntns_fd);
- assert(root_fd);
- mntns = procfs_file_alloca(pid, "ns/mnt");
- mntnsfd = open(mntns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
- if (mntnsfd < 0)
- return -errno;
+ if (mntns_fd) {
+ const char *mntns;
- pidns = procfs_file_alloca(pid, "ns/pid");
- pidnsfd = open(pidns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
- if (pidnsfd < 0)
- return -errno;
+ mntns = procfs_file_alloca(pid, "ns/mnt");
+ mntnsfd = open(mntns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
+ if (mntnsfd < 0)
+ return -errno;
+ }
- root = procfs_file_alloca(pid, "root");
- rfd = open(root, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
- if (rfd < 0)
- return -errno;
+ if (pidns_fd) {
+ const char *pidns;
+
+ pidns = procfs_file_alloca(pid, "ns/pid");
+ pidnsfd = open(pidns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
+ if (pidnsfd < 0)
+ return -errno;
+ }
- *pidns_fd = pidnsfd;
- *mntns_fd = mntnsfd;
- *root_fd = rfd;
- pidnsfd = -1;
- mntnsfd = -1;
+ if (netns_fd) {
+ const char *netns;
+
+ netns = procfs_file_alloca(pid, "ns/net");
+ netnsfd = open(netns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
+ if (netnsfd < 0)
+ return -errno;
+ }
+
+ if (root_fd) {
+ const char *root;
+
+ root = procfs_file_alloca(pid, "root");
+ rfd = open(root, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
+ if (rfd < 0)
+ return -errno;
+ }
+
+ if (pidns_fd)
+ *pidns_fd = pidnsfd;
+
+ if (mntns_fd)
+ *mntns_fd = mntnsfd;
+
+ if (netns_fd)
+ *netns_fd = netnsfd;
+
+ if (root_fd)
+ *root_fd = rfd;
+
+ pidnsfd = mntnsfd = netnsfd = -1;
return 0;
}
-int namespace_enter(int pidns_fd, int mntns_fd, int root_fd) {
- assert(pidns_fd >= 0);
- assert(mntns_fd >= 0);
- assert(root_fd >= 0);
+int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int root_fd) {
- if (setns(pidns_fd, CLONE_NEWPID) < 0)
- return -errno;
+ if (pidns_fd >= 0)
+ if (setns(pidns_fd, CLONE_NEWPID) < 0)
+ return -errno;
- if (setns(mntns_fd, CLONE_NEWNS) < 0)
- return -errno;
+ if (mntns_fd >= 0)
+ if (setns(mntns_fd, CLONE_NEWNS) < 0)
+ return -errno;
- if (fchdir(root_fd) < 0)
- return -errno;
+ if (netns_fd >= 0)
+ if (setns(netns_fd, CLONE_NEWNET) < 0)
+ return -errno;
- if (chroot(".") < 0)
- return -errno;
+ if (root_fd >= 0) {
+ if (fchdir(root_fd) < 0)
+ return -errno;
+
+ if (chroot(".") < 0)
+ return -errno;
+ }
if (setresgid(0, 0, 0) < 0)
return -errno;
+ if (setgroups(0, NULL) < 0)
+ return -errno;
+
if (setresuid(0, 0, 0) < 0)
return -errno;
s -= 16;
}
}
+
+int update_reboot_param_file(const char *param) {
+ int r = 0;
+
+ if (param) {
+
+ r = write_string_file(REBOOT_PARAM_FILE, param);
+ if (r < 0)
+ log_error("Failed to write reboot param to "
+ REBOOT_PARAM_FILE": %s", strerror(-r));
+ } else
+ unlink(REBOOT_PARAM_FILE);
+
+ return r;
+}
+
+int umount_recursive(const char *prefix, int flags) {
+ bool again;
+ int n = 0, r;
+
+ /* Try to umount everything recursively below a
+ * directory. Also, take care of stacked mounts, and keep
+ * unmounting them until they are gone. */
+
+ do {
+ _cleanup_fclose_ FILE *proc_self_mountinfo = NULL;
+
+ again = false;
+ r = 0;
+
+ proc_self_mountinfo = fopen("/proc/self/mountinfo", "re");
+ if (!proc_self_mountinfo)
+ return -errno;
+
+ for (;;) {
+ _cleanup_free_ char *path = NULL, *p = NULL;
+ int k;
+
+ k = fscanf(proc_self_mountinfo,
+ "%*s " /* (1) mount id */
+ "%*s " /* (2) parent id */
+ "%*s " /* (3) major:minor */
+ "%*s " /* (4) root */
+ "%ms " /* (5) mount point */
+ "%*s" /* (6) mount options */
+ "%*[^-]" /* (7) optional fields */
+ "- " /* (8) separator */
+ "%*s " /* (9) file system type */
+ "%*s" /* (10) mount source */
+ "%*s" /* (11) mount options 2 */
+ "%*[^\n]", /* some rubbish at the end */
+ &path);
+ if (k != 1) {
+ if (k == EOF)
+ break;
+
+ continue;
+ }
+
+ p = cunescape(path);
+ if (!p)
+ return -ENOMEM;
+
+ if (!path_startswith(p, prefix))
+ continue;
+
+ if (umount2(p, flags) < 0) {
+ r = -errno;
+ continue;
+ }
+
+ again = true;
+ n++;
+
+ break;
+ }
+
+ } while (again);
+
+ return r ? r : n;
+}
+
+int bind_remount_recursive(const char *prefix, bool ro) {
+ _cleanup_set_free_free_ Set *done = NULL;
+ _cleanup_free_ char *cleaned = NULL;
+ int r;
+
+ /* Recursively remount a directory (and all its submounts)
+ * read-only or read-write. If the directory is already
+ * mounted, we reuse the mount and simply mark it
+ * MS_BIND|MS_RDONLY (or remove the MS_RDONLY for read-write
+ * operation). If it isn't we first make it one. Afterwards we
+ * apply MS_BIND|MS_RDONLY (or remove MS_RDONLY) to all
+ * submounts we can access, too. When mounts are stacked on
+ * the same mount point we only care for each individual
+ * "top-level" mount on each point, as we cannot
+ * influence/access the underlying mounts anyway. We do not
+ * have any effect on future submounts that might get
+ * propagated, they migt be writable. This includes future
+ * submounts that have been triggered via autofs. */
+
+ cleaned = strdup(prefix);
+ if (!cleaned)
+ return -ENOMEM;
+
+ path_kill_slashes(cleaned);
+
+ done = set_new(&string_hash_ops);
+ if (!done)
+ return -ENOMEM;
+
+ for (;;) {
+ _cleanup_fclose_ FILE *proc_self_mountinfo = NULL;
+ _cleanup_set_free_free_ Set *todo = NULL;
+ bool top_autofs = false;
+ char *x;
+
+ todo = set_new(&string_hash_ops);
+ if (!todo)
+ return -ENOMEM;
+
+ proc_self_mountinfo = fopen("/proc/self/mountinfo", "re");
+ if (!proc_self_mountinfo)
+ return -errno;
+
+ for (;;) {
+ _cleanup_free_ char *path = NULL, *p = NULL, *type = NULL;
+ int k;
+
+ k = fscanf(proc_self_mountinfo,
+ "%*s " /* (1) mount id */
+ "%*s " /* (2) parent id */
+ "%*s " /* (3) major:minor */
+ "%*s " /* (4) root */
+ "%ms " /* (5) mount point */
+ "%*s" /* (6) mount options (superblock) */
+ "%*[^-]" /* (7) optional fields */
+ "- " /* (8) separator */
+ "%ms " /* (9) file system type */
+ "%*s" /* (10) mount source */
+ "%*s" /* (11) mount options (bind mount) */
+ "%*[^\n]", /* some rubbish at the end */
+ &path,
+ &type);
+ if (k != 2) {
+ if (k == EOF)
+ break;
+
+ continue;
+ }
+
+ p = cunescape(path);
+ if (!p)
+ return -ENOMEM;
+
+ /* Let's ignore autofs mounts. If they aren't
+ * triggered yet, we want to avoid triggering
+ * them, as we don't make any guarantees for
+ * future submounts anyway. If they are
+ * already triggered, then we will find
+ * another entry for this. */
+ if (streq(type, "autofs")) {
+ top_autofs = top_autofs || path_equal(cleaned, p);
+ continue;
+ }
+
+ if (path_startswith(p, cleaned) &&
+ !set_contains(done, p)) {
+
+ r = set_consume(todo, p);
+ p = NULL;
+
+ if (r == -EEXIST)
+ continue;
+ if (r < 0)
+ return r;
+ }
+ }
+
+ /* If we have no submounts to process anymore and if
+ * the root is either already done, or an autofs, we
+ * are done */
+ if (set_isempty(todo) &&
+ (top_autofs || set_contains(done, cleaned)))
+ return 0;
+
+ if (!set_contains(done, cleaned) &&
+ !set_contains(todo, cleaned)) {
+ /* The prefix directory itself is not yet a
+ * mount, make it one. */
+ 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)
+ return -errno;
+
+ x = strdup(cleaned);
+ if (!x)
+ return -ENOMEM;
+
+ r = set_consume(done, x);
+ if (r < 0)
+ return r;
+ }
+
+ while ((x = set_steal_first(todo))) {
+
+ r = set_consume(done, x);
+ if (r == -EEXIST)
+ continue;
+ if (r < 0)
+ return r;
+
+ if (mount(NULL, x, NULL, MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0) {
+
+ /* Deal with mount points that are
+ * obstructed by a later mount */
+
+ if (errno != ENOENT)
+ return -errno;
+ }
+
+ }
+ }
+}
+
+int fflush_and_check(FILE *f) {
+ assert(f);
+
+ errno = 0;
+ fflush(f);
+
+ if (ferror(f))
+ return errno ? -errno : -EIO;
+
+ return 0;
+}
+
+char *tempfn_xxxxxx(const char *p) {
+ const char *fn;
+ char *t;
+ size_t k;
+
+ assert(p);
+
+ t = new(char, strlen(p) + 1 + 6 + 1);
+ if (!t)
+ return NULL;
+
+ fn = basename(p);
+ k = fn - p;
+
+ strcpy(stpcpy(stpcpy(mempcpy(t, p, k), "."), fn), "XXXXXX");
+
+ return t;
+}
+
+char *tempfn_random(const char *p) {
+ const char *fn;
+ char *t, *x;
+ uint64_t u;
+ size_t k;
+ unsigned i;
+
+ assert(p);
+
+ t = new(char, strlen(p) + 1 + 16 + 1);
+ if (!t)
+ return NULL;
+
+ fn = basename(p);
+ k = fn - p;
+
+ x = stpcpy(stpcpy(mempcpy(t, p, k), "."), fn);
+
+ u = random_u64();
+ for (i = 0; i < 16; i++) {
+ *(x++) = hexchar(u & 0xF);
+ u >>= 4;
+ }
+
+ *x = 0;
+
+ return t;
+}
+
+/* make sure the hostname is not "localhost" */
+bool is_localhost(const char *hostname) {
+ assert(hostname);
+
+ /* This tries to identify local host and domain names
+ * described in RFC6761 plus the redhatism of .localdomain */
+
+ return streq(hostname, "localhost") ||
+ streq(hostname, "localhost.") ||
+ streq(hostname, "localdomain.") ||
+ streq(hostname, "localdomain") ||
+ endswith(hostname, ".localhost") ||
+ endswith(hostname, ".localhost.") ||
+ endswith(hostname, ".localdomain") ||
+ endswith(hostname, ".localdomain.");
+}
+
+int take_password_lock(const char *root) {
+
+ struct flock flock = {
+ .l_type = F_WRLCK,
+ .l_whence = SEEK_SET,
+ .l_start = 0,
+ .l_len = 0,
+ };
+
+ const char *path;
+ int fd, r;
+
+ /* This is roughly the same as lckpwdf(), but not as awful. We
+ * don't want to use alarm() and signals, hence we implement
+ * our own trivial version of this.
+ *
+ * Note that shadow-utils also takes per-database locks in
+ * addition to lckpwdf(). However, we don't given that they
+ * are redundant as they they invoke lckpwdf() first and keep
+ * it during everything they do. The per-database locks are
+ * awfully racy, and thus we just won't do them. */
+
+ if (root)
+ path = strappenda(root, "/etc/.pwd.lock");
+ else
+ path = "/etc/.pwd.lock";
+
+ fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600);
+ if (fd < 0)
+ return -errno;
+
+ r = fcntl(fd, F_SETLKW, &flock);
+ if (r < 0) {
+ safe_close(fd);
+ return -errno;
+ }
+
+ return fd;
+}
+
+int is_symlink(const char *path) {
+ struct stat info;
+
+ if (lstat(path, &info) < 0)
+ return -errno;
+
+ if (S_ISLNK(info.st_mode))
+ return 1;
+
+ return 0;
+}
+
+int unquote_first_word(const char **p, char **ret) {
+ _cleanup_free_ char *s = NULL;
+ size_t allocated = 0, sz = 0;
+
+ enum {
+ START,
+ VALUE,
+ VALUE_ESCAPE,
+ SINGLE_QUOTE,
+ SINGLE_QUOTE_ESCAPE,
+ DOUBLE_QUOTE,
+ DOUBLE_QUOTE_ESCAPE,
+ SPACE,
+ } state = START;
+
+ assert(p);
+ assert(*p);
+ assert(ret);
+
+ /* Parses the first word of a string, and returns it in
+ * *ret. Removes all quotes in the process. When parsing fails
+ * (because of an uneven number of quotes or similar), leaves
+ * the pointer *p at the first invalid character. */
+
+ for (;;) {
+ char c = **p;
+
+ switch (state) {
+
+ case START:
+ if (c == 0)
+ goto finish;
+ else if (strchr(WHITESPACE, c))
+ break;
+
+ state = VALUE;
+ /* fallthrough */
+
+ case VALUE:
+ if (c == 0)
+ goto finish;
+ else if (c == '\'')
+ state = SINGLE_QUOTE;
+ else if (c == '\\')
+ state = VALUE_ESCAPE;
+ else if (c == '\"')
+ state = DOUBLE_QUOTE;
+ else if (strchr(WHITESPACE, c))
+ state = SPACE;
+ else {
+ if (!GREEDY_REALLOC(s, allocated, sz+2))
+ return -ENOMEM;
+
+ s[sz++] = c;
+ }
+
+ break;
+
+ case VALUE_ESCAPE:
+ if (c == 0)
+ return -EINVAL;
+
+ if (!GREEDY_REALLOC(s, allocated, sz+2))
+ return -ENOMEM;
+
+ s[sz++] = c;
+ state = VALUE;
+
+ break;
+
+ case SINGLE_QUOTE:
+ if (c == 0)
+ return -EINVAL;
+ else if (c == '\'')
+ state = VALUE;
+ else if (c == '\\')
+ state = SINGLE_QUOTE_ESCAPE;
+ else {
+ if (!GREEDY_REALLOC(s, allocated, sz+2))
+ return -ENOMEM;
+
+ s[sz++] = c;
+ }
+
+ break;
+
+ case SINGLE_QUOTE_ESCAPE:
+ if (c == 0)
+ return -EINVAL;
+
+ if (!GREEDY_REALLOC(s, allocated, sz+2))
+ return -ENOMEM;
+
+ s[sz++] = c;
+ state = SINGLE_QUOTE;
+ break;
+
+ case DOUBLE_QUOTE:
+ if (c == 0)
+ return -EINVAL;
+ else if (c == '\"')
+ state = VALUE;
+ else if (c == '\\')
+ state = DOUBLE_QUOTE_ESCAPE;
+ else {
+ if (!GREEDY_REALLOC(s, allocated, sz+2))
+ return -ENOMEM;
+
+ s[sz++] = c;
+ }
+
+ break;
+
+ case DOUBLE_QUOTE_ESCAPE:
+ if (c == 0)
+ return -EINVAL;
+
+ if (!GREEDY_REALLOC(s, allocated, sz+2))
+ return -ENOMEM;
+
+ s[sz++] = c;
+ state = DOUBLE_QUOTE;
+ break;
+
+ case SPACE:
+ if (c == 0)
+ goto finish;
+ if (!strchr(WHITESPACE, c))
+ goto finish;
+
+ break;
+ }
+
+ (*p) ++;
+ }
+
+finish:
+ if (!s) {
+ *ret = NULL;
+ return 0;
+ }
+
+ s[sz] = 0;
+ *ret = s;
+ s = NULL;
+
+ return 1;
+}
+
+int unquote_many_words(const char **p, ...) {
+ va_list ap;
+ char **l;
+ int n = 0, i, c, r;
+
+ /* Parses a number of words from a string, stripping any
+ * quotes if necessary. */
+
+ assert(p);
+
+ /* Count how many words are expected */
+ va_start(ap, p);
+ for (;;) {
+ if (!va_arg(ap, char **))
+ break;
+ n++;
+ }
+ va_end(ap);
+
+ if (n <= 0)
+ return 0;
+
+ /* Read all words into a temporary array */
+ l = newa0(char*, n);
+ for (c = 0; c < n; c++) {
+
+ r = unquote_first_word(p, &l[c]);
+ if (r < 0) {
+ int j;
+
+ for (j = 0; j < c; j++)
+ free(l[j]);
+
+ return r;
+ }
+
+ if (r == 0)
+ break;
+ }
+
+ /* If we managed to parse all words, return them in the passed
+ * in parameters */
+ va_start(ap, p);
+ for (i = 0; i < n; i++) {
+ char **v;
+
+ v = va_arg(ap, char **);
+ assert(v);
+
+ *v = l[i];
+ }
+ va_end(ap);
+
+ return c;
+}
+
+int free_and_strdup(char **p, const char *s) {
+ char *t;
+
+ assert(p);
+
+ /* Replaces a string pointer with an strdup()ed new string,
+ * possibly freeing the old one. */
+
+ if (s) {
+ t = strdup(s);
+ if (!t)
+ return -ENOMEM;
+ } else
+ t = NULL;
+
+ free(*p);
+ *p = t;
+
+ return 0;
+}