chiark / gitweb /
Add more tests and fix capability logging
[elogind.git] / src / shared / fileio.c
index 5b8be5ce2d38ed0181c2c8834512ab4c499941de..603a1c7b38755ad35fec95f48915367341776361 100644 (file)
 #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);
-        assert(line);
-
-        f = fopen(fn, "we");
-        if (!f)
-                return -errno;
-
+int write_string_to_file(FILE *f, const char *line) {
         errno = 0;
-        if (fputs(line, f) < 0)
-                return errno ? -errno : -EIO;
-
+        fputs(line, f);
         if (!endswith(line, "\n"))
                 fputc('\n', f);
 
@@ -49,6 +40,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_to_file(f, line);
+}
+
 int write_string_file_atomic(const char *fn, const char *line) {
         _cleanup_fclose_ FILE *f = NULL;
         _cleanup_free_ char *p = NULL;
@@ -64,11 +68,7 @@ int write_string_file_atomic(const char *fn, const char *line) {
         fchmod_umask(fileno(f), 0644);
 
         errno = 0;
-        if (fputs(line, f) < 0) {
-                r = -errno;
-                goto finish;
-        }
-
+        fputs(line, f);
         if (!endswith(line, "\n"))
                 fputc('\n', f);
 
@@ -83,7 +83,6 @@ int write_string_file_atomic(const char *fn, const char *line) {
                         r = 0;
         }
 
-finish:
         if (r < 0)
                 unlink(p);
 
@@ -177,176 +176,422 @@ int read_full_file(const char *fn, char **contents, size_t *size) {
         return 0;
 }
 
-int parse_env_file(
+static int parse_env_file_internal(
                 const char *fname,
-                const char *separator, ...) {
-
-        int r = 0;
-        char *contents = NULL, *p;
+                const char *newline,
+                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,
+                KEY,
+                PRE_VALUE,
+                VALUE,
+                VALUE_ESCAPE,
+                SINGLE_QUOTE_VALUE,
+                SINGLE_QUOTE_VALUE_ESCAPE,
+                DOUBLE_QUOTE_VALUE,
+                DOUBLE_QUOTE_VALUE_ESCAPE,
+                COMMENT,
+                COMMENT_ESCAPE
+        } state = PRE_KEY;
 
         assert(fname);
-        assert(separator);
+        assert(newline);
 
         r = read_full_file(fname, &contents, NULL);
         if (r < 0)
                 return r;
 
-        p = contents;
-        for (;;) {
-                const char *key = NULL;
+        for (p = contents; *p; p++) {
+                char c = *p;
+
+                switch (state) {
+
+                case PRE_KEY:
+                        if (strchr(COMMENTS, c))
+                                state = COMMENT;
+                        else if (!strchr(WHITESPACE, c)) {
+                                state = KEY;
+                                last_key_whitespace = (size_t) -1;
+
+                                if (!greedy_realloc((void**) &key, &key_alloc, n_key+2)) {
+                                        r = -ENOMEM;
+                                        goto fail;
+                                }
+
+                                key[n_key++] = c;
+                        }
+                        break;
+
+                case KEY:
+                        if (strchr(newline, c)) {
+                                state = PRE_KEY;
+                                line ++;
+                                n_key = 0;
+                        } else if (c == '=') {
+                                state = PRE_VALUE;
+                                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((void**) &key, &key_alloc, n_key+2)) {
+                                        r = -ENOMEM;
+                                        goto fail;
+                                }
 
-                p += strspn(p, separator);
-                p += strspn(p, WHITESPACE);
+                                key[n_key++] = c;
+                        }
 
-                if (!*p)
                         break;
 
-                if (!strchr(COMMENTS, *p)) {
-                        va_list ap;
-                        char **value;
+                case PRE_VALUE:
+                        if (strchr(newline, c)) {
+                                state = PRE_KEY;
+                                line ++;
+                                key[n_key] = 0;
 
-                        va_start(ap, separator);
-                        while ((key = va_arg(ap, char *))) {
-                                size_t n;
-                                char *v;
+                                if (value)
+                                        value[n_value] = 0;
 
-                                value = va_arg(ap, char **);
+                                /* strip trailing whitespace from key */
+                                if (last_key_whitespace != (size_t) -1)
+                                        key[last_key_whitespace] = 0;
 
-                                n = strlen(key);
-                                if (!strneq(p, key, n) ||
-                                    p[n] != '=')
-                                        continue;
+                                r = push(fname, line, key, value, userdata);
+                                if (r < 0)
+                                        goto fail;
 
-                                p += n + 1;
-                                n = strcspn(p, separator);
+                                n_key = 0;
+                                value = NULL;
+                                value_alloc = n_value = 0;
 
-                                if (n >= 2 &&
-                                    strchr(QUOTES, p[0]) &&
-                                    p[n-1] == p[0])
-                                        v = strndup(p+1, n-2);
-                                else
-                                        v = strndup(p, n);
+                        } else if (c == '\'')
+                                state = SINGLE_QUOTE_VALUE;
+                        else if (c == '\"')
+                                state = DOUBLE_QUOTE_VALUE;
+                        else if (c == '\\')
+                                state = VALUE_ESCAPE;
+                        else if (!strchr(WHITESPACE, c)) {
+                                state = VALUE;
 
-                                if (!v) {
+                                if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
                                         r = -ENOMEM;
-                                        va_end(ap);
                                         goto fail;
                                 }
 
-                                if (v[0] == '\0') {
-                                        /* return empty value strings as NULL */
-                                        free(v);
-                                        v = NULL;
+                                value[n_value++] = c;
+                        }
+
+                        break;
+
+                case VALUE:
+                        if (strchr(newline, c)) {
+                                state = PRE_KEY;
+                                line ++;
+
+                                key[n_key] = 0;
+
+                                if (value)
+                                        value[n_value] = 0;
+
+                                /* Chomp off trailing whitespace from value */
+                                if (last_value_whitespace != (size_t) -1)
+                                        value[last_value_whitespace] = 0;
+
+                                /* strip trailing whitespace from key */
+                                if (last_key_whitespace != (size_t) -1)
+                                        key[last_key_whitespace] = 0;
+
+                                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_value_whitespace = (size_t) -1;
+                        } else {
+                                if (!strchr(WHITESPACE, c))
+                                        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)) {
+                                        r = -ENOMEM;
+                                        goto fail;
+                                }
+
+                                value[n_value++] = c;
+                        }
+
+                        break;
+
+                case VALUE_ESCAPE:
+                        state = VALUE;
+
+                        if (!strchr(newline, c)) {
+                                /* Escaped newlines we eat up entirely */
+                                if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
+                                        r = -ENOMEM;
+                                        goto fail;
+                                }
+
+                                value[n_value++] = c;
+                        }
+                        break;
+
+                case SINGLE_QUOTE_VALUE:
+                        if (c == '\'')
+                                state = PRE_VALUE;
+                        else if (c == '\\')
+                                state = SINGLE_QUOTE_VALUE_ESCAPE;
+                        else {
+                                if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
+                                        r = -ENOMEM;
+                                        goto fail;
+                                }
+
+                                value[n_value++] = c;
+                        }
+
+                        break;
+
+                case SINGLE_QUOTE_VALUE_ESCAPE:
+                        state = SINGLE_QUOTE_VALUE;
+
+                        if (!strchr(newline, c)) {
+                                if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
+                                        r = -ENOMEM;
+                                        goto fail;
                                 }
 
-                                free(*value);
-                                *value = v;
+                                value[n_value++] = c;
+                        }
+                        break;
 
-                                p += n;
+                case DOUBLE_QUOTE_VALUE:
+                        if (c == '\"')
+                                state = PRE_VALUE;
+                        else if (c == '\\')
+                                state = DOUBLE_QUOTE_VALUE_ESCAPE;
+                        else {
+                                if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
+                                        r = -ENOMEM;
+                                        goto fail;
+                                }
 
-                                r ++;
-                                break;
+                                value[n_value++] = c;
                         }
-                        va_end(ap);
+
+                        break;
+
+                case DOUBLE_QUOTE_VALUE_ESCAPE:
+                        state = DOUBLE_QUOTE_VALUE;
+
+                        if (!strchr(newline, c)) {
+                                if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
+                                        r = -ENOMEM;
+                                        goto fail;
+                                }
+
+                                value[n_value++] = c;
+                        }
+                        break;
+
+                case COMMENT:
+                        if (c == '\\')
+                                state = COMMENT_ESCAPE;
+                        else if (strchr(newline, c)) {
+                                state = PRE_KEY;
+                                line ++;
+                        }
+                        break;
+
+                case COMMENT_ESCAPE:
+                        state = COMMENT;
+                        break;
                 }
+        }
+
+        if (state == PRE_VALUE ||
+            state == VALUE ||
+            state == VALUE_ESCAPE ||
+            state == SINGLE_QUOTE_VALUE ||
+            state == SINGLE_QUOTE_VALUE_ESCAPE ||
+            state == DOUBLE_QUOTE_VALUE ||
+            state == DOUBLE_QUOTE_VALUE_ESCAPE) {
+
+                key[n_key] = 0;
+
+                if (value)
+                        value[n_value] = 0;
 
-                if (!key)
-                        p += strcspn(p, separator);
+                if (state == VALUE)
+                        if (last_value_whitespace != (size_t) -1)
+                                value[last_value_whitespace] = 0;
+
+                /* strip trailing whitespace from key */
+                if (last_key_whitespace != (size_t) -1)
+                        key[last_key_whitespace] = 0;
+
+                r = push(fname, line, key, value, userdata);
+                if (r < 0)
+                        goto fail;
         }
 
+        return 0;
+
 fail:
-        free(contents);
+        free(value);
         return r;
 }
 
-int load_env_file(const char *fname, char ***rl) {
+static int parse_env_file_push(const char *filename, unsigned line,
+                               const char *key, char *value, void *userdata) {
+        assert(utf8_is_valid(key));
 
-        _cleanup_fclose_ FILE *f;
-        _cleanup_strv_free_ char **m = NULL;
-        _cleanup_free_ char *c = NULL;
+        if (value && !utf8_is_valid(value))
+                /* FIXME: filter UTF-8 */
+                log_error("%s:%u: invalid UTF-8 for key %s: '%s', ignoring.",
+                          filename, line, key, value);
+        else {
+                const char *k;
+                va_list* ap = (va_list*) userdata;
+                va_list aq;
 
-        assert(fname);
-        assert(rl);
+                va_copy(aq, *ap);
 
-        /* This reads an environment file, but will not complain about
-         * any invalid assignments, that needs to be done by the
-         * caller */
+                while ((k = va_arg(aq, const char *))) {
+                        char **v;
 
-        f = fopen(fname, "re");
-        if (!f)
-                return -errno;
+                        v = va_arg(aq, char **);
 
-        while (!feof(f)) {
-                char l[LINE_MAX], *p, *cs, *b;
+                        if (streq(key, k)) {
+                                va_end(aq);
+                                free(*v);
+                                *v = value;
+                                return 1;
+                        }
+                }
 
-                if (!fgets(l, sizeof(l), f)) {
-                        if (ferror(f))
-                                return -errno;
+                va_end(aq);
+        }
 
-                        /* The previous line was a continuation line?
-                         * Let's process it now, before we leave the
-                         * loop */
-                        if (c)
-                                goto process;
+        free(value);
+        return 0;
+}
 
-                        break;
-                }
+int parse_env_file(
+                const char *fname,
+                const char *newline, ...) {
 
-                /* Is this a continuation line? If so, just append
-                 * this to c, and go to next line right-away */
-                cs = endswith(l, "\\\n");
-                if (cs) {
-                        *cs = '\0';
-                        b = strappend(c, l);
-                        if (!b)
-                                return -ENOMEM;
-
-                        free(c);
-                        c = b;
-                        continue;
-                }
+        va_list ap;
+        int r;
 
-                /* If the previous line was a continuation line,
-                 * append the current line to it */
-                if (c) {
-                        b = strappend(c, l);
-                        if (!b)
-                                return -ENOMEM;
+        if (!newline)
+                newline = NEWLINE;
 
-                        free(c);
-                        c = b;
-                }
+        va_start(ap, newline);
+        r = parse_env_file_internal(fname, newline, parse_env_file_push, &ap);
+        va_end(ap);
+
+        return r;
+}
 
-        process:
-                p = strstrip(c ? c : l);
+static int load_env_file_push(const char *filename, unsigned line,
+                              const char *key, char *value, void *userdata) {
+        assert(utf8_is_valid(key));
 
-                if (*p && !strchr(COMMENTS, *p)) {
-                        _cleanup_free_ char *u;
-                        int k;
+        if (value && !utf8_is_valid(value))
+                /* FIXME: filter UTF-8 */
+                log_error("%s:%u: invalid UTF-8 for key %s: '%s', ignoring.",
+                          filename, line, key, value);
+        else {
+                char ***m = userdata;
+                char *p;
+                int r;
 
-                        u = normalize_env_assignment(p);
-                        if (!u)
-                                return -ENOMEM;
+                p = strjoin(key, "=", strempty(value), NULL);
+                if (!p)
+                        return -ENOMEM;
 
-                        k = strv_extend(&m, u);
-                        if (k < 0)
-                                return -ENOMEM;
+                r = strv_push(m, p);
+                if (r < 0) {
+                        free(p);
+                        return r;
                 }
+        }
+
+        free(value);
+        return 0;
+}
+
+int load_env_file(const char *fname, const char *newline, char ***rl) {
+        char **m = NULL;
+        int r;
 
-                free(c);
-                c = NULL;
+        if (!newline)
+                newline = NEWLINE;
+
+        r = parse_env_file_internal(fname, newline, load_env_file_push, &m);
+        if (r < 0) {
+                strv_free(m);
+                return r;
         }
 
         *rl = m;
-        m = NULL;
-
         return 0;
 }
 
+static void write_env_var(FILE *f, const char *v) {
+        const char *p;
+
+        p = strchr(v, '=');
+        if (!p) {
+                /* Fallback */
+                fputs(v, f);
+                fputc('\n', f);
+                return;
+        }
+
+        p++;
+        fwrite(v, 1, p-v, f);
+
+        if (string_has_cc(p) || chars_intersect(p, WHITESPACE "\'\"\\`$")) {
+                fputc('\"', f);
+
+                for (; *p; p++) {
+                        if (strchr("\'\"\\`$", *p))
+                                fputc('\\', f);
+
+                        fputc(*p, f);
+                }
+
+                fputc('\"', f);
+        } else
+                fputs(p, f);
+
+        fputc('\n', f);
+}
+
 int write_env_file(const char *fname, char **l) {
         char **i;
-        char _cleanup_free_ *p = NULL;
-        FILE _cleanup_fclose_ *f = NULL;
+        _cleanup_free_ char *p = NULL;
+        _cleanup_fclose_ FILE *f = NULL;
         int r;
 
         r = fopen_temporary(fname, &f, &p);
@@ -356,19 +601,14 @@ int write_env_file(const char *fname, char **l) {
         fchmod_umask(fileno(f), 0644);
 
         errno = 0;
-        STRV_FOREACH(i, l) {
-                fputs(*i, f);
-                fputc('\n', f);
-        }
+        STRV_FOREACH(i, l)
+                write_env_var(f, *i);
 
         fflush(f);
 
-        if (ferror(f)) {
-                if (errno > 0)
-                        r = -errno;
-                else
-                        r = -EIO;
-        } else {
+        if (ferror(f))
+                r = errno ? -errno : -EIO;
+        else {
                 if (rename(p, fname) < 0)
                         r = -errno;
                 else
@@ -380,3 +620,79 @@ 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(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;
+}