along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
-#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <signal.h>
+#include <libintl.h>
#include <stdio.h>
#include <syslog.h>
#include <sched.h>
#include <linux/tiocl.h>
#include <termios.h>
#include <stdarg.h>
-#include <sys/inotify.h>
-#include <sys/poll.h>
+#include <poll.h>
#include <ctype.h>
#include <sys/prctl.h>
#include <sys/utsname.h>
#include <pwd.h>
#include <netinet/ip.h>
#include <linux/kd.h>
-#include <dlfcn.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <glob.h>
#include <langinfo.h>
#include <locale.h>
#include <sys/personality.h>
+#include <sys/xattr.h>
+#include <sys/statvfs.h>
+#include <sys/file.h>
+#include <linux/fs.h>
+
+/* When we include libgen.h because we need dirname() we immediately
+ * undefine basename() since libgen.h defines it as a macro to the XDG
+ * version which is really broken. */
#include <libgen.h>
#undef basename
#include <sys/auxv.h>
#endif
+#include "config.h"
#include "macro.h"
#include "util.h"
#include "ioprio.h"
#include "missing.h"
#include "log.h"
#include "strv.h"
-#include "label.h"
#include "mkdir.h"
#include "path-util.h"
#include "exit-status.h"
#include "gunicode.h"
#include "virt.h"
#include "def.h"
+#include "sparse-endian.h"
+#include "formats-util.h"
+#include "process-util.h"
+
+/* Put this test here for a lack of better place */
+assert_cc(EAGAIN == EWOULDBLOCK);
int saved_argc = 0;
char **saved_argv = NULL;
return (char*) p;
}
-static size_t cescape_char(char c, char *buf) {
+size_t cescape_char(char c, char *buf) {
char * buf_old = buf;
switch (c) {
int safe_atod(const char *s, double *ret_d) {
char *x = NULL;
double d = 0;
+ locale_t loc;
assert(s);
assert(ret_d);
- RUN_WITH_LOCALE(LC_NUMERIC_MASK, "C") {
- errno = 0;
- d = strtod(s, &x);
- }
+ loc = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0);
+ if (loc == (locale_t) 0)
+ return -errno;
- if (!x || x == s || *x || errno)
+ errno = 0;
+ d = strtod_l(s, &x, loc);
+
+ if (!x || x == s || *x || errno) {
+ freelocale(loc);
return errno ? -errno : -EINVAL;
+ }
+ freelocale(loc);
*ret_d = (double) d;
return 0;
}
return current;
}
-int get_parent_of_pid(pid_t pid, pid_t *_ppid) {
- int r;
- _cleanup_free_ char *line = NULL;
- long unsigned ppid;
- const char *p;
-
- assert(pid >= 0);
- assert(_ppid);
-
- if (pid == 0) {
- *_ppid = getppid();
- return 0;
- }
-
- p = procfs_file_alloca(pid, "stat");
- r = read_one_line_file(p, &line);
- if (r < 0)
- return r;
-
- /* Let's skip the pid and comm fields. The latter is enclosed
- * in () but does not escape any () in its value, so let's
- * skip over it manually */
-
- p = strrchr(line, ')');
- if (!p)
- return -EIO;
-
- p++;
-
- if (sscanf(p, " "
- "%*c " /* state */
- "%lu ", /* ppid */
- &ppid) != 1)
- return -EIO;
-
- if ((long unsigned) (pid_t) ppid != ppid)
- return -ERANGE;
-
- *_ppid = (pid_t) ppid;
-
- return 0;
-}
-
int fchmod_umask(int fd, mode_t m) {
mode_t u;
int r;
return s;
}
-int get_process_state(pid_t pid) {
- const char *p;
- char state;
- int r;
- _cleanup_free_ char *line = NULL;
-
- assert(pid >= 0);
-
- p = procfs_file_alloca(pid, "stat");
- r = read_one_line_file(p, &line);
- if (r < 0)
- return r;
-
- p = strrchr(line, ')');
- if (!p)
- return -EIO;
-
- p++;
-
- if (sscanf(p, " %c", &state) != 1)
- return -EIO;
-
- return (unsigned char) state;
-}
-
-int get_process_comm(pid_t pid, char **name) {
- const char *p;
- int r;
-
- assert(name);
- assert(pid >= 0);
-
- p = procfs_file_alloca(pid, "comm");
-
- r = read_one_line_file(p, name);
- if (r == -ENOENT)
- return -ESRCH;
-
- return r;
-}
-
-int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line) {
- _cleanup_fclose_ FILE *f = NULL;
- char *r = NULL, *k;
- const char *p;
- int c;
-
- assert(line);
- assert(pid >= 0);
-
- p = procfs_file_alloca(pid, "cmdline");
-
- f = fopen(p, "re");
- if (!f)
- return -errno;
-
- if (max_length == 0) {
- size_t len = 0, allocated = 0;
-
- while ((c = getc(f)) != EOF) {
-
- if (!GREEDY_REALLOC(r, allocated, len+2)) {
- free(r);
- return -ENOMEM;
- }
-
- r[len++] = isprint(c) ? c : ' ';
- }
-
- if (len > 0)
- r[len-1] = 0;
-
- } else {
- bool space = false;
- size_t left;
-
- r = new(char, max_length);
- if (!r)
- return -ENOMEM;
-
- k = r;
- left = max_length;
- while ((c = getc(f)) != EOF) {
-
- if (isprint(c)) {
- if (space) {
- if (left <= 4)
- break;
-
- *(k++) = ' ';
- left--;
- space = false;
- }
-
- if (left <= 4)
- break;
-
- *(k++) = (char) c;
- left--;
- } else
- space = true;
- }
-
- if (left <= 4) {
- size_t n = MIN(left-1, 3U);
- memcpy(k, "...", n);
- k[n] = 0;
- } else
- *k = 0;
- }
-
- /* Kernel threads have no argv[] */
- if (isempty(r)) {
- _cleanup_free_ char *t = NULL;
- int h;
-
- free(r);
-
- if (!comm_fallback)
- return -ENOENT;
-
- h = get_process_comm(pid, &t);
- if (h < 0)
- return h;
-
- r = strjoin("[", t, "]", NULL);
- if (!r)
- return -ENOMEM;
- }
-
- *line = r;
- return 0;
-}
-
-int is_kernel_thread(pid_t pid) {
- const char *p;
- size_t count;
- char c;
- bool eof;
- FILE *f;
-
- if (pid == 0)
- return 0;
-
- assert(pid > 0);
-
- p = procfs_file_alloca(pid, "cmdline");
- f = fopen(p, "re");
- if (!f)
- return -errno;
-
- count = fread(&c, 1, 1, f);
- eof = feof(f);
- fclose(f);
-
- /* Kernel threads have an empty cmdline */
-
- if (count <= 0)
- return eof ? 1 : -errno;
-
- return 0;
-}
-
-int get_process_capeff(pid_t pid, char **capeff) {
- const char *p;
-
- assert(capeff);
- assert(pid >= 0);
-
- p = procfs_file_alloca(pid, "status");
-
- return get_status_field(p, "\nCapEff:", capeff);
-}
-
-static int get_process_link_contents(const char *proc_file, char **name) {
- int r;
-
- assert(proc_file);
- assert(name);
-
- r = readlink_malloc(proc_file, name);
- if (r < 0)
- return r == -ENOENT ? -ESRCH : r;
-
- return 0;
-}
-
-int get_process_exe(pid_t pid, char **name) {
- const char *p;
- char *d;
- int r;
-
- assert(pid >= 0);
-
- p = procfs_file_alloca(pid, "exe");
- r = get_process_link_contents(p, name);
- if (r < 0)
- return r;
-
- d = endswith(*name, " (deleted)");
- if (d)
- *d = '\0';
-
- return 0;
-}
-
-static int get_process_id(pid_t pid, const char *field, uid_t *uid) {
- _cleanup_fclose_ FILE *f = NULL;
- char line[LINE_MAX];
- const char *p;
-
- assert(field);
- assert(uid);
-
- if (pid == 0)
- return getuid();
-
- p = procfs_file_alloca(pid, "status");
- f = fopen(p, "re");
- if (!f)
- return -errno;
-
- FOREACH_LINE(line, f, return -errno) {
- char *l;
-
- l = strstrip(line);
-
- if (startswith(l, field)) {
- l += strlen(field);
- l += strspn(l, WHITESPACE);
-
- l[strcspn(l, WHITESPACE)] = 0;
-
- return parse_uid(l, uid);
- }
- }
-
- return -EIO;
-}
-
-int get_process_uid(pid_t pid, uid_t *uid) {
- return get_process_id(pid, "Uid:", uid);
-}
-
-int get_process_gid(pid_t pid, gid_t *gid) {
- assert_cc(sizeof(uid_t) == sizeof(gid_t));
- return get_process_id(pid, "Gid:", gid);
-}
-
-int get_process_cwd(pid_t pid, char **cwd) {
- const char *p;
-
- assert(pid >= 0);
-
- p = procfs_file_alloca(pid, "cwd");
-
- return get_process_link_contents(p, cwd);
-}
-
-int get_process_root(pid_t pid, char **root) {
- const char *p;
-
- assert(pid >= 0);
-
- p = procfs_file_alloca(pid, "root");
-
- return get_process_link_contents(p, root);
-}
-
-int get_process_environ(pid_t pid, char **env) {
- _cleanup_fclose_ FILE *f = NULL;
- _cleanup_free_ char *outcome = NULL;
- int c;
- const char *p;
- size_t allocated = 0, sz = 0;
-
- assert(pid >= 0);
- assert(env);
-
- p = procfs_file_alloca(pid, "environ");
-
- f = fopen(p, "re");
- if (!f)
- return -errno;
-
- while ((c = fgetc(f)) != EOF) {
- if (!GREEDY_REALLOC(outcome, allocated, sz + 5))
- return -ENOMEM;
-
- if (c == '\0')
- outcome[sz++] = '\n';
- else
- sz += cescape_char(c, outcome + sz);
- }
-
- outcome[sz] = '\0';
- *env = outcome;
- outcome = NULL;
-
- return 0;
-}
-
char *strnappend(const char *s, const char *suffix, size_t b) {
size_t a;
char *r;
}
char *file_in_same_dir(const char *path, const char *filename) {
- char *e, *r;
+ char *e, *ret;
size_t k;
assert(path);
if (path_is_absolute(filename))
return strdup(filename);
- if (!(e = strrchr(path, '/')))
+ e = strrchr(path, '/');
+ if (!e)
return strdup(filename);
k = strlen(filename);
- if (!(r = new(char, e-path+1+k+1)))
+ ret = new(char, (e + 1 - path) + k + 1);
+ if (!ret)
return NULL;
- memcpy(r, path, e-path+1);
- memcpy(r+(e-path)+1, filename, k+1);
-
- return r;
+ memcpy(mempcpy(ret, path, e + 1 - path), filename, k + 1);
+ return ret;
}
int rmdir_parents(const char *path, const char *stop) {
assert(s);
- /* Does C style string escaping. */
+ /* Does C style string escaping. May be reversed with
+ * cunescape(). */
r = new(char, strlen(s)*4 + 1);
if (!r)
return r;
}
-char *cunescape_length_with_prefix(const char *s, size_t length, const char *prefix) {
- char *r, *t;
- const char *f;
- size_t pl;
+static int cunescape_one(const char *p, size_t length, char *ret, uint32_t *ret_unicode) {
+ int r = 1;
- assert(s);
+ assert(p);
+ assert(*p);
+ assert(ret);
- /* Undoes C style string escaping, and optionally prefixes it. */
+ /* Unescapes C style. Returns the unescaped character in ret,
+ * unless we encountered a \u sequence in which case the full
+ * unicode character is returned in ret_unicode, instead. */
- pl = prefix ? strlen(prefix) : 0;
+ if (length != (size_t) -1 && length < 1)
+ return -EINVAL;
- r = new(char, pl+length+1);
- if (!r)
- return NULL;
+ switch (p[0]) {
+
+ case 'a':
+ *ret = '\a';
+ break;
+ case 'b':
+ *ret = '\b';
+ break;
+ case 'f':
+ *ret = '\f';
+ break;
+ case 'n':
+ *ret = '\n';
+ break;
+ case 'r':
+ *ret = '\r';
+ break;
+ case 't':
+ *ret = '\t';
+ break;
+ case 'v':
+ *ret = '\v';
+ break;
+ case '\\':
+ *ret = '\\';
+ break;
+ case '"':
+ *ret = '"';
+ break;
+ case '\'':
+ *ret = '\'';
+ break;
+
+ case 's':
+ /* This is an extension of the XDG syntax files */
+ *ret = ' ';
+ break;
+
+ case 'x': {
+ /* hexadecimal encoding */
+ int a, b;
- if (prefix)
- memcpy(r, prefix, pl);
+ if (length != (size_t) -1 && length < 3)
+ return -EINVAL;
- for (f = s, t = r + pl; f < s + length; f++) {
+ a = unhexchar(p[1]);
+ if (a < 0)
+ return -EINVAL;
- if (*f != '\\') {
- *(t++) = *f;
- continue;
+ b = unhexchar(p[2]);
+ if (b < 0)
+ return -EINVAL;
+
+ /* Don't allow NUL bytes */
+ if (a == 0 && b == 0)
+ return -EINVAL;
+
+ *ret = (char) ((a << 4U) | b);
+ r = 3;
+ break;
+ }
+
+ case 'u': {
+ /* C++11 style 16bit unicode */
+
+ int a[4];
+ unsigned i;
+ uint32_t c;
+
+ if (length != (size_t) -1 && length < 5)
+ return -EINVAL;
+
+ for (i = 0; i < 4; i++) {
+ a[i] = unhexchar(p[1 + i]);
+ if (a[i] < 0)
+ return a[i];
}
- f++;
+ c = ((uint32_t) a[0] << 12U) | ((uint32_t) a[1] << 8U) | ((uint32_t) a[2] << 4U) | (uint32_t) a[3];
- switch (*f) {
+ /* Don't allow 0 chars */
+ if (c == 0)
+ return -EINVAL;
- case 'a':
- *(t++) = '\a';
- break;
- case 'b':
- *(t++) = '\b';
- break;
- case 'f':
- *(t++) = '\f';
- break;
- case 'n':
- *(t++) = '\n';
- break;
- case 'r':
- *(t++) = '\r';
- break;
- case 't':
- *(t++) = '\t';
- break;
- case 'v':
- *(t++) = '\v';
- break;
- case '\\':
- *(t++) = '\\';
- break;
- case '"':
- *(t++) = '"';
- break;
- case '\'':
- *(t++) = '\'';
- break;
+ if (c < 128)
+ *ret = c;
+ else {
+ if (!ret_unicode)
+ return -EINVAL;
- case 's':
- /* This is an extension of the XDG syntax files */
- *(t++) = ' ';
- break;
+ *ret = 0;
+ *ret_unicode = c;
+ }
- case 'x': {
- /* hexadecimal encoding */
- int a, b;
+ r = 5;
+ break;
+ }
- a = unhexchar(f[1]);
- b = unhexchar(f[2]);
+ case 'U': {
+ /* C++11 style 32bit unicode */
- if (a < 0 || b < 0 || (a == 0 && b == 0)) {
- /* Invalid escape code, let's take it literal then */
- *(t++) = '\\';
- *(t++) = 'x';
- } else {
- *(t++) = (char) ((a << 4) | b);
- f += 2;
+ int a[8];
+ unsigned i;
+ uint32_t c;
+
+ if (length != (size_t) -1 && length < 9)
+ return -EINVAL;
+
+ for (i = 0; i < 8; i++) {
+ a[i] = unhexchar(p[1 + i]);
+ if (a[i] < 0)
+ return a[i];
+ }
+
+ c = ((uint32_t) a[0] << 28U) | ((uint32_t) a[1] << 24U) | ((uint32_t) a[2] << 20U) | ((uint32_t) a[3] << 16U) |
+ ((uint32_t) a[4] << 12U) | ((uint32_t) a[5] << 8U) | ((uint32_t) a[6] << 4U) | (uint32_t) a[7];
+
+ /* Don't allow 0 chars */
+ if (c == 0)
+ return -EINVAL;
+
+ /* Don't allow invalid code points */
+ if (!unichar_is_valid(c))
+ return -EINVAL;
+
+ if (c < 128)
+ *ret = c;
+ else {
+ if (!ret_unicode)
+ return -EINVAL;
+
+ *ret = 0;
+ *ret_unicode = c;
+ }
+
+ r = 9;
+ break;
+ }
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7': {
+ /* octal encoding */
+ int a, b, c;
+ uint32_t m;
+
+ if (length != (size_t) -1 && length < 4)
+ return -EINVAL;
+
+ a = unoctchar(p[0]);
+ if (a < 0)
+ return -EINVAL;
+
+ b = unoctchar(p[1]);
+ if (b < 0)
+ return -EINVAL;
+
+ c = unoctchar(p[2]);
+ if (c < 0)
+ return -EINVAL;
+
+ /* don't allow NUL bytes */
+ if (a == 0 && b == 0 && c == 0)
+ return -EINVAL;
+
+ /* Don't allow bytes above 255 */
+ m = ((uint32_t) a << 6U) | ((uint32_t) b << 3U) | (uint32_t) c;
+ if (m > 255)
+ return -EINVAL;
+
+ *ret = m;
+ r = 3;
+ break;
+ }
+
+ default:
+ return -EINVAL;
+ }
+
+ return r;
+}
+
+int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret) {
+ char *r, *t;
+ const char *f;
+ size_t pl;
+
+ assert(s);
+ assert(ret);
+
+ /* Undoes C style string escaping, and optionally prefixes it. */
+
+ pl = prefix ? strlen(prefix) : 0;
+
+ r = new(char, pl+length+1);
+ if (!r)
+ return -ENOMEM;
+
+ if (prefix)
+ memcpy(r, prefix, pl);
+
+ for (f = s, t = r + pl; f < s + length; f++) {
+ size_t remaining;
+ uint32_t u;
+ char c;
+ int k;
+
+ remaining = s + length - f;
+ assert(remaining > 0);
+
+ if (*f != '\\') {
+ /* A literal literal, copy verbatim */
+ *(t++) = *f;
+ continue;
+ }
+
+ if (remaining == 1) {
+ if (flags & UNESCAPE_RELAX) {
+ /* A trailing backslash, copy verbatim */
+ *(t++) = *f;
+ continue;
}
- break;
+ free(r);
+ return -EINVAL;
}
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7': {
- /* octal encoding */
- int a, b, c;
-
- a = unoctchar(f[0]);
- b = unoctchar(f[1]);
- c = unoctchar(f[2]);
-
- if (a < 0 || b < 0 || c < 0 || (a == 0 && b == 0 && c == 0)) {
+ k = cunescape_one(f + 1, remaining - 1, &c, &u);
+ if (k < 0) {
+ if (flags & UNESCAPE_RELAX) {
/* Invalid escape code, let's take it literal then */
*(t++) = '\\';
- *(t++) = f[0];
- } else {
- *(t++) = (char) ((a << 6) | (b << 3) | c);
- f += 2;
+ continue;
}
- break;
+ free(r);
+ return k;
}
- case 0:
- /* premature end of string. */
- *(t++) = '\\';
- goto finish;
+ if (c != 0)
+ /* Non-Unicode? Let's encode this directly */
+ *(t++) = c;
+ else
+ /* Unicode? Then let's encode this in UTF-8 */
+ t += utf8_encode_unichar(t, u);
- default:
- /* Invalid escape code, let's take it literal then */
- *(t++) = '\\';
- *(t++) = *f;
- break;
- }
+ f += k;
}
-finish:
*t = 0;
- return r;
-}
-char *cunescape_length(const char *s, size_t length) {
- return cunescape_length_with_prefix(s, length, NULL);
+ *ret = r;
+ return t - r;
}
-char *cunescape(const char *s) {
- assert(s);
+int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret) {
+ return cunescape_length_with_prefix(s, length, NULL, flags, ret);
+}
- return cunescape_length(s, strlen(s));
+int cunescape(const char *s, UnescapeFlags flags, char **ret) {
+ return cunescape_length(s, strlen(s), flags, ret);
}
char *xescape(const char *s, const char *bad) {
/* Escapes all chars in bad, in addition to \ and all special
* chars, in \xFF style escaping. May be reversed with
- * cunescape. */
+ * cunescape(). */
r = new(char, strlen(s) * 4 + 1);
if (!r)
endswith(filename, ".dpkg-old") ||
endswith(filename, ".dpkg-new") ||
endswith(filename, ".dpkg-tmp") ||
+ endswith(filename, ".dpkg-dist") ||
+ endswith(filename, ".dpkg-bak") ||
+ endswith(filename, ".dpkg-backup") ||
+ endswith(filename, ".dpkg-remove") ||
endswith(filename, ".swp");
}
bool fstype_is_network(const char *fstype) {
static const char table[] =
+ "afs\0"
"cifs\0"
"smbfs\0"
"sshfs\0"
assert(notify >= 0);
for (;;) {
- uint8_t buffer[INOTIFY_EVENT_MAX] _alignas_(struct inotify_event);
+ union inotify_event_buffer buffer;
struct inotify_event *e;
ssize_t l;
}
}
- l = read(notify, buffer, sizeof(buffer));
+ l = read(notify, &buffer, sizeof(buffer));
if (l < 0) {
if (errno == EINTR || errno == EAGAIN)
continue;
return n;
}
+int loop_read_exact(int fd, void *buf, size_t nbytes, bool do_poll) {
+ ssize_t n;
+
+ n = loop_read(fd, buf, nbytes, do_poll);
+ if (n < 0)
+ return n;
+ if ((size_t) n != nbytes)
+ return -EIO;
+ return 0;
+}
+
int loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) {
const uint8_t *p = buf;
int dev_urandom(void *p, size_t n) {
static int have_syscall = -1;
- int r, fd;
- ssize_t k;
+
+ _cleanup_close_ int fd = -1;
+ int r;
/* Gathers some randomness from the kernel. This call will
* never block, and will always return some data from the
return -errno;
} else
/* too short read? */
- return -EIO;
+ return -ENODATA;
}
fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY);
if (fd < 0)
return errno == ENOENT ? -ENOSYS : -errno;
- k = loop_read(fd, p, n, true);
- safe_close(fd);
-
- if (k < 0)
- return (int) k;
- if ((size_t) k != n)
- return -EIO;
-
- return 0;
+ return loop_read_exact(fd, p, n, true);
}
void initialize_srand(void) {
int getttyname_harder(int fd, char **r) {
int k;
- char *s;
+ char *s = NULL;
k = getttyname_malloc(fd, &s);
if (k < 0)
/* This is an ugly hack */
if (major(devnr) == 136) {
- asprintf(&b, "pts/%u", minor(devnr));
- goto finish;
- }
+ if (asprintf(&b, "pts/%u", minor(devnr)) < 0)
+ return -ENOMEM;
+ } else {
+ /* Probably something like the ptys which have no
+ * symlink in /dev/char. Let's return something
+ * vaguely useful. */
- /* Probably something like the ptys which have no
- * symlink in /dev/char. Let's return something
- * vaguely useful. */
+ b = strdup(fn + 5);
+ if (!b)
+ return -ENOMEM;
+ }
+ } else {
+ if (startswith(s, "/dev/"))
+ p = s + 5;
+ else if (startswith(s, "../"))
+ p = s + 3;
+ else
+ p = s;
- b = strdup(fn + 5);
- goto finish;
+ b = strdup(p);
+ if (!b)
+ return -ENOMEM;
}
- if (startswith(s, "/dev/"))
- p = s + 5;
- else if (startswith(s, "../"))
- p = s + 3;
- else
- p = s;
-
- b = strdup(p);
-
-finish:
- if (!b)
- return -ENOMEM;
-
*r = b;
if (_devnr)
*_devnr = devnr;
return 0;
}
-int rm_rf_children_dangerous(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev) {
- _cleanup_closedir_ DIR *d = NULL;
- int ret = 0;
-
- assert(fd >= 0);
-
- /* This returns the first error we run into, but nevertheless
- * tries to go on. This closes the passed fd. */
-
- d = fdopendir(fd);
- if (!d) {
- safe_close(fd);
-
- return errno == ENOENT ? 0 : -errno;
- }
-
- for (;;) {
- struct dirent *de;
- bool is_dir, keep_around;
- struct stat st;
- int r;
-
- errno = 0;
- de = readdir(d);
- if (!de) {
- if (errno != 0 && ret == 0)
- ret = -errno;
- return ret;
- }
-
- if (streq(de->d_name, ".") || streq(de->d_name, ".."))
- continue;
-
- 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;
- }
-
- 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 {
- is_dir = de->d_type == DT_DIR;
- keep_around = false;
- }
-
- if (is_dir) {
- int subdir_fd;
-
- /* 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_dangerous(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) {
- if (ret == 0 && errno != ENOENT)
- ret = -errno;
- }
-
- } else if (!only_dirs && !keep_around) {
-
- if (unlinkat(fd, de->d_name, 0) < 0) {
- if (ret == 0 && errno != ENOENT)
- ret = -errno;
- }
- }
- }
-}
-
-_pure_ static int is_temporary_fs(struct statfs *s) {
+bool is_temporary_fs(const struct statfs *s) {
assert(s);
return F_TYPE_EQUAL(s->f_type, TMPFS_MAGIC) ||
F_TYPE_EQUAL(s->f_type, RAMFS_MAGIC);
}
-int is_fd_on_temporary_fs(int fd) {
+int fd_is_temporary_fs(int fd) {
struct statfs s;
if (fstatfs(fd, &s) < 0)
return is_temporary_fs(&s);
}
-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) {
- safe_close(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 (!is_temporary_fs(&s)) {
- log_error("Attempted to remove disk file system, and we can't allow that.");
- safe_close(fd);
- return -EPERM;
- }
-
- return rm_rf_children_dangerous(fd, only_dirs, honour_sticky, root_dev);
-}
-
-static int file_is_priv_sticky(const char *p) {
- struct stat st;
-
- assert(p);
-
- if (lstat(p, &st) < 0)
- return -errno;
-
- return
- (st.st_uid == 0 || st.st_uid == getuid()) &&
- (st.st_mode & S_ISVTX);
-}
-
-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 && errno != ELOOP)
- return -errno;
-
- if (!dangerous) {
- if (statfs(path, &s) < 0)
- return -errno;
-
- if (!is_temporary_fs(&s)) {
- 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;
- }
-
- if (!dangerous) {
- if (fstatfs(fd, &s) < 0) {
- safe_close(fd);
- return -errno;
- }
-
- if (!is_temporary_fs(&s)) {
- log_error("Attempted to remove disk file system, and we can't allow that.");
- safe_close(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;
-
- if (rmdir(path) < 0 && errno != ENOENT) {
- if (r == 0)
- r = -errno;
- }
- }
-
- 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);
/* If $FOO appears as single word, replace it by the split up variable */
if ((*i)[0] == '$' && (*i)[1] != '{') {
char *e;
- char **w, **m;
+ char **w, **m = NULL;
unsigned q;
e = strv_env_get(env, *i+1);
if (e) {
int r;
- r = strv_split_quoted(&m, e, true);
+ r = strv_split_quoted(&m, e, UNQUOTE_RELAX);
if (r < 0) {
ret[k] = NULL;
strv_free(ret);
c = 80;
cached_columns = c;
- return c;
+ return cached_columns;
}
int fd_lines(int fd) {
unsigned lines(void) {
const char *e;
- unsigned l;
+ int l;
if (_likely_(cached_lines > 0))
return cached_lines;
l = 0;
e = getenv("LINES");
if (e)
- (void) safe_atou(e, &l);
+ (void) safe_atoi(e, &l);
if (l <= 0)
l = fd_lines(STDOUT_FILENO);
return touch_file(path, false, USEC_INFINITY, UID_INVALID, GID_INVALID, 0);
}
-char *unquote(const char *s, const char* quotes) {
+static char *unquote(const char *s, const char* quotes) {
size_t l;
assert(s);
/* This is rather stupid, simply removes the heading and
* trailing quotes if there is one. Doesn't care about
- * escaping or anything. We should make this smarter one
- * day... */
+ * escaping or anything.
+ *
+ * DON'T USE THIS FOR NEW CODE ANYMORE!*/
l = strlen(s);
if (l < 2)
return strdup(s);
}
-char *normalize_env_assignment(const char *s) {
- _cleanup_free_ char *value = NULL;
- const char *eq;
- char *p, *name;
+noreturn void freeze(void) {
- eq = strchr(s, '=');
- if (!eq) {
- char *r, *t;
-
- r = strdup(s);
- if (!r)
- return NULL;
-
- t = strstrip(r);
- if (t != r)
- memmove(r, t, strlen(t) + 1);
-
- return r;
- }
-
- name = strndupa(s, eq - s);
- p = strdupa(eq + 1);
-
- value = unquote(strstrip(p), QUOTES);
- if (!value)
- return NULL;
-
- return strjoin(strstrip(name), "=", value, NULL);
-}
-
-int wait_for_terminate(pid_t pid, siginfo_t *status) {
- siginfo_t dummy;
-
- assert(pid >= 1);
-
- if (!status)
- status = &dummy;
-
- for (;;) {
- zero(*status);
-
- if (waitid(P_PID, pid, status, WEXITED) < 0) {
-
- if (errno == EINTR)
- continue;
-
- return -errno;
- }
-
- return 0;
- }
-}
-
-/*
- * 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.
- *
- * A warning is emitted if the process terminates abnormally,
- * and also if it returns non-zero unless check_exit_code is true.
- */
-int wait_for_terminate_and_warn(const char *name, pid_t pid, bool check_exit_code) {
- int r;
- siginfo_t status;
-
- assert(name);
- assert(pid > 1);
-
- r = wait_for_terminate(pid, &status);
- if (r < 0)
- return log_warning_errno(r, "Failed to wait for %s: %m", name);
-
- if (status.si_code == CLD_EXITED) {
- if (status.si_status != 0)
- log_full(check_exit_code ? LOG_WARNING : LOG_DEBUG,
- "%s failed with error code %i.", name, status.si_status);
- else
- log_debug("%s succeeded.", name);
-
- return status.si_status;
- } else if (status.si_code == CLD_KILLED ||
- status.si_code == CLD_DUMPED) {
-
- log_warning("%s terminated by signal %s.", name, signal_to_string(status.si_status));
- return -EPROTO;
- }
-
- log_warning("%s failed due to unknown reason.", name);
- return -EPROTO;
-}
-
-noreturn void freeze(void) {
-
- /* Make sure nobody waits for us on a socket anymore */
- close_all_fds(NULL, 0);
+ /* Make sure nobody waits for us on a socket anymore */
+ close_all_fds(NULL, 0);
sync();
_cleanup_free_ char *t = NULL, *u = NULL;
size_t enc_len;
- u = unquote(tagvalue, "\"\'");
+ u = unquote(tagvalue, QUOTES);
if (!u)
return NULL;
const char *default_term_for_tty(const char *tty) {
assert(tty);
- return tty_is_vc_resolve(tty) ? "TERM=linux" : "TERM=vt102";
+ return tty_is_vc_resolve(tty) ? "TERM=linux" : "TERM=vt220";
}
bool dirent_is_file(const struct dirent *de) {
return endswith(de->d_name, suffix);
}
-void execute_directory(const char *directory, DIR *d, usec_t timeout, char *argv[]) {
- pid_t executor_pid;
- int r;
+static int do_execute(char **directories, usec_t timeout, char *argv[]) {
+ _cleanup_hashmap_free_free_ Hashmap *pids = NULL;
+ _cleanup_set_free_free_ Set *seen = NULL;
+ char **directory;
- assert(directory);
+ /* We fork this all off from a child process so that we can
+ * somewhat cleanly make use of SIGALRM to set a time limit */
- /* Executes all binaries in a directory in parallel and waits
- * for them to finish. Optionally a timeout is applied. */
+ reset_all_signal_handlers();
+ reset_signal_mask();
- executor_pid = fork();
- if (executor_pid < 0) {
- log_error_errno(errno, "Failed to fork: %m");
- return;
+ assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
- } else if (executor_pid == 0) {
- _cleanup_hashmap_free_free_ Hashmap *pids = NULL;
- _cleanup_closedir_ DIR *_d = NULL;
- struct dirent *de;
-
- /* We fork this all off from a child process so that
- * we can somewhat cleanly make use of SIGALRM to set
- * a time limit */
+ pids = hashmap_new(NULL);
+ if (!pids)
+ return log_oom();
- reset_all_signal_handlers();
- reset_signal_mask();
+ seen = set_new(&string_hash_ops);
+ if (!seen)
+ return log_oom();
- assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
+ STRV_FOREACH(directory, directories) {
+ _cleanup_closedir_ DIR *d;
+ struct dirent *de;
+ d = opendir(*directory);
if (!d) {
- d = _d = opendir(directory);
- if (!d) {
- if (errno == ENOENT)
- _exit(EXIT_SUCCESS);
-
- log_error_errno(errno, "Failed to enumerate directory %s: %m", directory);
- _exit(EXIT_FAILURE);
- }
- }
+ if (errno == ENOENT)
+ continue;
- pids = hashmap_new(NULL);
- if (!pids) {
- log_oom();
- _exit(EXIT_FAILURE);
+ return log_error_errno(errno, "Failed to open directory %s: %m", *directory);
}
FOREACH_DIRENT(de, d, break) {
_cleanup_free_ char *path = NULL;
pid_t pid;
+ int r;
if (!dirent_is_file(de))
continue;
- path = strjoin(directory, "/", de->d_name, NULL);
- if (!path) {
- log_oom();
- _exit(EXIT_FAILURE);
+ if (set_contains(seen, de->d_name)) {
+ log_debug("%1$s/%2$s skipped (%2$s was already seen).", *directory, de->d_name);
+ continue;
+ }
+
+ r = set_put_strdup(seen, de->d_name);
+ if (r < 0)
+ return log_oom();
+
+ path = strjoin(*directory, "/", de->d_name, NULL);
+ if (!path)
+ return log_oom();
+
+ if (null_or_empty_path(path)) {
+ log_debug("%s is empty (a mask).", path);
+ continue;
}
pid = fork();
argv[0] = path;
execv(path, argv);
- log_error_errno(errno, "Failed to execute %s: %m", path);
- _exit(EXIT_FAILURE);
+ return log_error_errno(errno, "Failed to execute %s: %m", path);
}
log_debug("Spawned %s as " PID_FMT ".", path, pid);
r = hashmap_put(pids, UINT_TO_PTR(pid), path);
- if (r < 0) {
- log_oom();
- _exit(EXIT_FAILURE);
- }
-
+ if (r < 0)
+ return log_oom();
path = NULL;
}
+ }
- /* Abort execution of this process after the
- * timout. We simply rely on SIGALRM as default action
- * terminating the process, and turn on alarm(). */
-
- if (timeout != USEC_INFINITY)
- alarm((timeout + USEC_PER_SEC - 1) / USEC_PER_SEC);
+ /* Abort execution of this process after the timout. We simply
+ * rely on SIGALRM as default action terminating the process,
+ * and turn on alarm(). */
- while (!hashmap_isempty(pids)) {
- _cleanup_free_ char *path = NULL;
- pid_t pid;
+ if (timeout != USEC_INFINITY)
+ alarm((timeout + USEC_PER_SEC - 1) / USEC_PER_SEC);
- pid = PTR_TO_UINT(hashmap_first_key(pids));
- assert(pid > 0);
+ while (!hashmap_isempty(pids)) {
+ _cleanup_free_ char *path = NULL;
+ pid_t pid;
- path = hashmap_remove(pids, UINT_TO_PTR(pid));
- assert(path);
+ pid = PTR_TO_UINT(hashmap_first_key(pids));
+ assert(pid > 0);
- wait_for_terminate_and_warn(path, pid, true);
- }
+ path = hashmap_remove(pids, UINT_TO_PTR(pid));
+ assert(path);
- _exit(EXIT_SUCCESS);
+ wait_for_terminate_and_warn(path, pid, true);
}
- wait_for_terminate_and_warn(directory, executor_pid, true);
+ return 0;
}
-int kill_and_sigcont(pid_t pid, int sig) {
+void execute_directories(const char* const* directories, usec_t timeout, char *argv[]) {
+ pid_t executor_pid;
int r;
+ char *name;
+ char **dirs = (char**) directories;
- r = kill(pid, sig) < 0 ? -errno : 0;
+ assert(!strv_isempty(dirs));
- if (r >= 0)
- kill(pid, SIGCONT);
+ name = basename(dirs[0]);
+ assert(!isempty(name));
- return r;
+ /* Executes all binaries in the directories in parallel and waits
+ * for them to finish. Optionally a timeout is applied. If a file
+ * with the same name exists in more than one directory, the
+ * earliest one wins. */
+
+ executor_pid = fork();
+ if (executor_pid < 0) {
+ log_error_errno(errno, "Failed to fork: %m");
+ return;
+
+ } else if (executor_pid == 0) {
+ r = do_execute(dirs, timeout, argv);
+ _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
+ }
+
+ wait_for_terminate_and_warn(name, executor_pid, true);
}
bool nulstr_contains(const char*nulstr, const char *needle) {
if (isempty(s))
return false;
+ /* Doesn't accept empty hostnames, hostnames with trailing or
+ * leading dots, and hostnames with multiple dots in a
+ * sequence. Also ensures that the length stays below
+ * HOST_NAME_MAX. */
+
for (p = s, dot = true; *p; p++) {
if (*p == '.') {
if (dot)
return true;
}
-bool image_name_is_valid(const char *s) {
- if (!filename_is_valid(s))
- return false;
-
- if (string_has_cc(s, NULL))
- return false;
-
- if (!utf8_is_valid(s))
- return false;
-
- /* Temporary files for atomically creating new files */
- if (startswith(s, ".#"))
- return false;
-
- return true;
-}
-
int pipe_eof(int fd) {
struct pollfd pollfd = {
.fd = fd,
{ "K", 1024ULL },
};
+ if (t == (off_t) -1)
+ return NULL;
+
for (i = 0; i < ELEMENTSOF(table); i++) {
if (t >= table[i].factor) {
return 0;
}
-int getenv_for_pid(pid_t pid, const char *field, char **_value) {
- _cleanup_fclose_ FILE *f = NULL;
- char *value = NULL;
- int r;
- bool done = false;
- size_t l;
- const char *path;
-
- assert(pid >= 0);
- assert(field);
- assert(_value);
-
- path = procfs_file_alloca(pid, "environ");
-
- f = fopen(path, "re");
- if (!f)
- return -errno;
-
- l = strlen(field);
- r = 0;
+bool http_etag_is_valid(const char *etag) {
+ if (isempty(etag))
+ return false;
- do {
- char line[LINE_MAX];
- unsigned i;
+ if (!endswith(etag, "\""))
+ return false;
- for (i = 0; i < sizeof(line)-1; i++) {
- int c;
+ if (!startswith(etag, "\"") && !startswith(etag, "W/\""))
+ return false;
- c = getc(f);
- if (_unlikely_(c == EOF)) {
- done = true;
- break;
- } else if (c == 0)
- break;
+ return true;
+}
- line[i] = c;
- }
- line[i] = 0;
+bool http_url_is_valid(const char *url) {
+ const char *p;
- if (memcmp(line, field, l) == 0 && line[l] == '=') {
- value = strdup(line + l + 1);
- if (!value)
- return -ENOMEM;
+ if (isempty(url))
+ return false;
- r = 1;
- break;
- }
+ p = startswith(url, "http://");
+ if (!p)
+ p = startswith(url, "https://");
+ if (!p)
+ return false;
- } while (!done);
+ if (isempty(p))
+ return false;
- *_value = value;
- return r;
+ return ascii_is_valid(p);
}
-bool is_valid_documentation_url(const char *url) {
- assert(url);
-
- if (startswith(url, "http://") && url[7])
- return true;
+bool documentation_url_is_valid(const char *url) {
+ const char *p;
- if (startswith(url, "https://") && url[8])
- return true;
+ if (isempty(url))
+ return false;
- if (startswith(url, "file:") && url[5])
+ if (http_url_is_valid(url))
return true;
- if (startswith(url, "info:") && url[5])
- return true;
+ p = startswith(url, "file:/");
+ if (!p)
+ p = startswith(url, "info:");
+ if (!p)
+ p = startswith(url, "man:");
- if (startswith(url, "man:") && url[4])
- return true;
+ if (isempty(p))
+ return false;
- return false;
+ return ascii_is_valid(p);
}
bool in_initrd(void) {
return NULL;
}
+void init_gettext(void) {
+ setlocale(LC_ALL, "");
+ textdomain(GETTEXT_PACKAGE);
+}
+
bool is_locale_utf8(void) {
const char *set;
static int cached_answer = -1;
d = opendir("/sys/class/power_supply");
if (!d)
- return -errno;
+ return errno == ENOENT ? true : -errno;
for (;;) {
struct dirent *de;
_cleanup_free_ char *word = NULL;
char *value = NULL;
- r = unquote_first_word(&p, &word, true);
+ r = unquote_first_word(&p, &word, UNQUOTE_RELAX);
if (r < 0)
return r;
if (r == 0)
_cleanup_free_ char *word = NULL;
const char *e;
- r = unquote_first_word(&p, &word, true);
+ r = unquote_first_word(&p, &word, UNQUOTE_RELAX);
if (r < 0)
return r;
if (r == 0)
assert(machine);
assert(pid);
- p = strappenda("/run/systemd/machines/", machine);
+ p = strjoina("/run/systemd/machines/", machine);
r = parse_env_file(p, NEWLINE, "LEADER", &s, "CLASS", &class, NULL);
if (r == -ENOENT)
return -EHOSTDOWN;
return 0;
}
-bool pid_is_unwaited(pid_t pid) {
- /* Checks whether a PID is still valid at all, including a zombie */
-
- if (pid <= 0)
- return false;
-
- if (kill(pid, 0) >= 0)
- return true;
-
- return errno != ESRCH;
-}
-
-bool pid_is_alive(pid_t pid) {
- int r;
-
- /* Checks whether a PID is still valid and not a zombie */
-
- if (pid <= 0)
- return false;
-
- r = get_process_state(pid);
- if (r == -ENOENT || r == 'Z')
- return false;
-
- return true;
-}
-
int getpeercred(int fd, struct ucred *ucred) {
socklen_t n = sizeof(struct ucred);
struct ucred u;
if (isempty(s)) {
free(s);
- return -ENOTSUP;
+ return -EOPNOTSUPP;
}
*ret = s;
#endif
/* Fall back to unguessable name + unlinking */
- p = strappenda(path, "/systemd-tmp-XXXXXX");
+ p = strjoina(path, "/systemd-tmp-XXXXXX");
fd = mkostemp_safe(p, flags);
if (fd < 0)
return (uint64_t) mem * (uint64_t) page_size();
}
-char* mount_test_option(const char *haystack, const char *needle) {
-
- struct mntent me = {
- .mnt_opts = (char*) haystack
- };
-
- assert(needle);
-
- /* Like glibc's hasmntopt(), but works on a string, not a
- * struct mntent */
-
- if (!haystack)
- return NULL;
-
- return hasmntopt(&me, needle);
-}
-
void hexdump(FILE *f, const void *p, size_t s) {
const uint8_t *b = p;
unsigned n = 0;
continue;
}
- p = cunescape(path);
- if (!p)
- return -ENOMEM;
+ r = cunescape(path, UNESCAPE_RELAX, &p);
+ if (r < 0)
+ return r;
if (!path_startswith(p, prefix))
continue;
return r ? r : n;
}
+static int get_mount_flags(const char *path, unsigned long *flags) {
+ struct statvfs buf;
+
+ if (statvfs(path, &buf) < 0)
+ return -errno;
+ *flags = buf.f_flag;
+ return 0;
+}
+
int bind_remount_recursive(const char *prefix, bool ro) {
_cleanup_set_free_free_ Set *done = NULL;
_cleanup_free_ char *cleaned = NULL;
_cleanup_set_free_free_ Set *todo = NULL;
bool top_autofs = false;
char *x;
+ unsigned long orig_flags;
todo = set_new(&string_hash_ops);
if (!todo)
continue;
}
- p = cunescape(path);
- if (!p)
- return -ENOMEM;
+ r = cunescape(path, UNESCAPE_RELAX, &p);
+ if (r < 0)
+ return r;
/* Let's ignore autofs mounts. If they aren't
* triggered yet, we want to avoid triggering
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)
+ orig_flags = 0;
+ (void) get_mount_flags(cleaned, &orig_flags);
+ orig_flags &= ~MS_RDONLY;
+
+ if (mount(NULL, prefix, NULL, orig_flags|MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0)
return -errno;
x = strdup(cleaned);
if (r < 0)
return r;
- if (mount(NULL, x, NULL, MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0) {
+ /* Try to reuse the original flag set, but
+ * don't care for errors, in case of
+ * obstructed mounts */
+ orig_flags = 0;
+ (void) get_mount_flags(x, &orig_flags);
+ orig_flags &= ~MS_RDONLY;
+
+ if (mount(NULL, x, NULL, orig_flags|MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0) {
/* Deal with mount points that are
* obstructed by a later mount */
* awfully racy, and thus we just won't do them. */
if (root)
- path = strappenda(root, "/etc/.pwd.lock");
+ path = strjoina(root, "/etc/.pwd.lock");
else
path = "/etc/.pwd.lock";
return !!S_ISDIR(st.st_mode);
}
-int unquote_first_word(const char **p, char **ret, bool relax) {
+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,
case VALUE_ESCAPE:
if (c == 0) {
- if (relax)
+ if (flags & UNQUOTE_RELAX)
goto finish;
return -EINVAL;
}
- if (!GREEDY_REALLOC(s, allocated, sz+2))
+ if (!GREEDY_REALLOC(s, allocated, sz+7))
return -ENOMEM;
- s[sz++] = c;
- state = VALUE;
+ if (flags & UNQUOTE_CUNESCAPE) {
+ uint32_t u;
+ r = cunescape_one(*p, (size_t) -1, &c, &u);
+ if (r < 0)
+ 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;
+
+ state = VALUE;
break;
case SINGLE_QUOTE:
if (c == 0) {
- if (relax)
+ if (flags & UNQUOTE_RELAX)
goto finish;
return -EINVAL;
} else if (c == '\'')
case SINGLE_QUOTE_ESCAPE:
if (c == 0) {
- if (relax)
+ if (flags & UNQUOTE_RELAX)
goto finish;
return -EINVAL;
}
- if (!GREEDY_REALLOC(s, allocated, sz+2))
+ if (!GREEDY_REALLOC(s, allocated, sz+7))
return -ENOMEM;
- s[sz++] = c;
+ if (flags & UNQUOTE_CUNESCAPE) {
+ uint32_t u;
+
+ r = cunescape_one(*p, (size_t) -1, &c, &u);
+ if (r < 0)
+ return -EINVAL;
+
+ (*p) += r - 1;
+
+ if (c != 0)
+ s[sz++] = c;
+ else
+ sz += utf8_encode_unichar(s + sz, u);
+ } else
+ s[sz++] = c;
+
state = SINGLE_QUOTE;
break;
case DOUBLE_QUOTE_ESCAPE:
if (c == 0) {
- if (relax)
+ if (flags & UNQUOTE_RELAX)
goto finish;
return -EINVAL;
}
- if (!GREEDY_REALLOC(s, allocated, sz+2))
+ if (!GREEDY_REALLOC(s, allocated, sz+7))
return -ENOMEM;
- s[sz++] = c;
+ if (flags & UNQUOTE_CUNESCAPE) {
+ uint32_t u;
+
+ r = cunescape_one(*p, (size_t) -1, &c, &u);
+ if (r < 0)
+ return -EINVAL;
+
+ (*p) += r - 1;
+
+ if (c != 0)
+ s[sz++] = c;
+ else
+ sz += utf8_encode_unichar(s + sz, u);
+ } else
+ s[sz++] = c;
+
state = DOUBLE_QUOTE;
break;
return 1;
}
-int unquote_many_words(const char **p, ...) {
+int unquote_many_words(const char **p, UnquoteFlags flags, ...) {
va_list ap;
char **l;
int n = 0, i, c, r;
assert(p);
/* Count how many words are expected */
- va_start(ap, p);
+ va_start(ap, flags);
for (;;) {
if (!va_arg(ap, char **))
break;
l = newa0(char*, n);
for (c = 0; c < n; c++) {
- r = unquote_first_word(p, &l[c], false);
+ r = unquote_first_word(p, &l[c], flags);
if (r < 0) {
int j;
/* If we managed to parse all words, return them in the passed
* in parameters */
- va_start(ap, p);
+ va_start(ap, flags);
for (i = 0; i < n; i++) {
char **v;
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 chattr_fd(int fd, unsigned value, unsigned mask) {
+ unsigned old_attr, new_attr;
+
+ assert(fd >= 0);
+
+ 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) {
+ assert(fd >= 0);
+
+ 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);
+}
+
+int make_lock_file(const char *p, int operation, LockFile *ret) {
+ _cleanup_close_ int fd = -1;
+ _cleanup_free_ char *t = NULL;
+ int r;
+
+ /*
+ * We use UNPOSIX locks if they are available. They have nice
+ * semantics, and are mostly compatible with NFS. However,
+ * they are only available on new kernels. When we detect we
+ * are running on an older kernel, then we fall back to good
+ * old BSD locks. They also have nice semantics, but are
+ * slightly problematic on NFS, where they are upgraded to
+ * POSIX locks, even though locally they are orthogonal to
+ * POSIX locks.
+ */
+
+ t = strdup(p);
+ if (!t)
+ return -ENOMEM;
+
+ for (;;) {
+ struct flock fl = {
+ .l_type = (operation & ~LOCK_NB) == LOCK_EX ? F_WRLCK : F_RDLCK,
+ .l_whence = SEEK_SET,
+ };
+ struct stat st;
+
+ fd = open(p, O_CREAT|O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NOCTTY, 0600);
+ if (fd < 0)
+ return -errno;
+
+ r = fcntl(fd, (operation & LOCK_NB) ? F_OFD_SETLK : F_OFD_SETLKW, &fl);
+ if (r < 0) {
+
+ /* If the kernel is too old, use good old BSD locks */
+ if (errno == EINVAL)
+ r = flock(fd, operation);
+
+ if (r < 0)
+ return errno == EAGAIN ? -EBUSY : -errno;
+ }
+
+ /* If we acquired the lock, let's check if the file
+ * still exists in the file system. If not, then the
+ * previous exclusive owner removed it and then closed
+ * it. In such a case our acquired lock is worthless,
+ * hence try again. */
+
+ r = fstat(fd, &st);
+ if (r < 0)
+ return -errno;
+ if (st.st_nlink > 0)
+ break;
+
+ fd = safe_close(fd);
+ }
+
+ ret->path = t;
+ ret->fd = fd;
+ ret->operation = operation;
+
+ fd = -1;
+ t = NULL;
+
+ return r;
+}
+
+int make_lock_file_for(const char *p, int operation, LockFile *ret) {
+ const char *fn;
+ char *t;
+
+ assert(p);
+ assert(ret);
+
+ fn = basename(p);
+ if (!filename_is_valid(fn))
+ return -EINVAL;
+
+ t = newa(char, strlen(p) + 2 + 4 + 1);
+ stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), fn), ".lck");
+
+ return make_lock_file(t, operation, ret);
+}
+
+void release_lock_file(LockFile *f) {
+ int r;
+
+ if (!f)
+ return;
+
+ if (f->path) {
+
+ /* If we are the exclusive owner we can safely delete
+ * the lock file itself. If we are not the exclusive
+ * owner, we can try becoming it. */
+
+ if (f->fd >= 0 &&
+ (f->operation & ~LOCK_NB) == LOCK_SH) {
+ static const struct flock fl = {
+ .l_type = F_WRLCK,
+ .l_whence = SEEK_SET,
+ };
+
+ r = fcntl(f->fd, F_OFD_SETLK, &fl);
+ if (r < 0 && errno == EINVAL)
+ r = flock(f->fd, LOCK_EX|LOCK_NB);
+
+ if (r >= 0)
+ f->operation = LOCK_EX|LOCK_NB;
+ }
+
+ if ((f->operation & ~LOCK_NB) == LOCK_EX)
+ unlink_noerrno(f->path);
+
+ free(f->path);
+ f->path = NULL;
+ }
+
+ f->fd = safe_close(f->fd);
+ f->operation = 0;
+}
+
+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);
+
+ for (cmsg = CMSG_FIRSTHDR(mh); cmsg; cmsg = CMSG_NXTHDR(mh, cmsg))
+ 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;
+}