static int parse_line(const char *fname, unsigned line, const char *buffer, bool *invalid_config) {

        _cleanup_free_ char *action = NULL, *mode = NULL, *user = NULL, *group = NULL, *age = NULL, *path = NULL;
        _cleanup_(item_free_contents) Item i = {};
        ItemArray *existing;
        OrderedHashmap *h;
        int r, pos;
        bool force = false, boot = false;

        assert(fname);
        assert(line >= 1);
        assert(buffer);

        r = extract_many_words(
                        &buffer,
                        NULL,
                        EXTRACT_QUOTES,
                        &action,
                        &path,
                        &mode,
                        &user,
                        &group,
                        &age,
                        NULL);
        if (r < 0) {
                if (IN_SET(r, -EINVAL, -EBADSLT))
                        /* invalid quoting and such or an unknown specifier */
                        *invalid_config = true;
                return log_error_errno(r, "[%s:%u] Failed to parse line: %m", fname, line);
        }

        else if (r < 2) {
                *invalid_config = true;
                log_error("[%s:%u] Syntax error.", fname, line);
                return -EIO;
        }

        if (!isempty(buffer) && !streq(buffer, "-")) {
                i.argument = strdup(buffer);
                if (!i.argument)
                        return log_oom();
        }

        if (isempty(action)) {
                *invalid_config = true;
                log_error("[%s:%u] Command too short '%s'.", fname, line, action);
                return -EINVAL;
        }

        for (pos = 1; action[pos]; pos++) {
                if (action[pos] == '!' && !boot)
                        boot = true;
                else if (action[pos] == '+' && !force)
                        force = true;
                else {
                        *invalid_config = true;
                        log_error("[%s:%u] Unknown modifiers in command '%s'",
                                  fname, line, action);
                        return -EINVAL;
                }
        }

        if (boot && !arg_boot) {
                log_debug("Ignoring entry %s \"%s\" because --boot is not specified.",
                          action, path);
                return 0;
        }

        i.type = action[0];
        i.force = force;

        r = specifier_printf(path, specifier_table, NULL, &i.path);
        if (r == -ENXIO)
                return log_unresolvable_specifier(fname, line);
        if (r < 0) {
                if (IN_SET(r, -EINVAL, -EBADSLT))
                        *invalid_config = true;
                return log_error_errno(r, "[%s:%u] Failed to replace specifiers: %s", fname, line, path);
        }

        switch (i.type) {

        case CREATE_DIRECTORY:
        case CREATE_SUBVOLUME:
        case CREATE_SUBVOLUME_INHERIT_QUOTA:
        case CREATE_SUBVOLUME_NEW_QUOTA:
        case EMPTY_DIRECTORY:
        case TRUNCATE_DIRECTORY:
        case CREATE_FIFO:
        case IGNORE_PATH:
        case IGNORE_DIRECTORY_PATH:
        case REMOVE_PATH:
        case RECURSIVE_REMOVE_PATH:
        case ADJUST_MODE:
        case RELABEL_PATH:
        case RECURSIVE_RELABEL_PATH:
                if (i.argument)
                        log_warning("[%s:%u] %c lines don't take argument fields, ignoring.", fname, line, i.type);

                break;

        case CREATE_FILE:
        case TRUNCATE_FILE:
                break;

        case CREATE_SYMLINK:
                if (!i.argument) {
                        i.argument = strappend("/usr/share/factory/", i.path);
                        if (!i.argument)
                                return log_oom();
                }
                break;

        case WRITE_FILE:
                if (!i.argument) {
                        *invalid_config = true;
                        log_error("[%s:%u] Write file requires argument.", fname, line);
                        return -EBADMSG;
                }
                break;

        case COPY_FILES:
                if (!i.argument) {
                        i.argument = strappend("/usr/share/factory/", i.path);
                        if (!i.argument)
                                return log_oom();
                } else if (!path_is_absolute(i.argument)) {
                        *invalid_config = true;
                        log_error("[%s:%u] Source path is not absolute.", fname, line);
                        return -EBADMSG;
                }

                path_kill_slashes(i.argument);
                break;

        case CREATE_CHAR_DEVICE:
        case CREATE_BLOCK_DEVICE: {
                unsigned major, minor;

                if (!i.argument) {
                        *invalid_config = true;
                        log_error("[%s:%u] Device file requires argument.", fname, line);
                        return -EBADMSG;
                }

                if (sscanf(i.argument, "%u:%u", &major, &minor) != 2) {
                        *invalid_config = true;
                        log_error("[%s:%u] Can't parse device file major/minor '%s'.", fname, line, i.argument);
                        return -EBADMSG;
                }

                i.major_minor = makedev(major, minor);
                break;
        }

        case SET_XATTR:
        case RECURSIVE_SET_XATTR:
                if (!i.argument) {
                        *invalid_config = true;
                        log_error("[%s:%u] Set extended attribute requires argument.", fname, line);
                        return -EBADMSG;
                }
                r = parse_xattrs_from_arg(&i);
                if (r < 0)
                        return r;
                break;

        case SET_ACL:
        case RECURSIVE_SET_ACL:
                if (!i.argument) {
                        *invalid_config = true;
                        log_error("[%s:%u] Set ACLs requires argument.", fname, line);
                        return -EBADMSG;
                }
                r = parse_acls_from_arg(&i);
                if (r < 0)
                        return r;
                break;

        case SET_ATTRIBUTE:
        case RECURSIVE_SET_ATTRIBUTE:
                if (!i.argument) {
                        *invalid_config = true;
                        log_error("[%s:%u] Set file attribute requires argument.", fname, line);
                        return -EBADMSG;
                }
                r = parse_attribute_from_arg(&i);
                if (IN_SET(r, -EINVAL, -EBADSLT))
                        *invalid_config = true;
                if (r < 0)
                        return r;
                break;

        default:
                log_error("[%s:%u] Unknown command type '%c'.", fname, line, (char) i.type);
                *invalid_config = true;
                return -EBADMSG;
        }

        if (!path_is_absolute(i.path)) {
                log_error("[%s:%u] Path '%s' not absolute.", fname, line, i.path);
                *invalid_config = true;
                return -EBADMSG;
        }

        path_kill_slashes(i.path);

        if (!should_include_path(i.path))
                return 0;

        r = specifier_expansion_from_arg(&i);
        if (r == -ENXIO)
                return log_unresolvable_specifier(fname, line);
        if (r < 0) {
                if (IN_SET(r, -EINVAL, -EBADSLT))
                        *invalid_config = true;
                return log_error_errno(r, "[%s:%u] Failed to substitute specifiers in argument: %m",
                                       fname, line);
        }

        if (arg_root) {
                char *p;

                p = prefix_root(arg_root, i.path);
                if (!p)
                        return log_oom();

                free(i.path);
                i.path = p;
        }

        if (!isempty(user) && !streq(user, "-")) {
                const char *u = user;

                r = get_user_creds(&u, &i.uid, NULL, NULL, NULL);
                if (r < 0) {
                        *invalid_config = true;
                        return log_error_errno(r, "[%s:%u] Unknown user '%s'.", fname, line, user);
                }

                i.uid_set = true;
        }

        if (!isempty(group) && !streq(group, "-")) {
                const char *g = group;

                r = get_group_creds(&g, &i.gid);
                if (r < 0) {
                        *invalid_config = true;
                        log_error("[%s:%u] Unknown group '%s'.", fname, line, group);
                        return r;
                }

                i.gid_set = true;
        }

        if (!isempty(mode) && !streq(mode, "-")) {
                const char *mm = mode;
                unsigned m;

                if (*mm == '~') {
                        i.mask_perms = true;
                        mm++;
                }

                if (parse_mode(mm, &m) < 0) {
                        *invalid_config = true;
                        log_error("[%s:%u] Invalid mode '%s'.", fname, line, mode);
                        return -EBADMSG;
                }

                i.mode = m;
                i.mode_set = true;
        } else
                i.mode = IN_SET(i.type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY, CREATE_SUBVOLUME, CREATE_SUBVOLUME_INHERIT_QUOTA, CREATE_SUBVOLUME_NEW_QUOTA) ? 0755 : 0644;

        if (!isempty(age) && !streq(age, "-")) {
                const char *a = age;

                if (*a == '~') {
                        i.keep_first_level = true;
                        a++;
                }

                if (parse_sec(a, &i.age) < 0) {
                        *invalid_config = true;
                        log_error("[%s:%u] Invalid age '%s'.", fname, line, age);
                        return -EBADMSG;
                }

                i.age_set = true;
        }

        h = needs_glob(i.type) ? globs : items;

        existing = ordered_hashmap_get(h, i.path);
        if (existing) {
                unsigned n;

                for (n = 0; n < existing->count; n++) {
                        if (!item_compatible(existing->items + n, &i)) {
                                log_notice("[%s:%u] Duplicate line for path \"%s\", ignoring.",
                                           fname, line, i.path);
                                return 0;
                        }
                }
        } else {
                existing = new0(ItemArray, 1);
                if (!existing)
                        return log_oom();

                r = ordered_hashmap_put(h, i.path, existing);
                if (r < 0)
                        return log_oom();
        }

        if (!GREEDY_REALLOC(existing->items, existing->size, existing->count + 1))
                return log_oom();

        memcpy(existing->items + existing->count++, &i, sizeof(i));

        /* Sort item array, to enforce stable ordering of application */
        qsort_safe(existing->items, existing->count, sizeof(Item), item_compare);

        zero(i);
        return 0;
}