+
+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;
+ 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 */
+
+ agent_pid = fork();
+ if (agent_pid < 0)
+ return -errno;
+
+ if (agent_pid != 0) {
+ *pid = agent_pid;
+ return 0;
+ }
+
+ /* In the child:
+ *
+ * Make sure the agent goes away when the parent dies */
+ if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
+ _exit(EXIT_FAILURE);
+
+ /* Check whether our parent died before we were able
+ * to set the death signal */
+ if (getppid() != parent_pid)
+ _exit(EXIT_SUCCESS);
+
+ /* Don't leak fds to the agent */
+ close_all_fds(except, n_except);
+
+ stdout_is_tty = isatty(STDOUT_FILENO);
+ stderr_is_tty = isatty(STDERR_FILENO);
+
+ if (!stdout_is_tty || !stderr_is_tty) {
+ /* Detach from stdout/stderr. and reopen
+ * /dev/tty for them. This is important to
+ * ensure that when systemctl is started via
+ * popen() or a similar call that expects to
+ * read EOF we actually do generate EOF and
+ * not delay this indefinitely by because we
+ * keep an unused copy of stdin around. */
+ fd = open("/dev/tty", O_WRONLY);
+ if (fd < 0) {
+ log_error("Failed to open /dev/tty: %m");
+ _exit(EXIT_FAILURE);
+ }
+
+ if (!stdout_is_tty)
+ dup2(fd, STDOUT_FILENO);
+
+ if (!stderr_is_tty)
+ dup2(fd, STDERR_FILENO);
+
+ if (fd > 2)
+ close(fd);
+ }
+
+ /* Count arguments */
+ va_start(ap, path);
+ for (n = 0; va_arg(ap, char*); n++)
+ ;
+ va_end(ap);
+
+ /* Allocate strv */
+ l = alloca(sizeof(char *) * (n + 1));
+
+ /* Fill in arguments */
+ va_start(ap, path);
+ for (i = 0; i <= n; i++)
+ l[i] = va_arg(ap, char*);
+ va_end(ap);
+
+ execv(path, l);
+ _exit(EXIT_FAILURE);
+}
+
+int setrlimit_closest(int resource, const struct rlimit *rlim) {
+ struct rlimit highest, fixed;
+
+ assert(rlim);
+
+ if (setrlimit(resource, rlim) >= 0)
+ return 0;
+
+ if (errno != EPERM)
+ return -errno;
+
+ /* So we failed to set the desired setrlimit, then let's try
+ * to get as close as we can */
+ assert_se(getrlimit(resource, &highest) == 0);
+
+ fixed.rlim_cur = MIN(rlim->rlim_cur, highest.rlim_max);
+ fixed.rlim_max = MIN(rlim->rlim_max, highest.rlim_max);
+
+ if (setrlimit(resource, &fixed) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int getenv_for_pid(pid_t pid, const char *field, char **_value) {
+ char path[sizeof("/proc/")-1+10+sizeof("/environ")], *value = NULL;
+ int r;
+ FILE *f;
+ bool done = false;
+ size_t l;
+
+ assert(field);
+ assert(_value);
+
+ if (pid == 0)
+ pid = getpid();
+
+ snprintf(path, sizeof(path), "/proc/%lu/environ", (unsigned long) pid);
+ char_array_0(path);
+
+ f = fopen(path, "re");
+ if (!f)
+ return -errno;
+
+ l = strlen(field);
+ r = 0;
+
+ do {
+ char line[LINE_MAX];
+ unsigned i;
+
+ for (i = 0; i < sizeof(line)-1; i++) {
+ int c;
+
+ c = getc(f);
+ if (_unlikely_(c == EOF)) {
+ done = true;
+ break;
+ } else if (c == 0)
+ break;
+
+ line[i] = c;
+ }
+ line[i] = 0;
+
+ if (memcmp(line, field, l) == 0 && line[l] == '=') {
+ value = strdup(line + l + 1);
+ if (!value) {
+ r = -ENOMEM;
+ break;
+ }
+
+ r = 1;
+ break;
+ }
+
+ } while (!done);
+
+ fclose(f);
+
+ if (r >= 0)
+ *_value = value;
+
+ return r;
+}
+
+int can_sleep(const char *type) {
+ char *w, *state;
+ size_t l, k;
+ int r;
+ _cleanup_free_ char *p = NULL;
+
+ 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 && memcmp(w, type, l) == 0)
+ return true;
+
+ return false;
+}
+
+int can_sleep_disk(const char *type) {
+ char *w, *state;
+ size_t l, k;
+ int r;
+ _cleanup_free_ char *p = NULL;
+
+ assert(type);
+
+ r = read_one_line_file("/sys/power/disk", &p);
+ if (r < 0)
+ return r == -ENOENT ? 0 : r;
+
+ k = strlen(type);
+ FOREACH_WORD_SEPARATOR(w, l, p, WHITESPACE, state) {
+ if (l == k && memcmp(w, type, l) == 0)
+ return true;
+
+ if (l == k + 2 && w[0] == '[' && memcmp(w + 1, type, l - 2) == 0 && w[l-1] == ']')
+ return true;
+ }
+
+ return false;
+}
+
+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 __thread 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 &&
+ is_temporary_fs(&s);
+
+ return saved;
+}
+
+void warn_melody(void) {
+ _cleanup_close_ int fd = -1;
+
+ fd = open("/dev/console", O_WRONLY|O_CLOEXEC|O_NOCTTY);
+ if (fd < 0)
+ return;
+
+ /* Yeah, this is synchronous. Kinda sucks. But 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);
+}
+
+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 : -ESRCH;
+
+ 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;
+}
+
+void freep(void *p) {
+ free(*(void**) p);
+}
+
+void fclosep(FILE **f) {
+ if (*f)
+ fclose(*f);
+}
+
+void closep(int *fd) {
+ if (*fd >= 0)
+ close_nointr_nofail(*fd);
+}
+
+void closedirp(DIR **d) {
+ if (*d)
+ closedir(*d);
+}
+
+void umaskp(mode_t *u) {
+ umask(*u);
+}
+
+bool filename_is_safe(const char *p) {
+
+ if (isempty(p))
+ return false;
+
+ if (strchr(p, '/'))
+ return false;
+
+ if (streq(p, "."))
+ return false;
+
+ if (streq(p, ".."))
+ return false;
+
+ if (strlen(p) > FILENAME_MAX)
+ return false;
+
+ return true;
+}
+
+bool string_is_safe(const char *p) {
+ const char *t;
+
+ assert(p);
+
+ for (t = p; *t; t++) {
+ if (*t > 0 && *t < ' ')
+ return false;
+
+ if (strchr("\\\"\'", *t))
+ return false;
+ }
+
+ return true;
+}
+
+int parse_timestamp(const char *t, usec_t *usec) {
+ const char *k;
+ struct tm tm, copy;
+ time_t x;
+ usec_t plus = 0, minus = 0, ret;
+ int r;
+
+ /*
+ * Allowed syntaxes:
+ *
+ * 2012-09-22 16:34:22
+ * 2012-09-22 16:34 (seconds will be set to 0)
+ * 2012-09-22 (time will be set to 00:00:00)
+ * 16:34:22 (date will be set to today)
+ * 16:34 (date will be set to today, seconds to 0)
+ * now
+ * yesterday (time is set to 00:00:00)
+ * today (time is set to 00:00:00)
+ * tomorrow (time is set to 00:00:00)
+ * +5min
+ * -5days
+ *
+ */
+
+ assert(t);
+ assert(usec);
+
+ x = time(NULL);
+ assert_se(localtime_r(&x, &tm));
+
+ if (streq(t, "now"))
+ goto finish;
+
+ else if (streq(t, "today")) {
+ tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+ goto finish;
+
+ } else if (streq(t, "yesterday")) {
+ tm.tm_mday --;
+ tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+ goto finish;
+
+ } else if (streq(t, "tomorrow")) {
+ tm.tm_mday ++;
+ tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+ goto finish;
+
+ } else if (t[0] == '+') {
+
+ r = parse_usec(t+1, &plus);
+ if (r < 0)
+ return r;
+
+ goto finish;
+ } else if (t[0] == '-') {
+
+ r = parse_usec(t+1, &minus);
+ if (r < 0)
+ return r;
+
+ goto finish;
+ }
+
+ copy = tm;
+ k = strptime(t, "%y-%m-%d %H:%M:%S", &tm);
+ if (k && *k == 0)
+ goto finish;
+
+ tm = copy;
+ k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm);
+ if (k && *k == 0)
+ goto finish;
+
+ tm = copy;
+ k = strptime(t, "%y-%m-%d %H:%M", &tm);
+ if (k && *k == 0) {
+ tm.tm_sec = 0;
+ goto finish;
+ }
+
+ tm = copy;
+ k = strptime(t, "%Y-%m-%d %H:%M", &tm);
+ if (k && *k == 0) {
+ tm.tm_sec = 0;
+ goto finish;
+ }
+
+ tm = copy;
+ k = strptime(t, "%y-%m-%d", &tm);
+ if (k && *k == 0) {
+ tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+ goto finish;
+ }
+
+ tm = copy;
+ k = strptime(t, "%Y-%m-%d", &tm);
+ if (k && *k == 0) {
+ tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+ goto finish;
+ }
+
+ tm = copy;
+ k = strptime(t, "%H:%M:%S", &tm);
+ if (k && *k == 0)
+ goto finish;
+
+ tm = copy;
+ k = strptime(t, "%H:%M", &tm);
+ if (k && *k == 0) {
+ tm.tm_sec = 0;
+ goto finish;
+ }
+
+ return -EINVAL;
+
+finish:
+ x = mktime(&tm);
+ if (x == (time_t) -1)
+ return -EINVAL;
+
+ ret = (usec_t) x * USEC_PER_SEC;
+
+ ret += plus;
+ if (ret > minus)
+ ret -= minus;
+ else
+ ret = 0;
+
+ *usec = ret;
+
+ return 0;
+}
+
+/* hey glibc, APIs with callbacks without a user pointer are so useless */
+void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size,
+ int (*compar) (const void *, const void *, void *), void *arg) {
+ size_t l, u, idx;
+ const void *p;
+ int comparison;
+
+ l = 0;
+ u = nmemb;
+ while (l < u) {
+ idx = (l + u) / 2;
+ p = (void *)(((const char *) base) + (idx * size));
+ comparison = compar(key, p, arg);
+ if (comparison < 0)
+ u = idx;
+ else if (comparison > 0)
+ l = idx + 1;
+ else
+ return (void *)p;
+ }
+ return NULL;
+}
+
+bool is_locale_utf8(void) {
+ const char *set;
+ static int cached_answer = -1;
+
+ if (cached_answer >= 0)
+ goto out;
+
+ if (!setlocale(LC_ALL, "")) {
+ cached_answer = true;
+ goto out;
+ }
+
+ set = nl_langinfo(CODESET);
+ if (!set) {
+ cached_answer = true;
+ goto out;
+ }
+
+ cached_answer = streq(set, "UTF-8");
+out:
+ return (bool)cached_answer;
+}
+
+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_BRANCH] = "\342\224\234\342\224\200", /* ├─ */
+ [DRAW_TREE_RIGHT] = "\342\224\224\342\224\200", /* └─ */
+ [DRAW_TRIANGULAR_BULLET] = "\342\200\243 ", /* ‣ */
+ },
+ /* ASCII fallback */ {
+ [DRAW_TREE_VERT] = "| ",
+ [DRAW_TREE_BRANCH] = "|-",
+ [DRAW_TREE_RIGHT] = "`-",
+ [DRAW_TRIANGULAR_BULLET] = "> ",
+ }
+ };
+
+ return draw_table[!is_locale_utf8()][ch];
+}
+
+char *strreplace(const char *text, const char *old_string, const char *new_string) {
+ const char *f;
+ char *t, *r;
+ size_t l, old_len, new_len;
+
+ assert(text);
+ assert(old_string);
+ assert(new_string);
+
+ old_len = strlen(old_string);
+ new_len = strlen(new_string);
+
+ l = strlen(text);
+ r = new(char, l+1);
+ if (!r)
+ return NULL;
+
+ f = text;
+ t = r;
+ while (*f) {
+ char *a;
+ size_t d, nl;
+
+ if (!startswith(f, old_string)) {
+ *(t++) = *(f++);
+ continue;
+ }
+
+ d = t - r;
+ nl = l - old_len + new_len;
+ a = realloc(r, nl + 1);
+ if (!a)
+ goto oom;
+
+ l = nl;
+ r = a;
+ t = r + d;
+
+ t = stpcpy(t, new_string);
+ f += old_len;
+ }
+
+ *t = 0;
+ return r;
+
+oom:
+ free(r);
+ return NULL;
+}