chiark / gitweb /
seccomp-util.h: make sure seccomp-util.h can be included alone
[elogind.git] / src / shared / fileio.c
index 1c7d4851303af7ba9caae54809dc83207c8d55a5..ff6b1a7ed776db2c6b53a41b09fec1ed43466396 100644 (file)
 ***/
 
 #include <unistd.h>
-#include "fileio.h"
+
 #include "util.h"
 #include "strv.h"
+#include "utf8.h"
+#include "ctype.h"
+#include "fileio.h"
 
-int write_one_line_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;
-        if (fputs(line, f) < 0)
-                return errno ? -errno : -EIO;
 
+        fputs(line, f);
         if (!endswith(line, "\n"))
                 fputc('\n', f);
 
@@ -49,7 +45,42 @@ int write_one_line_file(const char *fn, const char *line) {
         return 0;
 }
 
-int write_one_line_file_atomic(const char *fn, const char *line) {
+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_no_create(const char *fn, const char *line) {
+        _cleanup_fclose_ FILE *f = NULL;
+        int fd;
+
+        assert(fn);
+        assert(line);
+
+        /* We manually build our own version of fopen(..., "we") that
+         * works without O_CREAT */
+        fd = open(fn, O_WRONLY|O_CLOEXEC|O_NOCTTY);
+        if (fd < 0)
+                return -errno;
+
+        f = fdopen(fd, "we");
+        if (!f) {
+                safe_close(fd);
+                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;
         int r;
@@ -63,27 +94,12 @@ int write_one_line_file_atomic(const char *fn, const char *line) {
 
         fchmod_umask(fileno(f), 0644);
 
-        errno = 0;
-        if (fputs(line, f) < 0) {
-                r = -errno;
-                goto finish;
-        }
-
-        if (!endswith(line, "\n"))
-                fputc('\n', f);
-
-        fflush(f);
-
-        if (ferror(f))
-                r = errno ? -errno : -EIO;
-        else {
+        r = write_string_stream(f, line);
+        if (r >= 0) {
                 if (rename(p, fn) < 0)
                         r = -errno;
-                else
-                        r = 0;
         }
 
-finish:
         if (r < 0)
                 unlink(p);
 
@@ -118,29 +134,33 @@ 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;
+int read_full_stream(FILE *f, char **contents, size_t *size) {
         size_t n, l;
         _cleanup_free_ char *buf = NULL;
         struct stat st;
 
-        assert(fn);
+        assert(f);
         assert(contents);
 
-        f = fopen(fn, "re");
-        if (!f)
-                return -errno;
-
         if (fstat(fileno(f), &st) < 0)
                 return -errno;
 
-        /* Safety check */
-        if (st.st_size > 4*1024*1024)
-                return -E2BIG;
+        n = LINE_MAX;
 
-        n = st.st_size > 0 ? st.st_size : LINE_MAX;
-        l = 0;
+        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;
@@ -169,7 +189,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;
@@ -177,206 +197,621 @@ int read_full_file(const char *fn, char **contents, size_t *size) {
         return 0;
 }
 
-int parse_env_file(
-                const char *fname,
-                const char *separator, ...) {
+int read_full_file(const char *fn, char **contents, size_t *size) {
+        _cleanup_fclose_ FILE *f = NULL;
 
-        int r = 0;
-        char *contents = NULL, *p;
+        assert(fn);
+        assert(contents);
 
-        assert(fname);
-        assert(separator);
+        f = fopen(fn, "re");
+        if (!f)
+                return -errno;
+
+        return read_full_stream(f, contents, size);
+}
 
-        r = read_full_file(fname, &contents, NULL);
+static int parse_env_file_internal(
+                FILE *f,
+                const char *fname,
+                const char *newline,
+                int (*push) (const char *filename, unsigned line,
+                             const char *key, char *value, void *userdata, int *n_pushed),
+                void *userdata,
+                int *n_pushed) {
+
+        _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(newline);
+
+        if (f)
+                r = read_full_stream(f, &contents, NULL);
+        else
+                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(key, key_alloc, n_key+2)) {
+                                        r = -ENOMEM;
+                                        goto fail;
+                                }
 
-                p += strspn(p, separator);
-                p += strspn(p, WHITESPACE);
+                                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(key, key_alloc, n_key+2)) {
+                                        r = -ENOMEM;
+                                        goto fail;
+                                }
+
+                                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, n_pushed);
+                                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(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, n_pushed);
+                                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(value, value_alloc, n_value+2)) {
+                                        r = -ENOMEM;
+                                        goto fail;
                                 }
 
-                                free(*value);
-                                *value = v;
+                                value[n_value++] = c;
+                        }
 
-                                p += n;
+                        break;
 
-                                r ++;
-                                break;
+                case VALUE_ESCAPE:
+                        state = VALUE;
+
+                        if (!strchr(newline, c)) {
+                                /* Escaped newlines we eat up entirely */
+                                if (!GREEDY_REALLOC(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(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(value, value_alloc, n_value+2)) {
+                                        r = -ENOMEM;
+                                        goto fail;
+                                }
+
+                                value[n_value++] = c;
                         }
-                        va_end(ap);
+                        break;
+
+                case DOUBLE_QUOTE_VALUE:
+                        if (c == '\"')
+                                state = PRE_VALUE;
+                        else if (c == '\\')
+                                state = DOUBLE_QUOTE_VALUE_ESCAPE;
+                        else {
+                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
+                                        r = -ENOMEM;
+                                        goto fail;
+                                }
+
+                                value[n_value++] = c;
+                        }
+
+                        break;
+
+                case DOUBLE_QUOTE_VALUE_ESCAPE:
+                        state = DOUBLE_QUOTE_VALUE;
+
+                        if (!strchr(newline, c)) {
+                                if (!GREEDY_REALLOC(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 (state == VALUE)
+                        if (last_value_whitespace != (size_t) -1)
+                                value[last_value_whitespace] = 0;
 
-                if (!key)
-                        p += strcspn(p, separator);
+                /* strip trailing whitespace from key */
+                if (last_key_whitespace != (size_t) -1)
+                        key[last_key_whitespace] = 0;
+
+                r = push(fname, line, key, value, userdata, n_pushed);
+                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,
+                int *n_pushed) {
 
-        _cleanup_fclose_ FILE *f;
-        _cleanup_strv_free_ char **m = NULL;
-        _cleanup_free_ char *c = NULL;
+        const char *k;
+        va_list aq, *ap = userdata;
 
-        assert(fname);
-        assert(rl);
+        if (!utf8_is_valid(key)) {
+                _cleanup_free_ char *p;
 
-        /* This reads an environment file, but will not complain about
-         * any invalid assignments, that needs to be done by the
-         * caller */
+                p = utf8_escape_invalid(key);
+                log_error("%s:%u: invalid UTF-8 in key '%s', ignoring.", strna(filename), line, p);
+                return -EINVAL;
+        }
 
-        f = fopen(fname, "re");
-        if (!f)
-                return -errno;
+        if (value && !utf8_is_valid(value)) {
+                _cleanup_free_ char *p;
 
-        while (!feof(f)) {
-                char l[LINE_MAX], *p, *cs, *b;
+                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;
+        }
 
-                if (!fgets(l, sizeof(l), f)) {
-                        if (ferror(f))
-                                return -errno;
+        va_copy(aq, *ap);
 
-                        /* The previous line was a continuation line?
-                         * Let's process it now, before we leave the
-                         * loop */
-                        if (c)
-                                goto process;
+        while ((k = va_arg(aq, const char *))) {
+                char **v;
 
-                        break;
-                }
+                v = va_arg(aq, char **);
 
-                /* 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;
-                }
+                if (streq(key, k)) {
+                        va_end(aq);
+                        free(*v);
+                        *v = value;
 
-                /* 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 (n_pushed)
+                                (*n_pushed)++;
 
-                        free(c);
-                        c = b;
+                        return 1;
                 }
+        }
+
+        va_end(aq);
+        free(value);
+
+        return 0;
+}
 
-        process:
-                p = strstrip(c ? c : l);
+int parse_env_file(
+                const char *fname,
+                const char *newline, ...) {
 
-                if (*p && !strchr(COMMENTS, *p)) {
-                        _cleanup_free_ char *u;
-                        int k;
+        va_list ap;
+        int r, n_pushed = 0;
 
-                        u = normalize_env_assignment(p);
-                        if (!u)
-                                return -ENOMEM;
+        if (!newline)
+                newline = NEWLINE;
 
-                        k = strv_extend(&m, u);
-                        if (k < 0)
-                                return -ENOMEM;
-                }
+        va_start(ap, newline);
+        r = parse_env_file_internal(NULL, fname, newline, parse_env_file_push, &ap, &n_pushed);
+        va_end(ap);
 
-                free(c);
-                c = NULL;
+        return r < 0 ? r : n_pushed;
+}
+
+static int load_env_file_push(
+                const char *filename, unsigned line,
+                const char *key, char *value,
+                void *userdata,
+                int *n_pushed) {
+        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_consume(m, p);
+        if (r < 0)
+                return r;
+
+        if (n_pushed)
+                (*n_pushed)++;
+
+        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, NULL);
+        if (r < 0) {
+                strv_free(m);
+                return r;
         }
 
         *rl = m;
-        m = NULL;
+        return 0;
+}
+
+static int load_env_file_push_pairs(
+                const char *filename, unsigned line,
+                const char *key, char *value,
+                void *userdata,
+                int *n_pushed) {
+        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;
+        }
+
+        if (n_pushed)
+                (*n_pushed)++;
+
+        return 0;
+}
+
+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(f, fname, newline, load_env_file_push_pairs, &m, NULL);
+        if (r < 0) {
+                strv_free(m);
+                return r;
+        }
+
+        *rl = m;
         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, NULL) || chars_intersect(p, WHITESPACE SHELL_NEED_QUOTES)) {
+                fputc('\"', f);
+
+                for (; *p; p++) {
+                        if (strchr(SHELL_NEED_ESCAPE, *p))
+                                fputc('\\', f);
+
+                        fputc(*p, f);
+                }
+
+                fputc('\"', f);
+        } else
+                fputs(p, f);
+
+        fputc('\n', f);
+}
+
 int write_env_file(const char *fname, char **l) {
+        _cleanup_fclose_ FILE *f = NULL;
+        _cleanup_free_ char *p = NULL;
         char **i;
-        char _cleanup_free_ *p = NULL;
-        FILE _cleanup_fclose_ *f = NULL;
         int r;
 
+        assert(fname);
+
         r = fopen_temporary(fname, &f, &p);
         if (r < 0)
                 return r;
 
         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);
+        r = fflush_and_check(f);
+        if (r >= 0) {
+                if (rename(p, fname) >= 0)
+                        return 0;
 
-        if (ferror(f)) {
-                if (errno > 0)
-                        r = -errno;
-                else
-                        r = -EIO;
-        } else {
-                if (rename(p, fname) < 0)
-                        r = -errno;
-                else
-                        r = 0;
+                r = -errno;
         }
 
+        unlink(p);
+        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)
-                unlink(p);
+                return r;
 
-        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;
 }