#include <linux/kd.h>
#include <dlfcn.h>
#include <sys/wait.h>
-#include <sys/capability.h>
#include <sys/time.h>
-#include <linux/rtc.h>
#include <glob.h>
#include <grp.h>
#include <sys/mman.h>
-#include <sys/statvfs.h>
+#include <sys/vfs.h>
+#include <linux/magic.h>
#include "macro.h"
#include "util.h"
#include "log.h"
#include "strv.h"
#include "label.h"
+#include "path-util.h"
#include "exit-status.h"
#include "hashmap.h"
return (char*) current;
}
-char **split_path_and_make_absolute(const char *p) {
- char **l;
- assert(p);
-
- if (!(l = strv_split(p, ":")))
- return NULL;
-
- if (!strv_path_make_absolute_cwd(l)) {
- strv_free(l);
- return NULL;
- }
-
- return l;
-}
-
int get_parent_of_pid(pid_t pid, pid_t *_ppid) {
int r;
FILE *f;
assert(fn);
assert(line);
- if (!(f = fopen(fn, "we")))
+ f = fopen(fn, "we");
+ if (!f)
return -errno;
errno = 0;
continue;
if (!(u = normalize_env_assignment(p))) {
- log_error("Out of memory");
- r = -ENOMEM;
+ r = log_oom();
goto finish;
}
free(u);
if (!t) {
- log_error("Out of memory");
- r = -ENOMEM;
+ r = log_oom();
goto finish;
}
if (h < 0)
return h;
- r = join("[", t, "]", NULL);
+ r = strjoin("[", t, "]", NULL);
free(t);
if (!r)
return 0;
}
-int parent_of_path(const char *path, char **_r) {
- const char *e, *a = NULL, *b = NULL, *p;
- char *r;
- bool slash = false;
-
- assert(path);
- assert(_r);
-
- if (!*path)
- return -EINVAL;
-
- for (e = path; *e; e++) {
-
- if (!slash && *e == '/') {
- a = b;
- b = e;
- slash = true;
- } else if (slash && *e != '/')
- slash = false;
- }
-
- if (*(e-1) == '/')
- p = a;
- else
- p = b;
-
- if (!p)
- return -EINVAL;
-
- if (p == path)
- r = strdup("/");
- else
- r = strndup(path, p-path);
-
- if (!r)
- return -ENOMEM;
-
- *_r = r;
- return 0;
-}
-
-
-char *file_name_from_path(const char *p) {
- char *r;
-
- assert(p);
-
- if ((r = strrchr(p, '/')))
- return r + 1;
-
- return (char*) p;
-}
-
-bool path_is_absolute(const char *p) {
- assert(p);
-
- return p[0] == '/';
-}
-
-bool is_path(const char *p) {
-
- return !!strchr(p, '/');
-}
-
-char *path_make_absolute(const char *p, const char *prefix) {
- assert(p);
-
- /* Makes every item in the list an absolute path by prepending
- * the prefix, if specified and necessary */
-
- if (path_is_absolute(p) || !prefix)
- return strdup(p);
-
- return join(prefix, "/", p, NULL);
-}
-
-char *path_make_absolute_cwd(const char *p) {
- char *cwd, *r;
-
- assert(p);
-
- /* Similar to path_make_absolute(), but prefixes with the
- * current working directory. */
-
- if (path_is_absolute(p))
- return strdup(p);
-
- if (!(cwd = get_current_dir_name()))
- return NULL;
-
- r = path_make_absolute(p, cwd);
- free(cwd);
-
- return r;
-}
-
-char **strv_path_make_absolute_cwd(char **l) {
- char **s;
-
- /* Goes through every item in the string list and makes it
- * absolute. This works in place and won't rollback any
- * changes on failure. */
-
- STRV_FOREACH(s, l) {
- char *t;
-
- if (!(t = path_make_absolute_cwd(*s)))
- return NULL;
-
- free(*s);
- *s = t;
- }
-
- return l;
-}
-
-char **strv_path_canonicalize(char **l) {
- char **s;
- unsigned k = 0;
- bool enomem = false;
-
- if (strv_isempty(l))
- return l;
-
- /* Goes through every item in the string list and canonicalize
- * the path. This works in place and won't rollback any
- * changes on failure. */
-
- STRV_FOREACH(s, l) {
- char *t, *u;
-
- t = path_make_absolute_cwd(*s);
- free(*s);
-
- if (!t) {
- enomem = true;
- continue;
- }
-
- errno = 0;
- u = canonicalize_file_name(t);
- free(t);
-
- if (!u) {
- if (errno == ENOMEM || !errno)
- enomem = true;
-
- continue;
- }
-
- l[k++] = u;
- }
-
- l[k] = NULL;
-
- if (enomem)
- return NULL;
-
- return l;
-}
-
-char **strv_path_remove_empty(char **l) {
- char **f, **t;
-
- if (!l)
- return NULL;
-
- for (f = t = l; *f; f++) {
-
- if (dir_is_empty(*f) > 0) {
- free(*f);
- continue;
- }
-
- *(t++) = *f;
- }
-
- *t = NULL;
- return l;
-}
-
int reset_all_signal_handlers(void) {
int sig;
return r;
}
-char *cunescape_length(const char *s, size_t length) {
+char *cunescape_length_with_prefix(const char *s, size_t length, const char *prefix) {
char *r, *t;
const char *f;
+ size_t pl;
assert(s);
- /* Undoes C style string escaping */
+ /* Undoes C style string escaping, and optionally prefixes it. */
- r = new(char, length+1);
+ pl = prefix ? strlen(prefix) : 0;
+
+ r = new(char, pl+length+1);
if (!r)
return r;
- for (f = s, t = r; f < s + length; f++) {
+ if (prefix)
+ memcpy(r, prefix, pl);
+
+ for (f = s, t = r + pl; f < s + length; f++) {
if (*f != '\\') {
*(t++) = *f;
return r;
}
+char *cunescape_length(const char *s, size_t length) {
+ return cunescape_length_with_prefix(s, length, NULL);
+}
+
char *cunescape(const char *s) {
+ assert(s);
+
return cunescape_length(s, strlen(s));
}
return r;
}
-char *path_kill_slashes(char *path) {
- char *f, *t;
- bool slash = false;
-
- /* Removes redundant inner and trailing slashes. Modifies the
- * passed string in-place.
- *
- * ///foo///bar/ becomes /foo/bar
- */
-
- for (f = path, t = path; *f; f++) {
-
- if (*f == '/') {
- slash = true;
- continue;
- }
-
- if (slash) {
- slash = false;
- *(t++) = '/';
- }
-
- *(t++) = *f;
- }
-
- /* Special rule, if we are talking of the root directory, a
- trailing slash is good */
-
- if (t == path && slash)
- *(t++) = '/';
-
- *t = 0;
- return path;
-}
-
-bool path_startswith(const char *path, const char *prefix) {
- assert(path);
- assert(prefix);
-
- if ((path[0] == '/') != (prefix[0] == '/'))
- return false;
-
- for (;;) {
- size_t a, b;
-
- path += strspn(path, "/");
- prefix += strspn(prefix, "/");
-
- if (*prefix == 0)
- return true;
-
- if (*path == 0)
- return false;
-
- a = strcspn(path, "/");
- b = strcspn(prefix, "/");
-
- if (a != b)
- return false;
-
- if (memcmp(path, prefix, a) != 0)
- return false;
-
- path += a;
- prefix += b;
- }
-}
-
-bool path_equal(const char *a, const char *b) {
- assert(a);
- assert(b);
-
- if ((a[0] == '/') != (b[0] == '/'))
- return false;
-
- for (;;) {
- size_t j, k;
-
- a += strspn(a, "/");
- b += strspn(b, "/");
-
- if (*a == 0 && *b == 0)
- return true;
-
- if (*a == 0 || *b == 0)
- return false;
-
- j = strcspn(a, "/");
- k = strcspn(b, "/");
-
- if (j != k)
- return false;
-
- if (memcmp(a, b, j) != 0)
- return false;
-
- a += j;
- b += k;
- }
-}
-
char *ascii_strlower(char *t) {
char *p;
return t;
}
-bool ignore_file(const char *filename) {
+static bool ignore_file_allow_backup(const char *filename) {
assert(filename);
return
streq(filename, "lost+found") ||
streq(filename, "aquota.user") ||
streq(filename, "aquota.group") ||
- endswith(filename, "~") ||
endswith(filename, ".rpmnew") ||
endswith(filename, ".rpmsave") ||
endswith(filename, ".rpmorig") ||
endswith(filename, ".swp");
}
+bool ignore_file(const char *filename) {
+ assert(filename);
+
+ if (endswith(filename, "~"))
+ return false;
+
+ return ignore_file_allow_backup(filename);
+}
+
int fd_nonblock(int fd, bool nonblock) {
int flags;
*/
for (;;) {
- if ((fd = open(name, mode)) >= 0)
+ fd = open(name, mode);
+ if (fd >= 0)
break;
if (errno != EIO)
return -errno;
+ /* Max 1s in total */
if (c >= 20)
return -errno;
if (fd < 0)
return -errno;
- if ((r = isatty(fd)) < 0) {
+ r = isatty(fd);
+ if (r < 0) {
close_nointr_nofail(fd);
return -errno;
}
}
}
-int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocstty_eperm) {
- int fd = -1, notify = -1, r, wd = -1;
+int acquire_terminal(
+ const char *name,
+ bool fail,
+ bool force,
+ bool ignore_tiocstty_eperm,
+ usec_t timeout) {
+
+ int fd = -1, notify = -1, r = 0, wd = -1;
+ usec_t ts = 0;
+ struct sigaction sa_old, sa_new;
assert(name);
* 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)
+ ts = now(CLOCK_MONOTONIC);
+
if (!fail && !force) {
- if ((notify = inotify_init1(IN_CLOEXEC)) < 0) {
+ notify = inotify_init1(IN_CLOEXEC | (timeout != (usec_t) -1 ? IN_NONBLOCK : 0));
+ if (notify < 0) {
r = -errno;
goto fail;
}
- if ((wd = inotify_add_watch(notify, name, IN_CLOSE)) < 0) {
+ wd = inotify_add_watch(notify, name, IN_CLOSE);
+ if (wd < 0) {
r = -errno;
goto fail;
}
}
for (;;) {
- if (notify >= 0)
- if ((r = flush_fd(notify)) < 0)
+ if (notify >= 0) {
+ r = flush_fd(notify);
+ if (r < 0)
goto fail;
+ }
/* We pass here O_NOCTTY only so that we can check the return
* value TIOCSCTTY and have a reliable way to figure out if we
* successfully became the controlling process of the tty */
- if ((fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC)) < 0)
+ fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
+ if (fd < 0)
return fd;
+ /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed
+ * if we already own the tty. */
+ zero(sa_new);
+ sa_new.sa_handler = SIG_IGN;
+ sa_new.sa_flags = SA_RESTART;
+ assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0);
+
/* First, try to get the tty */
- r = ioctl(fd, TIOCSCTTY, force);
+ if (ioctl(fd, TIOCSCTTY, force) < 0)
+ r = -errno;
+
+ assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0);
/* Sometimes it makes sense to ignore TIOCSCTTY
* returning EPERM, i.e. when very likely we already
* are have this controlling terminal. */
- if (r < 0 && errno == EPERM && ignore_tiocstty_eperm)
+ if (r < 0 && r == -EPERM && ignore_tiocstty_eperm)
r = 0;
- if (r < 0 && (force || fail || errno != EPERM)) {
- r = -errno;
+ if (r < 0 && (force || fail || r != -EPERM)) {
goto fail;
}
ssize_t l;
struct inotify_event *e;
- if ((l = read(notify, inotify_buffer, sizeof(inotify_buffer))) < 0) {
+ if (timeout != (usec_t) -1) {
+ usec_t n;
+
+ n = now(CLOCK_MONOTONIC);
+ if (ts + timeout < n) {
+ r = -ETIMEDOUT;
+ goto fail;
+ }
+
+ r = fd_wait_for_event(fd, POLLIN, ts + timeout - n);
+ if (r < 0)
+ goto fail;
+
+ if (r == 0) {
+ r = -ETIMEDOUT;
+ goto fail;
+ }
+ }
+
+ l = read(notify, inotify_buffer, sizeof(inotify_buffer));
+ if (l < 0) {
- if (errno == EINTR)
+ if (errno == EINTR || errno == EAGAIN)
continue;
r = -errno;
return n;
}
-int path_is_mount_point(const char *t, bool allow_symlink) {
- struct stat a, b;
- char *parent;
- int r;
-
- if (allow_symlink)
- r = stat(t, &a);
- else
- r = lstat(t, &a);
-
- if (r < 0) {
- if (errno == ENOENT)
- return 0;
-
- return -errno;
- }
-
- r = parent_of_path(t, &parent);
- if (r < 0)
- return r;
-
- r = lstat(parent, &b);
- free(parent);
-
- if (r < 0)
- return -errno;
-
- return a.st_dev != b.st_dev;
-}
-
int parse_usec(const char *t, usec_t *usec) {
static const struct {
const char *suffix;
{ "m", USEC_PER_MINUTE },
{ "usec", 1ULL },
{ "us", 1ULL },
- { "", USEC_PER_SEC },
+ { "", USEC_PER_SEC }, /* default is sec */
};
const char *p;
return 0;
}
-int parse_bytes(const char *t, off_t *bytes) {
+int parse_nsec(const char *t, nsec_t *nsec) {
static const struct {
const char *suffix;
- off_t factor;
+ nsec_t nsec;
} table[] = {
- { "B", 1 },
- { "K", 1024ULL },
- { "M", 1024ULL*1024ULL },
- { "G", 1024ULL*1024ULL*1024ULL },
- { "T", 1024ULL*1024ULL*1024ULL*1024ULL },
- { "P", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL },
- { "E", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL*1024ULL },
- { "", 1 },
+ { "sec", NSEC_PER_SEC },
+ { "s", NSEC_PER_SEC },
+ { "min", NSEC_PER_MINUTE },
+ { "hr", NSEC_PER_HOUR },
+ { "h", NSEC_PER_HOUR },
+ { "d", NSEC_PER_DAY },
+ { "w", NSEC_PER_WEEK },
+ { "msec", NSEC_PER_MSEC },
+ { "ms", NSEC_PER_MSEC },
+ { "m", NSEC_PER_MINUTE },
+ { "usec", NSEC_PER_USEC },
+ { "us", NSEC_PER_USEC },
+ { "nsec", 1ULL },
+ { "ns", 1ULL },
+ { "", 1ULL }, /* default is nsec */
};
const char *p;
- off_t r = 0;
+ nsec_t r = 0;
assert(t);
- assert(bytes);
+ assert(nsec);
p = t;
do {
for (i = 0; i < ELEMENTSOF(table); i++)
if (startswith(e, table[i].suffix)) {
- r += (off_t) l * table[i].factor;
+ r += (nsec_t) l * table[i].nsec;
p = e + strlen(table[i].suffix);
break;
}
} while (*p != 0);
- *bytes = r;
+ *nsec = r;
return 0;
}
-int make_stdio(int fd) {
- int r, s, t;
-
- assert(fd >= 0);
-
- r = dup2(fd, STDIN_FILENO);
- s = dup2(fd, STDOUT_FILENO);
- t = dup2(fd, STDERR_FILENO);
-
- if (fd >= 3)
- close_nointr_nofail(fd);
-
- if (r < 0 || s < 0 || t < 0)
+int parse_bytes(const char *t, off_t *bytes) {
+ static const struct {
+ const char *suffix;
+ off_t factor;
+ } table[] = {
+ { "B", 1 },
+ { "K", 1024ULL },
+ { "M", 1024ULL*1024ULL },
+ { "G", 1024ULL*1024ULL*1024ULL },
+ { "T", 1024ULL*1024ULL*1024ULL*1024ULL },
+ { "P", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL },
+ { "E", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL*1024ULL },
+ { "", 1 },
+ };
+
+ const char *p;
+ off_t r = 0;
+
+ assert(t);
+ assert(bytes);
+
+ p = t;
+ do {
+ long long l;
+ char *e;
+ unsigned i;
+
+ errno = 0;
+ l = strtoll(p, &e, 10);
+
+ if (errno != 0)
+ return -errno;
+
+ if (l < 0)
+ return -ERANGE;
+
+ if (e == p)
+ return -EINVAL;
+
+ e += strspn(e, WHITESPACE);
+
+ for (i = 0; i < ELEMENTSOF(table); i++)
+ if (startswith(e, table[i].suffix)) {
+ r += (off_t) l * table[i].factor;
+ p = e + strlen(table[i].suffix);
+ break;
+ }
+
+ if (i >= ELEMENTSOF(table))
+ return -EINVAL;
+
+ } while (*p != 0);
+
+ *bytes = r;
+
+ return 0;
+}
+
+int make_stdio(int fd) {
+ int r, s, t;
+
+ assert(fd >= 0);
+
+ r = dup2(fd, STDIN_FILENO);
+ s = dup2(fd, STDOUT_FILENO);
+ t = dup2(fd, STDERR_FILENO);
+
+ if (fd >= 3)
+ close_nointr_nofail(fd);
+
+ if (r < 0 || s < 0 || t < 0)
return -errno;
fd_cloexec(STDIN_FILENO, false);
int make_null_stdio(void) {
int null_fd;
- if ((null_fd = open("/dev/null", O_RDWR|O_NOCTTY)) < 0)
+ null_fd = open("/dev/null", O_RDWR|O_NOCTTY);
+ if (null_fd < 0)
return -errno;
return make_stdio(null_fd);
uint64_t ull;
ssize_t r;
- if ((fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY)) < 0)
+ fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY);
+ if (fd < 0)
goto fallback;
r = loop_read(fd, &ull, sizeof(ull), true);
assert_se(uname(&u) >= 0);
- if (u.nodename[0])
+ if (!isempty(u.nodename) && !streq(u.nodename, "(none)"))
return strdup(u.nodename);
return strdup(u.sysname);
}
-char* getlogname_malloc(void) {
- uid_t uid;
+bool hostname_is_set(void) {
+ struct utsname u;
+
+ assert_se(uname(&u) >= 0);
+
+ return !isempty(u.nodename) && !streq(u.nodename, "(none)");
+}
+
+static char *lookup_uid(uid_t uid) {
long bufsize;
char *buf, *name;
struct passwd pwbuf, *pw = NULL;
- struct stat st;
-
- if (isatty(STDIN_FILENO) && fstat(STDIN_FILENO, &st) >= 0)
- uid = st.st_uid;
- else
- uid = getuid();
/* Shortcut things to avoid NSS lookups */
if (uid == 0)
return strdup("root");
- if ((bufsize = sysconf(_SC_GETPW_R_SIZE_MAX)) <= 0)
+ bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
+ if (bufsize <= 0)
bufsize = 4096;
- if (!(buf = malloc(bufsize)))
+ buf = malloc(bufsize);
+ if (!buf)
return NULL;
if (getpwuid_r(uid, &pwbuf, buf, bufsize, &pw) == 0 && pw) {
return name;
}
+char* getlogname_malloc(void) {
+ uid_t uid;
+ struct stat st;
+
+ if (isatty(STDIN_FILENO) && fstat(STDIN_FILENO, &st) >= 0)
+ uid = st.st_uid;
+ else
+ uid = getuid();
+
+ return lookup_uid(uid);
+}
+
+char *getusername_malloc(void) {
+ const char *e;
+
+ e = getenv("USER");
+ if (e)
+ return strdup(e);
+
+ return lookup_uid(getuid());
+}
+
int getttyname_malloc(int fd, char **r) {
char path[PATH_MAX], *c;
int k;
return 0;
}
-static int rm_rf_children(int fd, bool only_dirs, bool honour_sticky) {
+int rm_rf_children_dangerous(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev) {
DIR *d;
int ret = 0;
assert(fd >= 0);
/* This returns the first error we run into, but nevertheless
- * tries to go on */
+ * tries to go on. This closes the passed fd. */
- if (!(d = fdopendir(fd))) {
+ d = fdopendir(fd);
+ if (!d) {
close_nointr_nofail(fd);
return errno == ENOENT ? 0 : -errno;
for (;;) {
struct dirent buf, *de;
- bool is_dir, keep_around = false;
+ bool is_dir, keep_around;
+ struct stat st;
int r;
- if ((r = readdir_r(d, &buf, &de)) != 0) {
- if (ret == 0)
- ret = -r;
+ r = readdir_r(d, &buf, &de);
+ if (r != 0 && ret == 0) {
+ ret = -r;
break;
}
if (streq(de->d_name, ".") || streq(de->d_name, ".."))
continue;
- if (de->d_type == DT_UNKNOWN) {
- struct stat st;
-
+ if (de->d_type == DT_UNKNOWN ||
+ honour_sticky ||
+ (de->d_type == DT_DIR && root_dev)) {
if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
if (ret == 0 && errno != ENOENT)
ret = -errno;
continue;
}
- if (honour_sticky)
- keep_around =
- (st.st_uid == 0 || st.st_uid == getuid()) &&
- (st.st_mode & S_ISVTX);
-
is_dir = S_ISDIR(st.st_mode);
-
+ keep_around =
+ honour_sticky &&
+ (st.st_uid == 0 || st.st_uid == getuid()) &&
+ (st.st_mode & S_ISVTX);
} else {
- if (honour_sticky) {
- struct stat st;
-
- if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
- if (ret == 0 && errno != ENOENT)
- ret = -errno;
- continue;
- }
-
- keep_around =
- (st.st_uid == 0 || st.st_uid == getuid()) &&
- (st.st_mode & S_ISVTX);
- }
-
is_dir = de->d_type == DT_DIR;
+ keep_around = false;
}
if (is_dir) {
int subdir_fd;
- subdir_fd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
+ /* if root_dev is set, remove subdirectories only, if device is same as dir */
+ if (root_dev && st.st_dev != root_dev->st_dev)
+ continue;
+
+ subdir_fd = openat(fd, de->d_name,
+ O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
if (subdir_fd < 0) {
if (ret == 0 && errno != ENOENT)
ret = -errno;
continue;
}
- if ((r = rm_rf_children(subdir_fd, only_dirs, honour_sticky)) < 0) {
- if (ret == 0)
- ret = r;
- }
+ r = rm_rf_children(subdir_fd, only_dirs, honour_sticky, root_dev);
+ if (r < 0 && ret == 0)
+ ret = r;
if (!keep_around)
if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) {
return ret;
}
-int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky) {
- int fd;
- int r;
+int rm_rf_children(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev) {
+ struct statfs s;
+
+ assert(fd >= 0);
+
+ if (fstatfs(fd, &s) < 0) {
+ close_nointr_nofail(fd);
+ return -errno;
+ }
+
+ /* We refuse to clean disk file systems with this call. This
+ * is extra paranoia just to be sure we never ever remove
+ * non-state data */
+
+ if (s.f_type != TMPFS_MAGIC &&
+ s.f_type != RAMFS_MAGIC) {
+ log_error("Attempted to remove disk file system, and we can't allow that.");
+ close_nointr_nofail(fd);
+ return -EPERM;
+ }
+
+ return rm_rf_children_dangerous(fd, only_dirs, honour_sticky, root_dev);
+}
+
+static int rm_rf_internal(const char *path, bool only_dirs, bool delete_root, bool honour_sticky, bool dangerous) {
+ int fd, r;
+ struct statfs s;
assert(path);
- if ((fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC)) < 0) {
+ /* We refuse to clean the root file system with this
+ * call. This is extra paranoia to never cause a really
+ * seriously broken system. */
+ if (path_equal(path, "/")) {
+ log_error("Attempted to remove entire root file system, and we can't allow that.");
+ return -EPERM;
+ }
+
+ fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
+ if (fd < 0) {
if (errno != ENOTDIR)
return -errno;
+ if (!dangerous) {
+ if (statfs(path, &s) < 0)
+ return -errno;
+
+ if (s.f_type != TMPFS_MAGIC &&
+ s.f_type != RAMFS_MAGIC) {
+ log_error("Attempted to remove disk file system, and we can't allow that.");
+ return -EPERM;
+ }
+ }
+
if (delete_root && !only_dirs)
- if (unlink(path) < 0)
+ if (unlink(path) < 0 && errno != ENOENT)
return -errno;
return 0;
}
- r = rm_rf_children(fd, only_dirs, honour_sticky);
+ if (!dangerous) {
+ if (fstatfs(fd, &s) < 0) {
+ close_nointr_nofail(fd);
+ return -errno;
+ }
+ if (s.f_type != TMPFS_MAGIC &&
+ s.f_type != RAMFS_MAGIC) {
+ log_error("Attempted to remove disk file system, and we can't allow that.");
+ close_nointr_nofail(fd);
+ return -EPERM;
+ }
+ }
+
+ r = rm_rf_children_dangerous(fd, only_dirs, honour_sticky, NULL);
if (delete_root) {
if (honour_sticky && file_is_priv_sticky(path) > 0)
return r;
}
+int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky) {
+ return rm_rf_internal(path, only_dirs, delete_root, honour_sticky, false);
+}
+
+int rm_rf_dangerous(const char *path, bool only_dirs, bool delete_root, bool honour_sticky) {
+ return rm_rf_internal(path, only_dirs, delete_root, honour_sticky, true);
+}
+
int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) {
assert(path);
}
void status_vprintf(const char *status, bool ellipse, const char *format, va_list ap) {
- char *s = NULL, *spaces = NULL, *e;
- int fd = -1, c;
- size_t emax, sl, left;
+ char *s = NULL;
+ static const char status_indent[] = " "; /* "[" STATUS "] " */
+ int fd = -1;
struct iovec iovec[5];
int n = 0;
assert(format);
- /* This independent of logging, as status messages are
+ /* This is independent of logging, as status messages are
* optional and go exclusively to the console. */
if (vasprintf(&s, format, ap) < 0)
goto finish;
if (ellipse) {
+ char *e;
+ size_t emax, sl;
+ int c;
+
c = fd_columns(fd);
if (c <= 0)
c = 80;
- if (status) {
- sl = 2 + 6 + 1; /* " [" status "]" */
- emax = (size_t) c > sl ? c - sl - 1 : 0;
- } else
- emax = c - 1;
+ sl = status ? strlen(status_indent) : 0;
+
+ emax = c - sl - 1;
+ if (emax < 3)
+ emax = 3;
e = ellipsize(s, emax, 75);
if (e) {
}
zero(iovec);
- IOVEC_SET_STRING(iovec[n++], s);
- if (ellipse) {
- sl = strlen(s);
- left = emax > sl ? emax - sl : 0;
- if (left > 0) {
- spaces = malloc(left);
- if (spaces) {
- memset(spaces, ' ', left);
- iovec[n].iov_base = spaces;
- iovec[n].iov_len = left;
- n++;
- }
- }
+ if (status) {
+ if (!isempty(status)) {
+ IOVEC_SET_STRING(iovec[n++], "[");
+ IOVEC_SET_STRING(iovec[n++], status);
+ IOVEC_SET_STRING(iovec[n++], "] ");
+ } else
+ IOVEC_SET_STRING(iovec[n++], status_indent);
}
- if (status) {
- IOVEC_SET_STRING(iovec[n++], " [");
- IOVEC_SET_STRING(iovec[n++], status);
- IOVEC_SET_STRING(iovec[n++], "]\n");
- } else
- IOVEC_SET_STRING(iovec[n++], "\n");
+ IOVEC_SET_STRING(iovec[n++], s);
+ IOVEC_SET_STRING(iovec[n++], "\n");
writev(fd, iovec, n);
finish:
free(s);
- free(spaces);
if (fd >= 0)
close_nointr_nofail(fd);
return ws.ws_col;
}
-unsigned columns(void) {
- static __thread int parsed_columns = 0;
+static unsigned columns_cached(bool cached) {
+ static __thread int parsed_columns = 0, env_columns = -1;
const char *e;
- if (_likely_(parsed_columns > 0))
+ if (_likely_(parsed_columns > 0 && cached))
return parsed_columns;
- e = getenv("COLUMNS");
- if (e)
- parsed_columns = atoi(e);
+ if (_unlikely_(env_columns == -1)) {
+ e = getenv("COLUMNS");
+ if (e)
+ env_columns = atoi(e);
+ else
+ env_columns = 0;
+ }
- if (parsed_columns <= 0)
+ if (env_columns > 0) {
+ parsed_columns = env_columns;
+ return parsed_columns;
+ }
+
+ if (parsed_columns <= 0 || !cached)
parsed_columns = fd_columns(STDOUT_FILENO);
if (parsed_columns <= 0)
return parsed_columns;
}
+unsigned columns(void) {
+ return columns_cached(true);
+}
+
+unsigned columns_uncached(void) {
+ return columns_cached(false);
+}
+
int fd_lines(int fd) {
struct winsize ws;
zero(ws);
bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) {
assert(de);
- if (!dirent_is_file(de))
+ if (de->d_type != DT_REG &&
+ de->d_type != DT_LNK &&
+ de->d_type != DT_UNKNOWN)
+ return false;
+
+ if (ignore_file_allow_backup(de->d_name))
return false;
return endswith(de->d_name, suffix);
continue;
if (asprintf(&path, "%s/%s", directory, de->d_name) < 0) {
- log_error("Out of memory");
+ log_oom();
continue;
}
_argv[1] = NULL;
argv = _argv;
} else
- if (!argv[0])
- argv[0] = path;
+ argv[0] = path;
execv(path, argv);
}
if ((path = hashmap_remove(pids, UINT_TO_PTR(si.si_pid)))) {
- if (!is_clean_exit(si.si_code, si.si_status)) {
+ if (!is_clean_exit(si.si_code, si.si_status, NULL)) {
if (si.si_code == CLD_EXITED)
log_error("%s exited with exit status %i.", path, si.si_status);
else
if (!t)
return -ENOMEM;
- fn = file_name_from_path(path);
+ fn = path_get_file_name(path);
k = fn-path;
memcpy(t, path, k);
t[k] = '.';
return 0;
}
-static int files_add(Hashmap *h, const char *path, const char *suffix) {
- DIR *dir;
- struct dirent buffer, *de;
- int r = 0;
-
- dir = opendir(path);
- if (!dir) {
- if (errno == ENOENT)
- return 0;
- return -errno;
- }
-
- for (;;) {
- int k;
- char *p, *f;
-
- k = readdir_r(dir, &buffer, &de);
- if (k != 0) {
- r = -k;
- goto finish;
- }
-
- if (!de)
- break;
-
- if (!dirent_is_file_with_suffix(de, suffix))
- continue;
-
- if (asprintf(&p, "%s/%s", path, de->d_name) < 0) {
- r = -ENOMEM;
- goto finish;
- }
-
- f = canonicalize_file_name(p);
- if (!f) {
- log_error("Failed to canonicalize file name '%s': %m", p);
- free(p);
- continue;
- }
- free(p);
-
- log_debug("found: %s\n", f);
- if (hashmap_put(h, file_name_from_path(f), f) <= 0)
- free(f);
- }
-
-finish:
- closedir(dir);
- return r;
-}
-
-static int base_cmp(const void *a, const void *b) {
- const char *s1, *s2;
-
- s1 = *(char * const *)a;
- s2 = *(char * const *)b;
- return strcmp(file_name_from_path(s1), file_name_from_path(s2));
-}
-
-int conf_files_list(char ***strv, const char *suffix, const char *dir, ...) {
- Hashmap *fh = NULL;
- char **dirs = NULL;
- char **files = NULL;
- char **p;
- va_list ap;
- int r = 0;
-
- va_start(ap, dir);
- dirs = strv_new_ap(dir, ap);
- va_end(ap);
- if (!dirs) {
- r = -ENOMEM;
- goto finish;
- }
- if (!strv_path_canonicalize(dirs)) {
- r = -ENOMEM;
- goto finish;
- }
- if (!strv_uniq(dirs)) {
- r = -ENOMEM;
- goto finish;
- }
-
- fh = hashmap_new(string_hash_func, string_compare_func);
- if (!fh) {
- r = -ENOMEM;
- goto finish;
- }
-
- STRV_FOREACH(p, dirs) {
- if (files_add(fh, *p, suffix) < 0) {
- log_error("Failed to search for files.");
- r = -EINVAL;
- goto finish;
- }
- }
-
- files = hashmap_get_strv(fh);
- if (files == NULL) {
- log_error("Failed to compose list of files.");
- r = -ENOMEM;
- goto finish;
- }
-
- qsort(files, hashmap_size(fh), sizeof(char *), base_cmp);
-
-finish:
- strv_free(dirs);
- hashmap_free(fh);
- *strv = files;
- return r;
-}
-
-int hwclock_is_localtime(void) {
- FILE *f;
- bool local = false;
-
- /*
- * The third line of adjtime is "UTC" or "LOCAL" or nothing.
- * # /etc/adjtime
- * 0.0 0 0
- * 0
- * UTC
- */
- f = fopen("/etc/adjtime", "re");
- if (f) {
- char line[LINE_MAX];
- bool b;
-
- b = fgets(line, sizeof(line), f) &&
- fgets(line, sizeof(line), f) &&
- fgets(line, sizeof(line), f);
-
- fclose(f);
-
- if (!b)
- return -EIO;
-
-
- truncate_nl(line);
- local = streq(line, "LOCAL");
-
- } else if (errno != -ENOENT)
- return -errno;
-
- return local;
-}
-
-int hwclock_apply_localtime_delta(int *min) {
- const struct timeval *tv_null = NULL;
- struct timespec ts;
- struct tm *tm;
- int minuteswest;
- struct timezone tz;
-
- assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
- assert_se(tm = localtime(&ts.tv_sec));
- minuteswest = tm->tm_gmtoff / 60;
-
- tz.tz_minuteswest = -minuteswest;
- tz.tz_dsttime = 0; /* DST_NONE*/
-
- /*
- * If the hardware clock does not run in UTC, but in local time:
- * The very first time we set the kernel's timezone, it will warp
- * the clock so that it runs in UTC instead of local time.
- */
- if (settimeofday(tv_null, &tz) < 0)
- return -errno;
- if (min)
- *min = minuteswest;
- return 0;
-}
-
-int hwclock_reset_localtime_delta(void) {
- const struct timeval *tv_null = NULL;
- struct timezone tz;
-
- tz.tz_minuteswest = 0;
- tz.tz_dsttime = 0; /* DST_NONE*/
-
- if (settimeofday(tv_null, &tz) < 0)
- return -errno;
-
- return 0;
-}
-
-int rtc_open(int flags) {
- int fd;
- DIR *d;
-
- /* First, we try to make use of the /dev/rtc symlink. If that
- * doesn't exist, we open the first RTC which has hctosys=1
- * set. If we don't find any we just take the first RTC that
- * exists at all. */
-
- fd = open("/dev/rtc", flags);
- if (fd >= 0)
- return fd;
-
- d = opendir("/sys/class/rtc");
- if (!d)
- goto fallback;
-
- for (;;) {
- char *p, *v;
- struct dirent buf, *de;
- int r;
-
- r = readdir_r(d, &buf, &de);
- if (r != 0)
- goto fallback;
-
- if (!de)
- goto fallback;
-
- if (ignore_file(de->d_name))
- continue;
-
- p = join("/sys/class/rtc/", de->d_name, "/hctosys", NULL);
- if (!p) {
- closedir(d);
- return -ENOMEM;
- }
-
- r = read_one_line_file(p, &v);
- free(p);
-
- if (r < 0)
- continue;
-
- r = parse_boolean(v);
- free(v);
-
- if (r <= 0)
- continue;
-
- p = strappend("/dev/", de->d_name);
- fd = open(p, flags);
- free(p);
-
- if (fd >= 0) {
- closedir(d);
- return fd;
- }
- }
-
-fallback:
- if (d)
- closedir(d);
-
- fd = open("/dev/rtc0", flags);
- if (fd < 0)
- return -errno;
-
- return fd;
-}
-
-int hwclock_get_time(struct tm *tm) {
- int fd;
- int err = 0;
-
- assert(tm);
-
- fd = rtc_open(O_RDONLY|O_CLOEXEC);
- if (fd < 0)
- return -errno;
-
- /* This leaves the timezone fields of struct tm
- * uninitialized! */
- if (ioctl(fd, RTC_RD_TIME, tm) < 0)
- err = -errno;
-
- /* We don't now daylight saving, so we reset this in order not
- * to confused mktime(). */
- tm->tm_isdst = -1;
-
- close_nointr_nofail(fd);
-
- return err;
-}
-
-int hwclock_set_time(const struct tm *tm) {
- int fd;
- int err = 0;
-
- assert(tm);
-
- fd = rtc_open(O_RDONLY|O_CLOEXEC);
- if (fd < 0)
- return -errno;
-
- if (ioctl(fd, RTC_SET_TIME, tm) < 0)
- err = -errno;
-
- close_nointr_nofail(fd);
-
- return err;
-}
-
int copy_file(const char *from, const char *to) {
int r, fdf, fdt;
assert(from);
assert(to);
- if (parent_of_path(from, &pf) < 0 ||
- parent_of_path(to, &pt) < 0) {
+ if (path_get_parent(from, &pf) < 0 ||
+ path_get_parent(to, &pt) < 0) {
r = -ENOMEM;
goto finish;
}
if (!t)
return -ENOMEM;
- fn = file_name_from_path(to);
+ fn = path_get_file_name(to);
k = fn-to;
memcpy(t, to, k);
t[k] = '.';
return 0;
}
-int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **home) {
+int get_user_creds(
+ const char **username,
+ uid_t *uid, gid_t *gid,
+ const char **home,
+ const char **shell) {
+
struct passwd *p;
uid_t u;
if (home)
*home = "/root";
+
+ if (shell)
+ *shell = "/bin/sh";
+
return 0;
}
if (home)
*home = p->pw_dir;
+ if (shell)
+ *shell = p->pw_shell;
+
return 0;
}
char **i, *parent;
int r;
- r = parent_of_path(path, &parent);
+ r = path_get_parent(path, &parent);
if (r < 0)
return r;
return r;
}
-char *join(const char *x, ...) {
+char *strjoin(const char *x, ...) {
va_list ap;
size_t l;
char *r, *p;
return 0;
}
-int path_is_read_only_fs(const char *path) {
- struct statvfs st;
-
- assert(path);
-
- if (statvfs(path, &st) < 0)
- return -errno;
-
- return !!(st.f_flag & ST_RDONLY);
-}
-
int getenv_for_pid(pid_t pid, const char *field, char **_value) {
char path[sizeof("/proc/")-1+10+sizeof("/environ")], *value = NULL;
int r;
return r;
}
+
+int can_sleep(const char *type) {
+ char *p, *w, *state;
+ size_t l, k;
+ bool found = false;
+ int r;
+
+ assert(type);
+
+ r = read_one_line_file("/sys/power/state", &p);
+ if (r < 0)
+ return r == -ENOENT ? 0 : r;
+
+ k = strlen(type);
+
+ FOREACH_WORD_SEPARATOR(w, l, p, WHITESPACE, state) {
+ if (l == k && strncmp(w, type, l) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ free(p);
+ return found;
+}
+
+bool is_valid_documentation_url(const char *url) {
+ assert(url);
+
+ if (startswith(url, "http://") && url[7])
+ return true;
+
+ if (startswith(url, "https://") && url[8])
+ return true;
+
+ if (startswith(url, "file:") && url[5])
+ return true;
+
+ if (startswith(url, "info:") && url[5])
+ return true;
+
+ if (startswith(url, "man:") && url[4])
+ return true;
+
+ return false;
+}
+
+bool in_initrd(void) {
+ static int saved = -1;
+ struct statfs s;
+
+ if (saved >= 0)
+ return saved;
+
+ /* We make two checks here:
+ *
+ * 1. the flag file /etc/initrd-release must exist
+ * 2. the root file system must be a memory file system
+ *
+ * The second check is extra paranoia, since misdetecting an
+ * initrd can have bad bad consequences due the initrd
+ * emptying when transititioning to the main systemd.
+ */
+
+ saved = access("/etc/initrd-release", F_OK) >= 0 &&
+ statfs("/", &s) >= 0 &&
+ (s.f_type == TMPFS_MAGIC || s.f_type == RAMFS_MAGIC);
+
+ return saved;
+}
+
+void warn_melody(void) {
+ int fd;
+
+ fd = open("/dev/console", O_WRONLY|O_CLOEXEC|O_NOCTTY);
+ if (fd < 0)
+ return;
+
+ /* Yeah, this is synchronous. Kinda sucks. Bute well... */
+
+ ioctl(fd, KIOCSOUND, (int)(1193180/440));
+ usleep(125*USEC_PER_MSEC);
+
+ ioctl(fd, KIOCSOUND, (int)(1193180/220));
+ usleep(125*USEC_PER_MSEC);
+
+ ioctl(fd, KIOCSOUND, (int)(1193180/220));
+ usleep(125*USEC_PER_MSEC);
+
+ ioctl(fd, KIOCSOUND, 0);
+ close_nointr_nofail(fd);
+}
+
+int make_console_stdio(void) {
+ int fd, r;
+
+ /* Make /dev/console the controlling terminal and stdin/stdout/stderr */
+
+ fd = acquire_terminal("/dev/console", false, true, true, (usec_t) -1);
+ if (fd < 0) {
+ log_error("Failed to acquire terminal: %s", strerror(-fd));
+ return fd;
+ }
+
+ r = make_stdio(fd);
+ if (r < 0) {
+ log_error("Failed to duplicate terminal fd: %s", strerror(-r));
+ return r;
+ }
+
+ return 0;
+}
+
+int get_home_dir(char **_h) {
+ char *h;
+ const char *e;
+ uid_t u;
+ struct passwd *p;
+
+ assert(_h);
+
+ /* Take the user specified one */
+ e = getenv("HOME");
+ if (e) {
+ h = strdup(e);
+ if (!h)
+ return -ENOMEM;
+
+ *_h = h;
+ return 0;
+ }
+
+ /* Hardcode home directory for root to avoid NSS */
+ u = getuid();
+ if (u == 0) {
+ h = strdup("/root");
+ if (!h)
+ return -ENOMEM;
+
+ *_h = h;
+ return 0;
+ }
+
+ /* Check the database... */
+ errno = 0;
+ p = getpwuid(u);
+ if (!p)
+ return errno ? -errno : -ENOENT;
+
+ if (!path_is_absolute(p->pw_dir))
+ return -EINVAL;
+
+ h = strdup(p->pw_dir);
+ if (!h)
+ return -ENOMEM;
+
+ *_h = h;
+ return 0;
+}
+
+int get_shell(char **_sh) {
+ char *sh;
+ const char *e;
+ uid_t u;
+ struct passwd *p;
+
+ assert(_sh);
+
+ /* Take the user specified one */
+ e = getenv("SHELL");
+ if (e) {
+ sh = strdup(e);
+ if (!sh)
+ return -ENOMEM;
+
+ *_sh = sh;
+ return 0;
+ }
+
+ /* Hardcode home directory for root to avoid NSS */
+ u = getuid();
+ if (u == 0) {
+ sh = strdup("/bin/sh");
+ if (!sh)
+ return -ENOMEM;
+
+ *_sh = sh;
+ return 0;
+ }
+
+ /* Check the database... */
+ errno = 0;
+ p = getpwuid(u);
+ if (!p)
+ return errno ? -errno : -ESRCH;
+
+ if (!path_is_absolute(p->pw_shell))
+ return -EINVAL;
+
+ sh = strdup(p->pw_shell);
+ if (!sh)
+ return -ENOMEM;
+
+ *_sh = sh;
+ return 0;
+}