chiark / gitweb /
util: don't consider tabs special in string_has_cc() anymore
[elogind.git] / src / shared / fileio.c
index 337b9e41476d2c418c14453d2f3a9f578ef072d6..b1de5908c538c32e792c093c4daeb34f58812852 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_file(const char *fn, const char *line) {
-        _cleanup_fclose_ FILE *f = NULL;
-
-        assert(fn);
+int write_string_stream(FILE *f, const char *line) {
+        assert(f);
         assert(line);
 
-        f = fopen(fn, "we");
-        if (!f)
-                return -errno;
-
         errno = 0;
+
         fputs(line, f);
         if (!endswith(line, "\n"))
                 fputc('\n', f);
@@ -47,6 +45,19 @@ int write_string_file(const char *fn, const char *line) {
         return 0;
 }
 
+int write_string_file(const char *fn, const char *line) {
+        _cleanup_fclose_ FILE *f = NULL;
+
+        assert(fn);
+        assert(line);
+
+        f = fopen(fn, "we");
+        if (!f)
+                return -errno;
+
+        return write_string_stream(f, line);
+}
+
 int write_string_file_atomic(const char *fn, const char *line) {
         _cleanup_fclose_ FILE *f = NULL;
         _cleanup_free_ char *p = NULL;
@@ -111,22 +122,37 @@ int read_one_line_file(const char *fn, char **line) {
         return 0;
 }
 
-int read_full_file(const char *fn, char **contents, size_t *size) {
-        _cleanup_fclose_ FILE *f = NULL;
+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;
-        struct stat st;
 
+        assert(out_fd > 0);
         assert(fn);
-        assert(contents);
 
         f = fopen(fn, "re");
         if (!f)
                 return -errno;
 
-        if (fstat(fileno(f), &st) < 0)
+        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;
@@ -134,6 +160,66 @@ int read_full_file(const char *fn, char **contents, size_t *size) {
         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_stream(FILE *f, char **contents, size_t *size) {
+        size_t n, l;
+        _cleanup_free_ char *buf = NULL;
+        struct stat st;
+
+        assert(f);
+        assert(contents);
+
+        if (fstat(fileno(f), &st) < 0)
+                return -errno;
+
+        n = LINE_MAX;
+
+        if (S_ISREG(st.st_mode)) {
+
+                /* Safety check */
+                if (st.st_size > 4*1024*1024)
+                        return -E2BIG;
+
+                /* Start with the right file size, but be prepared for
+                 * files from /proc which generally report a file size
+                 * of 0 */
+                if (st.st_size > 0)
+                        n = st.st_size;
+        }
+
+        l = 0;
         for (;;) {
                 char *t;
                 size_t k;
@@ -162,7 +248,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;
@@ -170,16 +256,32 @@ int read_full_file(const char *fn, char **contents, size_t *size) {
         return 0;
 }
 
+int read_full_file(const char *fn, char **contents, size_t *size) {
+        _cleanup_fclose_ FILE *f = NULL;
+
+        assert(fn);
+        assert(contents);
+
+        f = fopen(fn, "re");
+        if (!f)
+                return -errno;
+
+        return read_full_stream(f, contents, size);
+}
+
 static int parse_env_file_internal(
+                FILE *f,
                 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_whitespace = (size_t) -1;
+        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,
@@ -195,10 +297,12 @@ static int parse_env_file_internal(
                 COMMENT_ESCAPE
         } state = PRE_KEY;
 
-        assert(fname);
         assert(newline);
 
-        r = read_full_file(fname, &contents, NULL);
+        if (f)
+                r = read_full_stream(f, &contents, NULL);
+        else
+                r = read_full_file(fname, &contents, NULL);
         if (r < 0)
                 return r;
 
@@ -212,7 +316,9 @@ static int parse_env_file_internal(
                                 state = COMMENT;
                         else if (!strchr(WHITESPACE, c)) {
                                 state = KEY;
-                                if (!greedy_realloc((void**) &key, &key_alloc, n_key+2)) {
+                                last_key_whitespace = (size_t) -1;
+
+                                if (!GREEDY_REALLOC(key, key_alloc, n_key+2)) {
                                         r = -ENOMEM;
                                         goto fail;
                                 }
@@ -224,11 +330,18 @@ static int parse_env_file_internal(
                 case KEY:
                         if (strchr(newline, c)) {
                                 state = PRE_KEY;
+                                line ++;
                                 n_key = 0;
-                        } else if (c == '=')
+                        } else if (c == '=') {
                                 state = PRE_VALUE;
-                        else {
-                                if (!greedy_realloc((void**) &key, &key_alloc, n_key+2)) {
+                                last_value_whitespace = (size_t) -1;
+                        } else {
+                                if (!strchr(WHITESPACE, c))
+                                        last_key_whitespace = (size_t) -1;
+                                else if (last_key_whitespace == (size_t) -1)
+                                         last_key_whitespace = n_key;
+
+                                if (!GREEDY_REALLOC(key, key_alloc, n_key+2)) {
                                         r = -ENOMEM;
                                         goto fail;
                                 }
@@ -241,22 +354,24 @@ static int parse_env_file_internal(
                 case PRE_VALUE:
                         if (strchr(newline, c)) {
                                 state = PRE_KEY;
+                                line ++;
                                 key[n_key] = 0;
 
                                 if (value)
                                         value[n_value] = 0;
 
                                 /* strip trailing whitespace from key */
-                                while(n_key && strchr(WHITESPACE, key[--n_key]))
-                                        key[n_key]=0;
+                                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;
 
                                 n_key = 0;
                                 value = NULL;
                                 value_alloc = n_value = 0;
+
                         } else if (c == '\'')
                                 state = SINGLE_QUOTE_VALUE;
                         else if (c == '\"')
@@ -266,7 +381,7 @@ static int parse_env_file_internal(
                         else if (!strchr(WHITESPACE, c)) {
                                 state = VALUE;
 
-                                if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
+                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
                                         r = -ENOMEM;
                                         goto fail;
                                 }
@@ -279,37 +394,39 @@ static int parse_env_file_internal(
                 case VALUE:
                         if (strchr(newline, c)) {
                                 state = PRE_KEY;
+                                line ++;
 
                                 key[n_key] = 0;
 
                                 if (value)
                                         value[n_value] = 0;
 
-                                /* Chomp off trailing whitespace */
-                                if (last_whitespace != (size_t) -1)
-                                        value[last_whitespace] = 0;
+                                /* Chomp off trailing whitespace from value */
+                                if (last_value_whitespace != (size_t) -1)
+                                        value[last_value_whitespace] = 0;
 
                                 /* strip trailing whitespace from key */
-                                while(n_key && strchr(WHITESPACE, key[--n_key]))
-                                        key[n_key]=0;
+                                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;
 
                                 n_key = 0;
                                 value = NULL;
                                 value_alloc = n_value = 0;
+
                         } else if (c == '\\') {
                                 state = VALUE_ESCAPE;
-                                last_whitespace = (size_t) -1;
+                                last_value_whitespace = (size_t) -1;
                         } else {
                                 if (!strchr(WHITESPACE, c))
-                                        last_whitespace = (size_t) -1;
-                                else if (last_whitespace == (size_t) -1)
-                                        last_whitespace = n_value;
+                                        last_value_whitespace = (size_t) -1;
+                                else if (last_value_whitespace == (size_t) -1)
+                                        last_value_whitespace = n_value;
 
-                                if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
+                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
                                         r = -ENOMEM;
                                         goto fail;
                                 }
@@ -324,7 +441,7 @@ static int parse_env_file_internal(
 
                         if (!strchr(newline, c)) {
                                 /* Escaped newlines we eat up entirely */
-                                if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
+                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
                                         r = -ENOMEM;
                                         goto fail;
                                 }
@@ -339,7 +456,7 @@ static int parse_env_file_internal(
                         else if (c == '\\')
                                 state = SINGLE_QUOTE_VALUE_ESCAPE;
                         else {
-                                if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
+                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
                                         r = -ENOMEM;
                                         goto fail;
                                 }
@@ -353,7 +470,7 @@ static int parse_env_file_internal(
                         state = SINGLE_QUOTE_VALUE;
 
                         if (!strchr(newline, c)) {
-                                if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
+                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
                                         r = -ENOMEM;
                                         goto fail;
                                 }
@@ -368,7 +485,7 @@ static int parse_env_file_internal(
                         else if (c == '\\')
                                 state = DOUBLE_QUOTE_VALUE_ESCAPE;
                         else {
-                                if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
+                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
                                         r = -ENOMEM;
                                         goto fail;
                                 }
@@ -382,7 +499,7 @@ static int parse_env_file_internal(
                         state = DOUBLE_QUOTE_VALUE;
 
                         if (!strchr(newline, c)) {
-                                if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
+                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
                                         r = -ENOMEM;
                                         goto fail;
                                 }
@@ -394,8 +511,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:
@@ -417,11 +536,15 @@ static int parse_env_file_internal(
                 if (value)
                         value[n_value] = 0;
 
+                if (state == VALUE)
+                        if (last_value_whitespace != (size_t) -1)
+                                value[last_value_whitespace] = 0;
+
                 /* strip trailing whitespace from key */
-                while(n_key && strchr(WHITESPACE, key[--n_key]))
-                        key[n_key]=0;
+                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;
         }
@@ -433,10 +556,29 @@ 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;
+
+                p = utf8_escape_invalid(key);
+                log_error("%s:%u: invalid UTF-8 in key '%s', ignoring.", strna(filename), line, p);
+                return -EINVAL;
+        }
+
+        if (value && !utf8_is_valid(value)) {
+                _cleanup_free_ char *p;
+
+                p = utf8_escape_invalid(value);
+                log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.", strna(filename), line, key, p);
+                return -EINVAL;
+        }
 
         va_copy(aq, *ap);
 
@@ -454,8 +596,8 @@ static int parse_env_file_push(const char *key, char *value, void *userdata) {
         }
 
         va_end(aq);
-
         free(value);
+
         return 0;
 }
 
@@ -470,39 +612,109 @@ int parse_env_file(
                 newline = NEWLINE;
 
         va_start(ap, newline);
-        r = parse_env_file_internal(fname, newline, parse_env_file_push, &ap);
+        r = parse_env_file_internal(NULL, fname, newline, parse_env_file_push, &ap);
         va_end(ap);
 
         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.", strna(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.", strna(filename), line, key, t);
+                return -EINVAL;
+        }
+
         p = strjoin(key, "=", strempty(value), NULL);
         if (!p)
                 return -ENOMEM;
 
-        r = strv_push(m, p);
+        r = strv_consume(m, p);
+        if (r < 0)
+                return r;
+
+        free(value);
+        return 0;
+}
+
+int load_env_file(FILE *f, const char *fname, const char *newline, char ***rl) {
+        char **m = NULL;
+        int r;
+
+        if (!newline)
+                newline = NEWLINE;
+
+        r = parse_env_file_internal(f, fname, newline, load_env_file_push, &m);
         if (r < 0) {
-                free(p);
+                strv_free(m);
                 return r;
         }
 
-        free(value);
+        *rl = m;
+        return 0;
+}
+
+static int load_env_file_push_pairs(
+                const char *filename, unsigned line,
+                const char *key, char *value,
+                void *userdata) {
+        char ***m = userdata;
+        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.", strna(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.", strna(filename), line, key, t);
+                return -EINVAL;
+        }
+
+        r = strv_extend(m, key);
+        if (r < 0)
+                return -ENOMEM;
+
+        if (!value) {
+                r = strv_extend(m, "");
+                if (r < 0)
+                        return -ENOMEM;
+        } else {
+                r = strv_push(m, value);
+                if (r < 0)
+                        return r;
+        }
+
         return 0;
 }
 
-int load_env_file(const char *fname, const char *newline, char ***rl) {
+int load_env_file_pairs(FILE *f, const char *fname, const char *newline, char ***rl) {
         char **m = NULL;
         int r;
 
         if (!newline)
                 newline = NEWLINE;
 
-        r = parse_env_file_internal(fname, newline, load_env_file_push, &m);
+        r = parse_env_file_internal(f, fname, newline, load_env_file_push_pairs, &m);
         if (r < 0) {
                 strv_free(m);
                 return r;
@@ -526,7 +738,7 @@ static void write_env_var(FILE *f, const char *v) {
         p++;
         fwrite(v, 1, p-v, f);
 
-        if (string_has_cc(p) || chars_intersect(p, WHITESPACE "\'\"\\`$")) {
+        if (string_has_cc(p, NULL) || chars_intersect(p, WHITESPACE "\'\"\\`$")) {
                 fputc('\"', f);
 
                 for (; *p; p++) {
@@ -575,3 +787,80 @@ int write_env_file(const char *fname, char **l) {
 
         return r;
 }
+
+int executable_is_script(const char *path, char **interpreter) {
+        int r;
+        _cleanup_free_ char *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;
+}