parse_flags(const char *s, const char *(*bit_to_string)(uint32_t),
            char end, const char *field_name, char **res_string,
            uint32_t *res_flags, uint32_t allowed, uint32_t *res_mask)
{
    uint32_t result = 0;
    int n;

    /* Parse masked flags in numeric format? */
    if (res_mask && ovs_scan(s, "%"SCNi32"/%"SCNi32"%n",
                             res_flags, res_mask, &n) && n > 0) {
        if (*res_flags & ~allowed || *res_mask & ~allowed) {
            goto unknown;
        }
        return n;
    }

    n = 0;

    if (res_mask && (*s == '+' || *s == '-')) {
        uint32_t flags = 0, mask = 0;

        /* Parse masked flags. */
        while (s[0] != end) {
            bool set;
            uint32_t bit;
            size_t len;

            if (s[0] == '+') {
                set = true;
            } else if (s[0] == '-') {
                set = false;
            } else {
                if (res_string) {
                    *res_string = xasprintf("%s: %s must be preceded by '+' "
                                            "(for SET) or '-' (NOT SET)", s,
                                            field_name);
                }
                return -EINVAL;
            }
            s++;
            n++;

            for (bit = 1; bit; bit <<= 1) {
                const char *fname = bit_to_string(bit);

                if (!fname) {
                    continue;
                }

                len = strlen(fname);
                if (strncmp(s, fname, len) ||
                    (s[len] != '+' && s[len] != '-' && s[len] != end)) {
                    continue;
                }

                if (mask & bit) {
                    /* bit already set. */
                    if (res_string) {
                        *res_string = xasprintf("%s: Each %s flag can be "
                                                "specified only once", s,
                                                field_name);
                    }
                    return -EINVAL;
                }
                if (!(bit & allowed)) {
                    goto unknown;
                }
                if (set) {
                   flags |= bit;
                }
                mask |= bit;
                break;
            }

            if (!bit) {
                goto unknown;
            }
            s += len;
            n += len;
        }

        *res_flags = flags;
        *res_mask = mask;
        return n;
    }

    /* Parse unmasked flags.  If a flag is present, it is set, otherwise
     * it is not set. */
    while (s[n] != end) {
        unsigned long long int flags;
        uint32_t bit;
        int n0;

        if (ovs_scan(&s[n], "%lli%n", &flags, &n0)) {
            if (flags & ~allowed) {
                goto unknown;
            }
            n += n0 + (s[n + n0] == '|');
            result |= flags;
            continue;
        }

        for (bit = 1; bit; bit <<= 1) {
            const char *name = bit_to_string(bit);
            size_t len;

            if (!name) {
                continue;
            }

            len = strlen(name);
            if (!strncmp(s + n, name, len) &&
                (s[n + len] == '|' || s[n + len] == end)) {
                if (!(bit & allowed)) {
                    goto unknown;
                }
                result |= bit;
                n += len + (s[n + len] == '|');
                break;
            }
        }

        if (!bit) {
            goto unknown;
        }
    }

    *res_flags = result;
    if (res_mask) {
        *res_mask = UINT32_MAX;
    }
    if (res_string) {
        *res_string = NULL;
    }
    return n;

unknown:
    if (res_string) {
        *res_string = xasprintf("%s: unknown %s flag(s)", s, field_name);
    }
    return -EINVAL;
}