chiark / gitweb /
systemd-run: make sure --nice=, --uid=, --gid=, --setenv= also work in --scope mode
[elogind.git] / src / shared / fileio.c
index ad068bf30da6c4687b80393a6c387ca388ee74ea..f10126954b2d4d2b6a53c52cb0eb65e77f385a5c 100644 (file)
 ***/
 
 #include <unistd.h>
+#include <sys/sendfile.h>
 #include "fileio.h"
 #include "util.h"
 #include "strv.h"
-
+#include "utf8.h"
+#include "ctype.h"
 
 int write_string_to_file(FILE *f, const char *line) {
         errno = 0;
@@ -116,6 +118,77 @@ int read_one_line_file(const char *fn, char **line) {
         return 0;
 }
 
+ssize_t sendfile_full(int out_fd, const char *fn) {
+        _cleanup_fclose_ FILE *f;
+        struct stat st;
+        int r;
+        ssize_t s;
+
+        size_t n, l;
+        _cleanup_free_ char *buf = NULL;
+
+        assert(out_fd > 0);
+        assert(fn);
+
+        f = fopen(fn, "re");
+        if (!f)
+                return -errno;
+
+        r = fstat(fileno(f), &st);
+        if (r < 0)
+                return -errno;
+
+        s = sendfile(out_fd, fileno(f), NULL, st.st_size);
+        if (s < 0)
+                if (errno == EINVAL || errno == ENOSYS) {
+                        /* continue below */
+                } else
+                        return -errno;
+        else
+                return s;
+
+        /* sendfile() failed, fall back to read/write */
+
+        /* Safety check */
+        if (st.st_size > 4*1024*1024)
+                return -E2BIG;
+
+        n = st.st_size > 0 ? st.st_size : LINE_MAX;
+        l = 0;
+
+        while (true) {
+                char *t;
+                size_t k;
+
+                t = realloc(buf, n);
+                if (!t)
+                        return -ENOMEM;
+
+                buf = t;
+                k = fread(buf + l, 1, n - l, f);
+
+                if (k <= 0) {
+                        if (ferror(f))
+                                return -errno;
+
+                        break;
+                }
+
+                l += k;
+                n *= 2;
+
+                /* Safety check */
+                if (n > 4*1024*1024)
+                        return -E2BIG;
+        }
+
+        r = write(out_fd, buf, l);
+        if (r < 0)
+                return -errno;
+
+        return (ssize_t) l;
+}
+
 int read_full_file(const char *fn, char **contents, size_t *size) {
         _cleanup_fclose_ FILE *f = NULL;
         size_t n, l;
@@ -167,7 +240,7 @@ int read_full_file(const char *fn, char **contents, size_t *size) {
 
         buf[l] = 0;
         *contents = buf;
-        buf = NULL;
+        buf = NULL; /* do not free */
 
         if (size)
                 *size = l;
@@ -178,13 +251,15 @@ int read_full_file(const char *fn, char **contents, size_t *size) {
 static int parse_env_file_internal(
                 const char *fname,
                 const char *newline,
-                int (*push) (const char *key, char *value, void *userdata),
+                int (*push) (const char *filename, unsigned line,
+                             const char *key, char *value, void *userdata),
                 void *userdata) {
 
         _cleanup_free_ char *contents = NULL, *key = NULL;
         size_t key_alloc = 0, n_key = 0, value_alloc = 0, n_value = 0, last_value_whitespace = (size_t) -1, last_key_whitespace = (size_t) -1;
         char *p, *value = NULL;
         int r;
+        unsigned line = 1;
 
         enum {
                 PRE_KEY,
@@ -231,6 +306,7 @@ static int parse_env_file_internal(
                 case KEY:
                         if (strchr(newline, c)) {
                                 state = PRE_KEY;
+                                line ++;
                                 n_key = 0;
                         } else if (c == '=') {
                                 state = PRE_VALUE;
@@ -254,6 +330,7 @@ static int parse_env_file_internal(
                 case PRE_VALUE:
                         if (strchr(newline, c)) {
                                 state = PRE_KEY;
+                                line ++;
                                 key[n_key] = 0;
 
                                 if (value)
@@ -263,7 +340,7 @@ static int parse_env_file_internal(
                                 if (last_key_whitespace != (size_t) -1)
                                         key[last_key_whitespace] = 0;
 
-                                r = push(key, value, userdata);
+                                r = push(fname, line, key, value, userdata);
                                 if (r < 0)
                                         goto fail;
 
@@ -293,6 +370,7 @@ static int parse_env_file_internal(
                 case VALUE:
                         if (strchr(newline, c)) {
                                 state = PRE_KEY;
+                                line ++;
 
                                 key[n_key] = 0;
 
@@ -307,7 +385,7 @@ static int parse_env_file_internal(
                                 if (last_key_whitespace != (size_t) -1)
                                         key[last_key_whitespace] = 0;
 
-                                r = push(key, value, userdata);
+                                r = push(fname, line, key, value, userdata);
                                 if (r < 0)
                                         goto fail;
 
@@ -409,8 +487,10 @@ static int parse_env_file_internal(
                 case COMMENT:
                         if (c == '\\')
                                 state = COMMENT_ESCAPE;
-                        else if (strchr(newline, c))
+                        else if (strchr(newline, c)) {
                                 state = PRE_KEY;
+                                line ++;
+                        }
                         break;
 
                 case COMMENT_ESCAPE:
@@ -440,7 +520,7 @@ static int parse_env_file_internal(
                 if (last_key_whitespace != (size_t) -1)
                         key[last_key_whitespace] = 0;
 
-                r = push(key, value, userdata);
+                r = push(fname, line, key, value, userdata);
                 if (r < 0)
                         goto fail;
         }
@@ -452,10 +532,27 @@ fail:
         return r;
 }
 
-static int parse_env_file_push(const char *key, char *value, void *userdata) {
+static int parse_env_file_push(const char *filename, unsigned line,
+                               const char *key, char *value, void *userdata) {
+
         const char *k;
-        va_list* ap = (va_list*) userdata;
-        va_list aq;
+        va_list aq, *ap = userdata;
+
+        if (!utf8_is_valid(key)) {
+                _cleanup_free_ char *p = utf8_escape_invalid(key);
+
+                log_error("%s:%u: invalid UTF-8 in key '%s', ignoring.",
+                          filename, line, p);
+                return -EINVAL;
+        }
+
+        if (value && !utf8_is_valid(value)) {
+                _cleanup_free_ char *p = utf8_escape_invalid(value);
+
+                log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.",
+                          filename, line, key, p);
+                return -EINVAL;
+        }
 
         va_copy(aq, *ap);
 
@@ -473,7 +570,6 @@ static int parse_env_file_push(const char *key, char *value, void *userdata) {
         }
 
         va_end(aq);
-
         free(value);
         return 0;
 }
@@ -495,20 +591,35 @@ int parse_env_file(
         return r;
 }
 
-static int load_env_file_push(const char *key, char *value, void *userdata) {
+static int load_env_file_push(const char *filename, unsigned line,
+                              const char *key, char *value, void *userdata) {
         char ***m = userdata;
         char *p;
         int r;
 
+        if (!utf8_is_valid(key)) {
+                _cleanup_free_ char *t = utf8_escape_invalid(key);
+
+                log_error("%s:%u: invalid UTF-8 for key '%s', ignoring.",
+                          filename, line, t);
+                return -EINVAL;
+        }
+
+        if (value && !utf8_is_valid(value)) {
+                _cleanup_free_ char *t = utf8_escape_invalid(value);
+
+                log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.",
+                          filename, line, key, t);
+                return -EINVAL;
+        }
+
         p = strjoin(key, "=", strempty(value), NULL);
         if (!p)
                 return -ENOMEM;
 
-        r = strv_push(m, p);
-        if (r < 0) {
-                free(p);
+        r = strv_consume(m, p);
+        if (r < 0)
                 return r;
-        }
 
         free(value);
         return 0;
@@ -594,3 +705,80 @@ int write_env_file(const char *fname, char **l) {
 
         return r;
 }
+
+int executable_is_script(const char *path, char **interpreter) {
+        int r;
+        char _cleanup_free_ *line = NULL;
+        int len;
+        char *ans;
+
+        assert(path);
+
+        r = read_one_line_file(path, &line);
+        if (r < 0)
+                return r;
+
+        if (!startswith(line, "#!"))
+                return 0;
+
+        ans = strstrip(line + 2);
+        len = strcspn(ans, " \t");
+
+        if (len == 0)
+                return 0;
+
+        ans = strndup(ans, len);
+        if (!ans)
+                return -ENOMEM;
+
+        *interpreter = ans;
+        return 1;
+}
+
+/**
+ * Retrieve one field from a file like /proc/self/status.  pattern
+ * should start with '\n' and end with a ':'. Whitespace and zeros
+ * after the ':' will be skipped. field must be freed afterwards.
+ */
+int get_status_field(const char *filename, const char *pattern, char **field) {
+        _cleanup_free_ char *status = NULL;
+        char *t;
+        size_t len;
+        int r;
+
+        assert(filename);
+        assert(pattern);
+        assert(field);
+
+        r = read_full_file(filename, &status, NULL);
+        if (r < 0)
+                return r;
+
+        t = strstr(status, pattern);
+        if (!t)
+                return -ENOENT;
+
+        t += strlen(pattern);
+        if (*t) {
+                t += strspn(t, " \t");
+
+                /* Also skip zeros, because when this is used for
+                 * capabilities, we don't want the zeros. This way the
+                 * same capability set always maps to the same string,
+                 * irrespective of the total capability set size. For
+                 * other numbers it shouldn't matter. */
+                t += strspn(t, "0");
+                /* Back off one char if there's nothing but whitespace
+                   and zeros */
+                if (!*t || isspace(*t))
+                        t --;
+        }
+
+        len = strcspn(t, WHITESPACE);
+
+        *field = strndup(t, len);
+        if (!*field)
+                return -ENOMEM;
+
+        return 0;
+}