#include <linux/kd.h>
#include <dlfcn.h>
#include <sys/wait.h>
-#include <sys/capability.h>
#include <sys/time.h>
#include <glob.h>
#include <grp.h>
#include <sys/mman.h>
+#include <sys/vfs.h>
+#include <linux/magic.h>
#include "macro.h"
#include "util.h"
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 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));
}
*/
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 (errno == EINTR)
+ if (r == 0) {
+ r = -ETIMEDOUT;
+ goto fail;
+ }
+ }
+
+ l = read(notify, inotify_buffer, sizeof(inotify_buffer));
+ if (l < 0) {
+
+ if (errno == EINTR || errno == EAGAIN)
continue;
r = -errno;
{ "m", USEC_PER_MINUTE },
{ "usec", 1ULL },
{ "us", 1ULL },
- { "", USEC_PER_SEC },
+ { "", USEC_PER_SEC }, /* default is sec */
};
const char *p;
return 0;
}
+int parse_nsec(const char *t, nsec_t *nsec) {
+ static const struct {
+ const char *suffix;
+ nsec_t nsec;
+ } table[] = {
+ { "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;
+ nsec_t r = 0;
+
+ assert(t);
+ assert(nsec);
+
+ 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 += (nsec_t) l * table[i].nsec;
+ p = e + strlen(table[i].suffix);
+ break;
+ }
+
+ if (i >= ELEMENTSOF(table))
+ return -EINVAL;
+
+ } while (*p != 0);
+
+ *nsec = r;
+
+ return 0;
+}
+
int parse_bytes(const char *t, off_t *bytes) {
static const struct {
const char *suffix;
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. */
d = fdopendir(fd);
if (!d) {
for (;;) {
struct dirent buf, *de;
- bool is_dir, keep_around = false;
+ bool is_dir, keep_around;
+ struct stat st;
int r;
r = readdir_r(d, &buf, &de);
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|O_NOATIME);
+ /* 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;
}
- r = rm_rf_children(subdir_fd, only_dirs, honour_sticky);
+ r = rm_rf_children(subdir_fd, only_dirs, honour_sticky, root_dev);
if (r < 0 && ret == 0)
ret = r;
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);
+ /* 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 && 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);
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);
continue;
if (asprintf(&path, "%s/%s", directory, de->d_name) < 0) {
- log_error("Out of memory");
+ log_oom();
continue;
}
}
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
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;
}
return r;
}
-char *join(const char *x, ...) {
+char *strjoin(const char *x, ...) {
va_list ap;
size_t l;
char *r, *p;
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;
+}