xref: /linux-6.15/fs/fs_parser.c (revision 5eede625)
1b4d0d230SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
231d921c7SDavid Howells /* Filesystem parameter parser.
331d921c7SDavid Howells  *
431d921c7SDavid Howells  * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
531d921c7SDavid Howells  * Written by David Howells ([email protected])
631d921c7SDavid Howells  */
731d921c7SDavid Howells 
831d921c7SDavid Howells #include <linux/export.h>
931d921c7SDavid Howells #include <linux/fs_context.h>
1031d921c7SDavid Howells #include <linux/fs_parser.h>
1131d921c7SDavid Howells #include <linux/slab.h>
1231d921c7SDavid Howells #include <linux/security.h>
1331d921c7SDavid Howells #include <linux/namei.h>
1431d921c7SDavid Howells #include "internal.h"
1531d921c7SDavid Howells 
1631d921c7SDavid Howells static const struct constant_table bool_names[] = {
1731d921c7SDavid Howells 	{ "0",		false },
1831d921c7SDavid Howells 	{ "1",		true },
1931d921c7SDavid Howells 	{ "false",	false },
2031d921c7SDavid Howells 	{ "no",		false },
2131d921c7SDavid Howells 	{ "true",	true },
2231d921c7SDavid Howells 	{ "yes",	true },
2331d921c7SDavid Howells };
2431d921c7SDavid Howells 
2531d921c7SDavid Howells /**
2631d921c7SDavid Howells  * lookup_constant - Look up a constant by name in an ordered table
2731d921c7SDavid Howells  * @tbl: The table of constants to search.
2831d921c7SDavid Howells  * @tbl_size: The size of the table.
2931d921c7SDavid Howells  * @name: The name to look up.
3031d921c7SDavid Howells  * @not_found: The value to return if the name is not found.
3131d921c7SDavid Howells  */
3231d921c7SDavid Howells int __lookup_constant(const struct constant_table *tbl, size_t tbl_size,
3331d921c7SDavid Howells 		      const char *name, int not_found)
3431d921c7SDavid Howells {
3531d921c7SDavid Howells 	unsigned int i;
3631d921c7SDavid Howells 
3731d921c7SDavid Howells 	for (i = 0; i < tbl_size; i++)
3831d921c7SDavid Howells 		if (strcmp(name, tbl[i].name) == 0)
3931d921c7SDavid Howells 			return tbl[i].value;
4031d921c7SDavid Howells 
4131d921c7SDavid Howells 	return not_found;
4231d921c7SDavid Howells }
4331d921c7SDavid Howells EXPORT_SYMBOL(__lookup_constant);
4431d921c7SDavid Howells 
4531d921c7SDavid Howells static const struct fs_parameter_spec *fs_lookup_key(
4631d921c7SDavid Howells 	const struct fs_parameter_description *desc,
4731d921c7SDavid Howells 	const char *name)
4831d921c7SDavid Howells {
4931d921c7SDavid Howells 	const struct fs_parameter_spec *p;
5031d921c7SDavid Howells 
5131d921c7SDavid Howells 	if (!desc->specs)
5231d921c7SDavid Howells 		return NULL;
5331d921c7SDavid Howells 
5431d921c7SDavid Howells 	for (p = desc->specs; p->name; p++)
5531d921c7SDavid Howells 		if (strcmp(p->name, name) == 0)
5631d921c7SDavid Howells 			return p;
5731d921c7SDavid Howells 
5831d921c7SDavid Howells 	return NULL;
5931d921c7SDavid Howells }
6031d921c7SDavid Howells 
6131d921c7SDavid Howells /*
6231d921c7SDavid Howells  * fs_parse - Parse a filesystem configuration parameter
6331d921c7SDavid Howells  * @fc: The filesystem context to log errors through.
6431d921c7SDavid Howells  * @desc: The parameter description to use.
6531d921c7SDavid Howells  * @param: The parameter.
6631d921c7SDavid Howells  * @result: Where to place the result of the parse
6731d921c7SDavid Howells  *
6831d921c7SDavid Howells  * Parse a filesystem configuration parameter and attempt a conversion for a
6931d921c7SDavid Howells  * simple parameter for which this is requested.  If successful, the determined
7031d921c7SDavid Howells  * parameter ID is placed into @result->key, the desired type is indicated in
7131d921c7SDavid Howells  * @result->t and any converted value is placed into an appropriate member of
7231d921c7SDavid Howells  * the union in @result.
7331d921c7SDavid Howells  *
7431d921c7SDavid Howells  * The function returns the parameter number if the parameter was matched,
7531d921c7SDavid Howells  * -ENOPARAM if it wasn't matched and @desc->ignore_unknown indicated that
7631d921c7SDavid Howells  * unknown parameters are okay and -EINVAL if there was a conversion issue or
7731d921c7SDavid Howells  * the parameter wasn't recognised and unknowns aren't okay.
7831d921c7SDavid Howells  */
7931d921c7SDavid Howells int fs_parse(struct fs_context *fc,
8031d921c7SDavid Howells 	     const struct fs_parameter_description *desc,
8131d921c7SDavid Howells 	     struct fs_parameter *param,
8231d921c7SDavid Howells 	     struct fs_parse_result *result)
8331d921c7SDavid Howells {
8431d921c7SDavid Howells 	const struct fs_parameter_spec *p;
85*5eede625SAl Viro 	const struct constant_table *e;
8631d921c7SDavid Howells 	int ret = -ENOPARAM, b;
8731d921c7SDavid Howells 
8831d921c7SDavid Howells 	result->negated = false;
8931d921c7SDavid Howells 	result->uint_64 = 0;
9031d921c7SDavid Howells 
9131d921c7SDavid Howells 	p = fs_lookup_key(desc, param->key);
9231d921c7SDavid Howells 	if (!p) {
9331d921c7SDavid Howells 		/* If we didn't find something that looks like "noxxx", see if
9431d921c7SDavid Howells 		 * "xxx" takes the "no"-form negative - but only if there
9531d921c7SDavid Howells 		 * wasn't an value.
9631d921c7SDavid Howells 		 */
970f89589aSAl Viro 		if (param->type != fs_value_is_flag)
9831d921c7SDavid Howells 			goto unknown_parameter;
9931d921c7SDavid Howells 		if (param->key[0] != 'n' || param->key[1] != 'o' || !param->key[2])
10031d921c7SDavid Howells 			goto unknown_parameter;
10131d921c7SDavid Howells 
10231d921c7SDavid Howells 		p = fs_lookup_key(desc, param->key + 2);
10331d921c7SDavid Howells 		if (!p)
10431d921c7SDavid Howells 			goto unknown_parameter;
10531d921c7SDavid Howells 		if (!(p->flags & fs_param_neg_with_no))
10631d921c7SDavid Howells 			goto unknown_parameter;
10731d921c7SDavid Howells 		result->boolean = false;
10831d921c7SDavid Howells 		result->negated = true;
10931d921c7SDavid Howells 	}
11031d921c7SDavid Howells 
11131d921c7SDavid Howells 	if (p->flags & fs_param_deprecated)
11231d921c7SDavid Howells 		warnf(fc, "%s: Deprecated parameter '%s'",
11331d921c7SDavid Howells 		      desc->name, param->key);
11431d921c7SDavid Howells 
11531d921c7SDavid Howells 	if (result->negated)
11631d921c7SDavid Howells 		goto okay;
11731d921c7SDavid Howells 
11831d921c7SDavid Howells 	/* Certain parameter types only take a string and convert it. */
11931d921c7SDavid Howells 	switch (p->type) {
12031d921c7SDavid Howells 	case __fs_param_wasnt_defined:
12131d921c7SDavid Howells 		return -EINVAL;
12231d921c7SDavid Howells 	case fs_param_is_u32:
12331d921c7SDavid Howells 	case fs_param_is_u32_octal:
12431d921c7SDavid Howells 	case fs_param_is_u32_hex:
12531d921c7SDavid Howells 	case fs_param_is_s32:
12631d921c7SDavid Howells 	case fs_param_is_u64:
12731d921c7SDavid Howells 	case fs_param_is_enum:
12831d921c7SDavid Howells 	case fs_param_is_string:
1290f89589aSAl Viro 		if (param->type == fs_value_is_string) {
1300f89589aSAl Viro 			if (p->flags & fs_param_v_optional)
1310f89589aSAl Viro 				break;
1320f89589aSAl Viro 			if (!*param->string)
13331d921c7SDavid Howells 				goto bad_value;
1340f89589aSAl Viro 			break;
1350f89589aSAl Viro 		}
1360f89589aSAl Viro 		if (param->type == fs_value_is_flag) {
13731d921c7SDavid Howells 			if (p->flags & fs_param_v_optional)
13831d921c7SDavid Howells 				goto okay;
13931d921c7SDavid Howells 		}
1400f89589aSAl Viro 		goto bad_value;
14131d921c7SDavid Howells 	default:
14231d921c7SDavid Howells 		break;
14331d921c7SDavid Howells 	}
14431d921c7SDavid Howells 
14531d921c7SDavid Howells 	/* Try to turn the type we were given into the type desired by the
14631d921c7SDavid Howells 	 * parameter and give an error if we can't.
14731d921c7SDavid Howells 	 */
14831d921c7SDavid Howells 	switch (p->type) {
14931d921c7SDavid Howells 	case fs_param_is_flag:
1500f89589aSAl Viro 		if (param->type != fs_value_is_flag)
15131d921c7SDavid Howells 			return invalf(fc, "%s: Unexpected value for '%s'",
15231d921c7SDavid Howells 				      desc->name, param->key);
15331d921c7SDavid Howells 		result->boolean = true;
15431d921c7SDavid Howells 		goto okay;
15531d921c7SDavid Howells 
15631d921c7SDavid Howells 	case fs_param_is_bool:
15731d921c7SDavid Howells 		switch (param->type) {
15831d921c7SDavid Howells 		case fs_value_is_flag:
15931d921c7SDavid Howells 			result->boolean = true;
16031d921c7SDavid Howells 			goto okay;
16131d921c7SDavid Howells 		case fs_value_is_string:
16231d921c7SDavid Howells 			if (param->size == 0) {
16331d921c7SDavid Howells 				result->boolean = true;
16431d921c7SDavid Howells 				goto okay;
16531d921c7SDavid Howells 			}
16631d921c7SDavid Howells 			b = lookup_constant(bool_names, param->string, -1);
16731d921c7SDavid Howells 			if (b == -1)
16831d921c7SDavid Howells 				goto bad_value;
16931d921c7SDavid Howells 			result->boolean = b;
17031d921c7SDavid Howells 			goto okay;
17131d921c7SDavid Howells 		default:
17231d921c7SDavid Howells 			goto bad_value;
17331d921c7SDavid Howells 		}
17431d921c7SDavid Howells 
17531d921c7SDavid Howells 	case fs_param_is_u32:
17631d921c7SDavid Howells 		ret = kstrtouint(param->string, 0, &result->uint_32);
17731d921c7SDavid Howells 		goto maybe_okay;
17831d921c7SDavid Howells 	case fs_param_is_u32_octal:
17931d921c7SDavid Howells 		ret = kstrtouint(param->string, 8, &result->uint_32);
18031d921c7SDavid Howells 		goto maybe_okay;
18131d921c7SDavid Howells 	case fs_param_is_u32_hex:
18231d921c7SDavid Howells 		ret = kstrtouint(param->string, 16, &result->uint_32);
18331d921c7SDavid Howells 		goto maybe_okay;
18431d921c7SDavid Howells 	case fs_param_is_s32:
18531d921c7SDavid Howells 		ret = kstrtoint(param->string, 0, &result->int_32);
18631d921c7SDavid Howells 		goto maybe_okay;
18731d921c7SDavid Howells 	case fs_param_is_u64:
18831d921c7SDavid Howells 		ret = kstrtoull(param->string, 0, &result->uint_64);
18931d921c7SDavid Howells 		goto maybe_okay;
19031d921c7SDavid Howells 
19131d921c7SDavid Howells 	case fs_param_is_enum:
1922710c957SAl Viro 		for (e = p->data; e->name; e++) {
1932710c957SAl Viro 			if (strcmp(e->name, param->string) == 0) {
19431d921c7SDavid Howells 				result->uint_32 = e->value;
19531d921c7SDavid Howells 				goto okay;
19631d921c7SDavid Howells 			}
19731d921c7SDavid Howells 		}
19831d921c7SDavid Howells 		goto bad_value;
19931d921c7SDavid Howells 
20031d921c7SDavid Howells 	case fs_param_is_string:
20131d921c7SDavid Howells 		goto okay;
20231d921c7SDavid Howells 	case fs_param_is_blob:
20331d921c7SDavid Howells 		if (param->type != fs_value_is_blob)
20431d921c7SDavid Howells 			goto bad_value;
20531d921c7SDavid Howells 		goto okay;
20631d921c7SDavid Howells 
20731d921c7SDavid Howells 	case fs_param_is_fd: {
20874983ac2SDavid Howells 		switch (param->type) {
20974983ac2SDavid Howells 		case fs_value_is_string:
21074983ac2SDavid Howells 			ret = kstrtouint(param->string, 0, &result->uint_32);
21174983ac2SDavid Howells 			break;
21274983ac2SDavid Howells 		case fs_value_is_file:
21374983ac2SDavid Howells 			result->uint_32 = param->dirfd;
21474983ac2SDavid Howells 			ret = 0;
21574983ac2SDavid Howells 		default:
21674983ac2SDavid Howells 			goto bad_value;
21774983ac2SDavid Howells 		}
21874983ac2SDavid Howells 
21974983ac2SDavid Howells 		if (result->uint_32 > INT_MAX)
22074983ac2SDavid Howells 			goto bad_value;
22174983ac2SDavid Howells 		goto maybe_okay;
22231d921c7SDavid Howells 	}
22331d921c7SDavid Howells 
22431d921c7SDavid Howells 	case fs_param_is_blockdev:
22531d921c7SDavid Howells 	case fs_param_is_path:
22631d921c7SDavid Howells 		goto okay;
22731d921c7SDavid Howells 	default:
22831d921c7SDavid Howells 		BUG();
22931d921c7SDavid Howells 	}
23031d921c7SDavid Howells 
23131d921c7SDavid Howells maybe_okay:
23231d921c7SDavid Howells 	if (ret < 0)
23331d921c7SDavid Howells 		goto bad_value;
23431d921c7SDavid Howells okay:
23531d921c7SDavid Howells 	return p->opt;
23631d921c7SDavid Howells 
23731d921c7SDavid Howells bad_value:
23831d921c7SDavid Howells 	return invalf(fc, "%s: Bad value for '%s'", desc->name, param->key);
23931d921c7SDavid Howells unknown_parameter:
24031d921c7SDavid Howells 	return -ENOPARAM;
24131d921c7SDavid Howells }
24231d921c7SDavid Howells EXPORT_SYMBOL(fs_parse);
24331d921c7SDavid Howells 
24431d921c7SDavid Howells /**
24531d921c7SDavid Howells  * fs_lookup_param - Look up a path referred to by a parameter
24631d921c7SDavid Howells  * @fc: The filesystem context to log errors through.
24731d921c7SDavid Howells  * @param: The parameter.
24831d921c7SDavid Howells  * @want_bdev: T if want a blockdev
24931d921c7SDavid Howells  * @_path: The result of the lookup
25031d921c7SDavid Howells  */
25131d921c7SDavid Howells int fs_lookup_param(struct fs_context *fc,
25231d921c7SDavid Howells 		    struct fs_parameter *param,
25331d921c7SDavid Howells 		    bool want_bdev,
25431d921c7SDavid Howells 		    struct path *_path)
25531d921c7SDavid Howells {
25631d921c7SDavid Howells 	struct filename *f;
25731d921c7SDavid Howells 	unsigned int flags = 0;
25831d921c7SDavid Howells 	bool put_f;
25931d921c7SDavid Howells 	int ret;
26031d921c7SDavid Howells 
26131d921c7SDavid Howells 	switch (param->type) {
26231d921c7SDavid Howells 	case fs_value_is_string:
26331d921c7SDavid Howells 		f = getname_kernel(param->string);
26431d921c7SDavid Howells 		if (IS_ERR(f))
26531d921c7SDavid Howells 			return PTR_ERR(f);
26631d921c7SDavid Howells 		put_f = true;
26731d921c7SDavid Howells 		break;
26831d921c7SDavid Howells 	case fs_value_is_filename_empty:
26931d921c7SDavid Howells 		flags = LOOKUP_EMPTY;
27031d921c7SDavid Howells 		/* Fall through */
27131d921c7SDavid Howells 	case fs_value_is_filename:
27231d921c7SDavid Howells 		f = param->name;
27331d921c7SDavid Howells 		put_f = false;
27431d921c7SDavid Howells 		break;
27531d921c7SDavid Howells 	default:
27631d921c7SDavid Howells 		return invalf(fc, "%s: not usable as path", param->key);
27731d921c7SDavid Howells 	}
27831d921c7SDavid Howells 
2797cdfa442SDavid Howells 	f->refcnt++; /* filename_lookup() drops our ref. */
28031d921c7SDavid Howells 	ret = filename_lookup(param->dirfd, f, flags, _path, NULL);
28131d921c7SDavid Howells 	if (ret < 0) {
28231d921c7SDavid Howells 		errorf(fc, "%s: Lookup failure for '%s'", param->key, f->name);
28331d921c7SDavid Howells 		goto out;
28431d921c7SDavid Howells 	}
28531d921c7SDavid Howells 
28631d921c7SDavid Howells 	if (want_bdev &&
28731d921c7SDavid Howells 	    !S_ISBLK(d_backing_inode(_path->dentry)->i_mode)) {
28831d921c7SDavid Howells 		path_put(_path);
28931d921c7SDavid Howells 		_path->dentry = NULL;
29031d921c7SDavid Howells 		_path->mnt = NULL;
29131d921c7SDavid Howells 		errorf(fc, "%s: Non-blockdev passed as '%s'",
29231d921c7SDavid Howells 		       param->key, f->name);
29331d921c7SDavid Howells 		ret = -ENOTBLK;
29431d921c7SDavid Howells 	}
29531d921c7SDavid Howells 
29631d921c7SDavid Howells out:
29731d921c7SDavid Howells 	if (put_f)
29831d921c7SDavid Howells 		putname(f);
29931d921c7SDavid Howells 	return ret;
30031d921c7SDavid Howells }
30131d921c7SDavid Howells EXPORT_SYMBOL(fs_lookup_param);
30231d921c7SDavid Howells 
30331d921c7SDavid Howells #ifdef CONFIG_VALIDATE_FS_PARSER
30431d921c7SDavid Howells /**
30531d921c7SDavid Howells  * validate_constant_table - Validate a constant table
30631d921c7SDavid Howells  * @name: Name to use in reporting
30731d921c7SDavid Howells  * @tbl: The constant table to validate.
30831d921c7SDavid Howells  * @tbl_size: The size of the table.
30931d921c7SDavid Howells  * @low: The lowest permissible value.
31031d921c7SDavid Howells  * @high: The highest permissible value.
31131d921c7SDavid Howells  * @special: One special permissible value outside of the range.
31231d921c7SDavid Howells  */
31331d921c7SDavid Howells bool validate_constant_table(const struct constant_table *tbl, size_t tbl_size,
31431d921c7SDavid Howells 			     int low, int high, int special)
31531d921c7SDavid Howells {
31631d921c7SDavid Howells 	size_t i;
31731d921c7SDavid Howells 	bool good = true;
31831d921c7SDavid Howells 
31931d921c7SDavid Howells 	if (tbl_size == 0) {
32031d921c7SDavid Howells 		pr_warn("VALIDATE C-TBL: Empty\n");
32131d921c7SDavid Howells 		return true;
32231d921c7SDavid Howells 	}
32331d921c7SDavid Howells 
32431d921c7SDavid Howells 	for (i = 0; i < tbl_size; i++) {
32531d921c7SDavid Howells 		if (!tbl[i].name) {
32631d921c7SDavid Howells 			pr_err("VALIDATE C-TBL[%zu]: Null\n", i);
32731d921c7SDavid Howells 			good = false;
32831d921c7SDavid Howells 		} else if (i > 0 && tbl[i - 1].name) {
32931d921c7SDavid Howells 			int c = strcmp(tbl[i-1].name, tbl[i].name);
33031d921c7SDavid Howells 
33131d921c7SDavid Howells 			if (c == 0) {
33231d921c7SDavid Howells 				pr_err("VALIDATE C-TBL[%zu]: Duplicate %s\n",
33331d921c7SDavid Howells 				       i, tbl[i].name);
33431d921c7SDavid Howells 				good = false;
33531d921c7SDavid Howells 			}
33631d921c7SDavid Howells 			if (c > 0) {
33731d921c7SDavid Howells 				pr_err("VALIDATE C-TBL[%zu]: Missorted %s>=%s\n",
33831d921c7SDavid Howells 				       i, tbl[i-1].name, tbl[i].name);
33931d921c7SDavid Howells 				good = false;
34031d921c7SDavid Howells 			}
34131d921c7SDavid Howells 		}
34231d921c7SDavid Howells 
34331d921c7SDavid Howells 		if (tbl[i].value != special &&
34431d921c7SDavid Howells 		    (tbl[i].value < low || tbl[i].value > high)) {
34531d921c7SDavid Howells 			pr_err("VALIDATE C-TBL[%zu]: %s->%d const out of range (%d-%d)\n",
34631d921c7SDavid Howells 			       i, tbl[i].name, tbl[i].value, low, high);
34731d921c7SDavid Howells 			good = false;
34831d921c7SDavid Howells 		}
34931d921c7SDavid Howells 	}
35031d921c7SDavid Howells 
35131d921c7SDavid Howells 	return good;
35231d921c7SDavid Howells }
35331d921c7SDavid Howells 
35431d921c7SDavid Howells /**
35531d921c7SDavid Howells  * fs_validate_description - Validate a parameter description
35631d921c7SDavid Howells  * @desc: The parameter description to validate.
35731d921c7SDavid Howells  */
35831d921c7SDavid Howells bool fs_validate_description(const struct fs_parameter_description *desc)
35931d921c7SDavid Howells {
36031d921c7SDavid Howells 	const struct fs_parameter_spec *param, *p2;
36131d921c7SDavid Howells 	const char *name = desc->name;
3622710c957SAl Viro 	bool good = true;
36331d921c7SDavid Howells 
36431d921c7SDavid Howells 	pr_notice("*** VALIDATE %s ***\n", name);
36531d921c7SDavid Howells 
36631d921c7SDavid Howells 	if (!name[0]) {
36731d921c7SDavid Howells 		pr_err("VALIDATE Parser: No name\n");
36831d921c7SDavid Howells 		name = "Unknown";
36931d921c7SDavid Howells 		good = false;
37031d921c7SDavid Howells 	}
37131d921c7SDavid Howells 
37231d921c7SDavid Howells 	if (desc->specs) {
37331d921c7SDavid Howells 		for (param = desc->specs; param->name; param++) {
37431d921c7SDavid Howells 			enum fs_parameter_type t = param->type;
37531d921c7SDavid Howells 
37631d921c7SDavid Howells 			/* Check that the type is in range */
37731d921c7SDavid Howells 			if (t == __fs_param_wasnt_defined ||
37831d921c7SDavid Howells 			    t >= nr__fs_parameter_type) {
37931d921c7SDavid Howells 				pr_err("VALIDATE %s: PARAM[%s] Bad type %u\n",
38031d921c7SDavid Howells 				       name, param->name, t);
38131d921c7SDavid Howells 				good = false;
38231d921c7SDavid Howells 			} else if (t == fs_param_is_enum) {
383*5eede625SAl Viro 				const struct constant_table *e = param->data;
3842710c957SAl Viro 				if (!e || !e->name) {
3852710c957SAl Viro 					pr_err("VALIDATE %s: PARAM[%s] enum with no values\n",
3862710c957SAl Viro 					       name, param->name);
3872710c957SAl Viro 					good = false;
3882710c957SAl Viro 				}
38931d921c7SDavid Howells 			}
39031d921c7SDavid Howells 
39131d921c7SDavid Howells 			/* Check for duplicate parameter names */
39231d921c7SDavid Howells 			for (p2 = desc->specs; p2 < param; p2++) {
39331d921c7SDavid Howells 				if (strcmp(param->name, p2->name) == 0) {
39431d921c7SDavid Howells 					pr_err("VALIDATE %s: PARAM[%s]: Duplicate\n",
39531d921c7SDavid Howells 					       name, param->name);
39631d921c7SDavid Howells 					good = false;
39731d921c7SDavid Howells 				}
39831d921c7SDavid Howells 			}
39931d921c7SDavid Howells 		}
40031d921c7SDavid Howells 	}
40131d921c7SDavid Howells 	return good;
40231d921c7SDavid Howells }
40331d921c7SDavid Howells #endif /* CONFIG_VALIDATE_FS_PARSER */
404