+
+int is_symlink(const char *path) {
+ struct stat info;
+
+ if (lstat(path, &info) < 0)
+ return -errno;
+
+ return !!S_ISLNK(info.st_mode);
+}
+
+int is_dir(const char* path, bool follow) {
+ struct stat st;
+ int r;
+
+ if (follow)
+ r = stat(path, &st);
+ else
+ r = lstat(path, &st);
+ if (r < 0)
+ return -errno;
+
+ return !!S_ISDIR(st.st_mode);
+}
+
+int is_device_node(const char *path) {
+ struct stat info;
+
+ if (lstat(path, &info) < 0)
+ return -errno;
+
+ return !!(S_ISBLK(info.st_mode) || S_ISCHR(info.st_mode));
+}
+
+int unquote_first_word(const char **p, char **ret, UnquoteFlags flags) {
+ _cleanup_free_ char *s = NULL;
+ size_t allocated = 0, sz = 0;
+ int r;
+
+ enum {
+ START,
+ 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 SINGLE_QUOTE:
+ if (c == 0) {
+ if (flags & UNQUOTE_RELAX)
+ goto finish;
+ 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 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 SINGLE_QUOTE_ESCAPE:
+ case DOUBLE_QUOTE_ESCAPE:
+ case VALUE_ESCAPE:
+ if (!GREEDY_REALLOC(s, allocated, sz+7))
+ return -ENOMEM;
+
+ if (c == 0) {
+ if ((flags & UNQUOTE_CUNESCAPE_RELAX) &&
+ (state == VALUE_ESCAPE || flags & UNQUOTE_RELAX)) {
+ /* If we find an unquoted trailing backslash and we're in
+ * UNQUOTE_CUNESCAPE_RELAX mode, keep it verbatim in the
+ * output.
+ *
+ * Unbalanced quotes will only be allowed in UNQUOTE_RELAX
+ * mode, UNQUOTE_CUNESCAP_RELAX mode does not allow them.
+ */
+ s[sz++] = '\\';
+ goto finish;
+ }
+ if (flags & UNQUOTE_RELAX)
+ goto finish;
+ return -EINVAL;
+ }
+
+ if (flags & UNQUOTE_CUNESCAPE) {
+ uint32_t u;
+
+ r = cunescape_one(*p, (size_t) -1, &c, &u);
+ if (r < 0) {
+ if (flags & UNQUOTE_CUNESCAPE_RELAX) {
+ s[sz++] = '\\';
+ s[sz++] = c;
+ goto end_escape;
+ }
+ return -EINVAL;
+ }
+
+ (*p) += r - 1;
+
+ if (c != 0)
+ s[sz++] = c; /* normal explicit char */
+ else
+ sz += utf8_encode_unichar(s + sz, u); /* unicode chars we'll encode as utf8 */
+ } else
+ s[sz++] = c;
+
+end_escape:
+ state = (state == SINGLE_QUOTE_ESCAPE) ? SINGLE_QUOTE :
+ (state == DOUBLE_QUOTE_ESCAPE) ? DOUBLE_QUOTE :
+ VALUE;
+ 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_first_word_and_warn(
+ const char **p,
+ char **ret,
+ UnquoteFlags flags,
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *rvalue) {
+ /* Try to unquote it, if it fails, warn about it and try again but this
+ * time using UNQUOTE_CUNESCAPE_RELAX to keep the backslashes verbatim
+ * in invalid escape sequences. */
+ const char *save;
+ int r;
+
+ save = *p;
+ r = unquote_first_word(p, ret, flags);
+ if (r < 0 && !(flags&UNQUOTE_CUNESCAPE_RELAX)) {
+ /* Retry it with UNQUOTE_CUNESCAPE_RELAX. */
+ *p = save;
+ r = unquote_first_word(p, ret, flags|UNQUOTE_CUNESCAPE_RELAX);
+ if (r < 0)
+ log_syntax(unit, LOG_ERR, filename, line, EINVAL,
+ "Unbalanced quoting in command line, ignoring: \"%s\"", rvalue);
+ else
+ log_syntax(unit, LOG_WARNING, filename, line, EINVAL,
+ "Invalid escape sequences in command line: \"%s\"", rvalue);
+ }
+ return r;
+}
+
+int unquote_many_words(const char **p, UnquoteFlags flags, ...) {
+ 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, flags);
+ 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], flags);
+ 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, flags);
+ 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 (streq_ptr(*p, s))
+ return 0;
+
+ if (s) {
+ t = strdup(s);
+ if (!t)
+ return -ENOMEM;
+ } else
+ t = NULL;
+
+ free(*p);
+ *p = t;
+
+ return 1;
+}
+
+int ptsname_malloc(int fd, char **ret) {
+ size_t l = 100;
+
+ assert(fd >= 0);
+ assert(ret);
+
+ for (;;) {
+ char *c;
+
+ c = new(char, l);
+ if (!c)
+ return -ENOMEM;
+
+ if (ptsname_r(fd, c, l) == 0) {
+ *ret = c;
+ return 0;
+ }
+ if (errno != ERANGE) {
+ free(c);
+ return -errno;
+ }
+
+ free(c);
+ l *= 2;
+ }
+}
+
+int openpt_in_namespace(pid_t pid, int flags) {
+ _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, rootfd = -1;
+ _cleanup_close_pair_ int pair[2] = { -1, -1 };
+ union {
+ struct cmsghdr cmsghdr;
+ uint8_t buf[CMSG_SPACE(sizeof(int))];
+ } control = {};
+ struct msghdr mh = {
+ .msg_control = &control,
+ .msg_controllen = sizeof(control),
+ };
+ struct cmsghdr *cmsg;
+ siginfo_t si;
+ pid_t child;
+ int r;
+
+ assert(pid > 0);
+
+ r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, &rootfd);
+ if (r < 0)
+ return r;
+
+ if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0)
+ return -errno;
+
+ child = fork();
+ if (child < 0)
+ return -errno;
+
+ if (child == 0) {
+ int master;
+
+ pair[0] = safe_close(pair[0]);
+
+ r = namespace_enter(pidnsfd, mntnsfd, -1, rootfd);
+ if (r < 0)
+ _exit(EXIT_FAILURE);
+
+ master = posix_openpt(flags);
+ if (master < 0)
+ _exit(EXIT_FAILURE);
+
+ cmsg = CMSG_FIRSTHDR(&mh);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+ memcpy(CMSG_DATA(cmsg), &master, sizeof(int));
+
+ mh.msg_controllen = cmsg->cmsg_len;
+
+ if (sendmsg(pair[1], &mh, MSG_NOSIGNAL) < 0)
+ _exit(EXIT_FAILURE);
+
+ _exit(EXIT_SUCCESS);
+ }
+
+ pair[1] = safe_close(pair[1]);
+
+ r = wait_for_terminate(child, &si);
+ if (r < 0)
+ return r;
+ if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS)
+ return -EIO;
+
+ if (recvmsg(pair[0], &mh, MSG_NOSIGNAL|MSG_CMSG_CLOEXEC) < 0)
+ return -errno;
+
+ CMSG_FOREACH(cmsg, &mh)
+ if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
+ int *fds;
+ unsigned n_fds;
+
+ fds = (int*) CMSG_DATA(cmsg);
+ n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+
+ if (n_fds != 1) {
+ close_many(fds, n_fds);
+ return -EIO;
+ }
+
+ return fds[0];
+ }
+
+ return -EIO;
+}
+
+ssize_t fgetxattrat_fake(int dirfd, const char *filename, const char *attribute, void *value, size_t size, int flags) {
+ _cleanup_close_ int fd = -1;
+ ssize_t l;
+
+ /* The kernel doesn't have a fgetxattrat() command, hence let's emulate one */
+
+ fd = openat(dirfd, filename, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOATIME|(flags & AT_SYMLINK_NOFOLLOW ? O_NOFOLLOW : 0));
+ if (fd < 0)
+ return -errno;
+
+ l = fgetxattr(fd, attribute, value, size);
+ if (l < 0)
+ return -errno;
+
+ return l;
+}
+
+static int parse_crtime(le64_t le, usec_t *usec) {
+ uint64_t u;
+
+ assert(usec);
+
+ u = le64toh(le);
+ if (u == 0 || u == (uint64_t) -1)
+ return -EIO;
+
+ *usec = (usec_t) u;
+ return 0;
+}
+
+int fd_getcrtime(int fd, usec_t *usec) {
+ le64_t le;
+ ssize_t n;
+
+ assert(fd >= 0);
+ assert(usec);
+
+ /* Until Linux gets a real concept of birthtime/creation time,
+ * let's fake one with xattrs */
+
+ n = fgetxattr(fd, "user.crtime_usec", &le, sizeof(le));
+ if (n < 0)
+ return -errno;
+ if (n != sizeof(le))
+ return -EIO;
+
+ return parse_crtime(le, usec);
+}
+
+int fd_getcrtime_at(int dirfd, const char *name, usec_t *usec, int flags) {
+ le64_t le;
+ ssize_t n;
+
+ n = fgetxattrat_fake(dirfd, name, "user.crtime_usec", &le, sizeof(le), flags);
+ if (n < 0)
+ return -errno;
+ if (n != sizeof(le))
+ return -EIO;
+
+ return parse_crtime(le, usec);
+}
+
+int path_getcrtime(const char *p, usec_t *usec) {
+ le64_t le;
+ ssize_t n;
+
+ assert(p);
+ assert(usec);
+
+ n = getxattr(p, "user.crtime_usec", &le, sizeof(le));
+ if (n < 0)
+ return -errno;
+ if (n != sizeof(le))
+ return -EIO;
+
+ return parse_crtime(le, usec);
+}
+
+int fd_setcrtime(int fd, usec_t usec) {
+ le64_t le;
+
+ assert(fd >= 0);
+
+ if (usec <= 0)
+ usec = now(CLOCK_REALTIME);
+
+ le = htole64((uint64_t) usec);
+ if (fsetxattr(fd, "user.crtime_usec", &le, sizeof(le), 0) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int same_fd(int a, int b) {
+ struct stat sta, stb;
+ pid_t pid;
+ int r, fa, fb;
+
+ assert(a >= 0);
+ assert(b >= 0);
+
+ /* Compares two file descriptors. Note that semantics are
+ * quite different depending on whether we have kcmp() or we
+ * don't. If we have kcmp() this will only return true for
+ * dup()ed file descriptors, but not otherwise. If we don't
+ * have kcmp() this will also return true for two fds of the same
+ * file, created by separate open() calls. Since we use this
+ * call mostly for filtering out duplicates in the fd store
+ * this difference hopefully doesn't matter too much. */
+
+ if (a == b)
+ return true;
+
+ /* Try to use kcmp() if we have it. */
+ pid = getpid();
+ r = kcmp(pid, pid, KCMP_FILE, a, b);
+ if (r == 0)
+ return true;
+ if (r > 0)
+ return false;
+ if (errno != ENOSYS)
+ return -errno;
+
+ /* We don't have kcmp(), use fstat() instead. */
+ if (fstat(a, &sta) < 0)
+ return -errno;
+
+ if (fstat(b, &stb) < 0)
+ return -errno;
+
+ if ((sta.st_mode & S_IFMT) != (stb.st_mode & S_IFMT))
+ return false;
+
+ /* We consider all device fds different, since two device fds
+ * might refer to quite different device contexts even though
+ * they share the same inode and backing dev_t. */
+
+ if (S_ISCHR(sta.st_mode) || S_ISBLK(sta.st_mode))
+ return false;
+
+ if (sta.st_dev != stb.st_dev || sta.st_ino != stb.st_ino)
+ return false;
+
+ /* The fds refer to the same inode on disk, let's also check
+ * if they have the same fd flags. This is useful to
+ * distinguish the read and write side of a pipe created with
+ * pipe(). */
+ fa = fcntl(a, F_GETFL);
+ if (fa < 0)
+ return -errno;
+
+ fb = fcntl(b, F_GETFL);
+ if (fb < 0)
+ return -errno;
+
+ return fa == fb;
+}
+
+int chattr_fd(int fd, unsigned value, unsigned mask) {
+ unsigned old_attr, new_attr;
+ struct stat st;
+
+ assert(fd >= 0);
+
+ if (fstat(fd, &st) < 0)
+ return -errno;
+
+ /* Explicitly check whether this is a regular file or
+ * directory. If it is anything else (such as a device node or
+ * fifo), then the ioctl will not hit the file systems but
+ * possibly drivers, where the ioctl might have different
+ * effects. Notably, DRM is using the same ioctl() number. */
+
+ if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode))
+ return -ENOTTY;
+
+ if (mask == 0)
+ return 0;
+
+ if (ioctl(fd, FS_IOC_GETFLAGS, &old_attr) < 0)
+ return -errno;
+
+ new_attr = (old_attr & ~mask) | (value & mask);
+ if (new_attr == old_attr)
+ return 0;
+
+ if (ioctl(fd, FS_IOC_SETFLAGS, &new_attr) < 0)
+ return -errno;
+
+ return 1;
+}
+
+int chattr_path(const char *p, unsigned value, unsigned mask) {
+ _cleanup_close_ int fd = -1;
+
+ assert(p);
+
+ if (mask == 0)
+ return 0;
+
+ fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
+ if (fd < 0)
+ return -errno;
+
+ return chattr_fd(fd, value, mask);
+}
+
+int read_attr_fd(int fd, unsigned *ret) {
+ struct stat st;
+
+ assert(fd >= 0);
+
+ if (fstat(fd, &st) < 0)
+ return -errno;
+
+ if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode))
+ return -ENOTTY;
+
+ if (ioctl(fd, FS_IOC_GETFLAGS, ret) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int read_attr_path(const char *p, unsigned *ret) {
+ _cleanup_close_ int fd = -1;
+
+ assert(p);
+ assert(ret);
+
+ fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
+ if (fd < 0)
+ return -errno;
+
+ return read_attr_fd(fd, ret);
+}
+
+static size_t nul_length(const uint8_t *p, size_t sz) {
+ size_t n = 0;
+
+ while (sz > 0) {
+ if (*p != 0)
+ break;
+
+ n++;
+ p++;
+ sz--;
+ }
+
+ return n;
+}
+
+ssize_t sparse_write(int fd, const void *p, size_t sz, size_t run_length) {
+ const uint8_t *q, *w, *e;
+ ssize_t l;
+
+ q = w = p;
+ e = q + sz;
+ while (q < e) {
+ size_t n;
+
+ n = nul_length(q, e - q);
+
+ /* If there are more than the specified run length of
+ * NUL bytes, or if this is the beginning or the end
+ * of the buffer, then seek instead of write */
+ if ((n > run_length) ||
+ (n > 0 && q == p) ||
+ (n > 0 && q + n >= e)) {
+ if (q > w) {
+ l = write(fd, w, q - w);
+ if (l < 0)
+ return -errno;
+ if (l != q -w)
+ return -EIO;
+ }
+
+ if (lseek(fd, n, SEEK_CUR) == (off_t) -1)
+ return -errno;
+
+ q += n;
+ w = q;
+ } else if (n > 0)
+ q += n;
+ else
+ q ++;
+ }
+
+ if (q > w) {
+ l = write(fd, w, q - w);
+ if (l < 0)
+ return -errno;
+ if (l != q - w)
+ return -EIO;
+ }
+
+ return q - (const uint8_t*) p;
+}
+
+void sigkill_wait(pid_t *pid) {
+ if (!pid)
+ return;
+ if (*pid <= 1)
+ return;
+
+ if (kill(*pid, SIGKILL) > 0)
+ (void) wait_for_terminate(*pid, NULL);
+}
+
+int syslog_parse_priority(const char **p, int *priority, bool with_facility) {
+ int a = 0, b = 0, c = 0;
+ int k;
+
+ assert(p);
+ assert(*p);
+ assert(priority);
+
+ if ((*p)[0] != '<')
+ return 0;
+
+ if (!strchr(*p, '>'))
+ return 0;
+
+ if ((*p)[2] == '>') {
+ c = undecchar((*p)[1]);
+ k = 3;
+ } else if ((*p)[3] == '>') {
+ b = undecchar((*p)[1]);
+ c = undecchar((*p)[2]);
+ k = 4;
+ } else if ((*p)[4] == '>') {
+ a = undecchar((*p)[1]);
+ b = undecchar((*p)[2]);
+ c = undecchar((*p)[3]);
+ k = 5;
+ } else
+ return 0;
+
+ if (a < 0 || b < 0 || c < 0 ||
+ (!with_facility && (a || b || c > 7)))
+ return 0;
+
+ if (with_facility)
+ *priority = a*100 + b*10 + c;
+ else
+ *priority = (*priority & LOG_FACMASK) | c;
+
+ *p += k;
+ return 1;
+}
+
+ssize_t string_table_lookup(const char * const *table, size_t len, const char *key) {
+ size_t i;
+
+ if (!key)
+ return -1;
+
+ for (i = 0; i < len; ++i)
+ if (streq_ptr(table[i], key))
+ return (ssize_t)i;
+
+ return -1;
+}
+
+void cmsg_close_all(struct msghdr *mh) {
+ struct cmsghdr *cmsg;
+
+ assert(mh);
+
+ CMSG_FOREACH(cmsg, mh)
+ if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS)
+ close_many((int*) CMSG_DATA(cmsg), (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int));
+}
+
+int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) {
+ struct stat buf;
+ int ret;
+
+ ret = renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE);
+ if (ret >= 0)
+ return 0;
+
+ /* Even though renameat2() exists since Linux 3.15, btrfs added
+ * support for it later. If it is not implemented, fallback to another
+ * method. */
+ if (errno != EINVAL)
+ return -errno;
+
+ /* The link()/unlink() fallback does not work on directories. But
+ * renameat() without RENAME_NOREPLACE gives the same semantics on
+ * directories, except when newpath is an *empty* directory. This is
+ * good enough. */
+ ret = fstatat(olddirfd, oldpath, &buf, AT_SYMLINK_NOFOLLOW);
+ if (ret >= 0 && S_ISDIR(buf.st_mode)) {
+ ret = renameat(olddirfd, oldpath, newdirfd, newpath);
+ return ret >= 0 ? 0 : -errno;
+ }
+
+ /* If it is not a directory, use the link()/unlink() fallback. */
+ ret = linkat(olddirfd, oldpath, newdirfd, newpath, 0);
+ if (ret < 0)
+ return -errno;
+
+ ret = unlinkat(olddirfd, oldpath, 0);
+ if (ret < 0) {
+ /* backup errno before the following unlinkat() alters it */
+ ret = errno;
+ (void) unlinkat(newdirfd, newpath, 0);
+ errno = ret;
+ return -errno;
+ }
+
+ return 0;
+}
+
+char *shell_maybe_quote(const char *s) {
+ const char *p;
+ char *r, *t;
+
+ assert(s);
+
+ /* Encloses a string in double quotes if necessary to make it
+ * OK as shell string. */
+
+ for (p = s; *p; p++)
+ if (*p <= ' ' ||
+ *p >= 127 ||
+ strchr(SHELL_NEED_QUOTES, *p))
+ break;
+
+ if (!*p)
+ return strdup(s);
+
+ r = new(char, 1+strlen(s)*2+1+1);
+ if (!r)
+ return NULL;
+
+ t = r;
+ *(t++) = '"';
+ t = mempcpy(t, s, p - s);
+
+ for (; *p; p++) {
+
+ if (strchr(SHELL_NEED_ESCAPE, *p))
+ *(t++) = '\\';
+
+ *(t++) = *p;
+ }
+
+ *(t++)= '"';
+ *t = 0;
+
+ return r;
+}
+
+int parse_mode(const char *s, mode_t *ret) {
+ char *x;
+ long l;
+
+ assert(s);
+ assert(ret);
+
+ errno = 0;
+ l = strtol(s, &x, 8);
+ if (errno != 0)
+ return -errno;
+
+ if (!x || x == s || *x)
+ return -EINVAL;
+ if (l < 0 || l > 07777)
+ return -ERANGE;
+
+ *ret = (mode_t) l;
+ return 0;
+}
+
+int mount_move_root(const char *path) {
+ assert(path);
+
+ if (chdir(path) < 0)
+ return -errno;
+
+ if (mount(path, "/", NULL, MS_MOVE, NULL) < 0)
+ return -errno;
+
+ if (chroot(".") < 0)
+ return -errno;
+
+ if (chdir("/") < 0)
+ return -errno;
+
+ return 0;
+}
+
+int reset_uid_gid(void) {
+
+ if (setgroups(0, NULL) < 0)
+ return -errno;
+
+ if (setresgid(0, 0, 0) < 0)
+ return -errno;
+
+ if (setresuid(0, 0, 0) < 0)
+ return -errno;
+
+ return 0;
+}