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 }, 2334264ae3SAl Viro { }, 2431d921c7SDavid Howells }; 2531d921c7SDavid Howells 2634264ae3SAl Viro static const struct constant_table * 2734264ae3SAl Viro __lookup_constant(const struct constant_table *tbl, const char *name) 2834264ae3SAl Viro { 2934264ae3SAl Viro for ( ; tbl->name; tbl++) 3034264ae3SAl Viro if (strcmp(name, tbl->name) == 0) 3134264ae3SAl Viro return tbl; 3234264ae3SAl Viro return NULL; 3334264ae3SAl Viro } 3434264ae3SAl Viro 3531d921c7SDavid Howells /** 3631d921c7SDavid Howells * lookup_constant - Look up a constant by name in an ordered table 3731d921c7SDavid Howells * @tbl: The table of constants to search. 3831d921c7SDavid Howells * @name: The name to look up. 3931d921c7SDavid Howells * @not_found: The value to return if the name is not found. 4031d921c7SDavid Howells */ 4134264ae3SAl Viro int lookup_constant(const struct constant_table *tbl, const char *name, int not_found) 4231d921c7SDavid Howells { 4334264ae3SAl Viro const struct constant_table *p = __lookup_constant(tbl, name); 4431d921c7SDavid Howells 4534264ae3SAl Viro return p ? p->value : not_found; 4631d921c7SDavid Howells } 4734264ae3SAl Viro EXPORT_SYMBOL(lookup_constant); 4831d921c7SDavid Howells 4948ce73b1SAl Viro static inline bool is_flag(const struct fs_parameter_spec *p) 5048ce73b1SAl Viro { 51328de528SAl Viro return p->type == NULL; 5248ce73b1SAl Viro } 5348ce73b1SAl Viro 5431d921c7SDavid Howells static const struct fs_parameter_spec *fs_lookup_key( 55d7167b14SAl Viro const struct fs_parameter_spec *desc, 5648ce73b1SAl Viro struct fs_parameter *param, bool *negated) 5731d921c7SDavid Howells { 5848ce73b1SAl Viro const struct fs_parameter_spec *p, *other = NULL; 5948ce73b1SAl Viro const char *name = param->key; 6048ce73b1SAl Viro bool want_flag = param->type == fs_value_is_flag; 6131d921c7SDavid Howells 6248ce73b1SAl Viro *negated = false; 6348ce73b1SAl Viro for (p = desc; p->name; p++) { 6448ce73b1SAl Viro if (strcmp(p->name, name) != 0) 6548ce73b1SAl Viro continue; 6648ce73b1SAl Viro if (likely(is_flag(p) == want_flag)) 6731d921c7SDavid Howells return p; 6848ce73b1SAl Viro other = p; 6948ce73b1SAl Viro } 7048ce73b1SAl Viro if (want_flag) { 7148ce73b1SAl Viro if (name[0] == 'n' && name[1] == 'o' && name[2]) { 7248ce73b1SAl Viro for (p = desc; p->name; p++) { 7348ce73b1SAl Viro if (strcmp(p->name, name + 2) != 0) 7448ce73b1SAl Viro continue; 7548ce73b1SAl Viro if (!(p->flags & fs_param_neg_with_no)) 7648ce73b1SAl Viro continue; 7748ce73b1SAl Viro *negated = true; 7848ce73b1SAl Viro return p; 7948ce73b1SAl Viro } 8048ce73b1SAl Viro } 8148ce73b1SAl Viro } 8248ce73b1SAl Viro return other; 8331d921c7SDavid Howells } 8431d921c7SDavid Howells 8531d921c7SDavid Howells /* 8631d921c7SDavid Howells * fs_parse - Parse a filesystem configuration parameter 8731d921c7SDavid Howells * @fc: The filesystem context to log errors through. 8831d921c7SDavid Howells * @desc: The parameter description to use. 8931d921c7SDavid Howells * @param: The parameter. 9031d921c7SDavid Howells * @result: Where to place the result of the parse 9131d921c7SDavid Howells * 9231d921c7SDavid Howells * Parse a filesystem configuration parameter and attempt a conversion for a 9331d921c7SDavid Howells * simple parameter for which this is requested. If successful, the determined 9431d921c7SDavid Howells * parameter ID is placed into @result->key, the desired type is indicated in 9531d921c7SDavid Howells * @result->t and any converted value is placed into an appropriate member of 9631d921c7SDavid Howells * the union in @result. 9731d921c7SDavid Howells * 9831d921c7SDavid Howells * The function returns the parameter number if the parameter was matched, 9931d921c7SDavid Howells * -ENOPARAM if it wasn't matched and @desc->ignore_unknown indicated that 10031d921c7SDavid Howells * unknown parameters are okay and -EINVAL if there was a conversion issue or 10131d921c7SDavid Howells * the parameter wasn't recognised and unknowns aren't okay. 10231d921c7SDavid Howells */ 1037f5d3814SAl Viro int __fs_parse(struct p_log *log, 104d7167b14SAl Viro const struct fs_parameter_spec *desc, 10531d921c7SDavid Howells struct fs_parameter *param, 10631d921c7SDavid Howells struct fs_parse_result *result) 10731d921c7SDavid Howells { 10831d921c7SDavid Howells const struct fs_parameter_spec *p; 10931d921c7SDavid Howells 11031d921c7SDavid Howells result->uint_64 = 0; 11131d921c7SDavid Howells 11248ce73b1SAl Viro p = fs_lookup_key(desc, param, &result->negated); 11331d921c7SDavid Howells if (!p) 11448ce73b1SAl Viro return -ENOPARAM; 11531d921c7SDavid Howells 11631d921c7SDavid Howells if (p->flags & fs_param_deprecated) 1177f5d3814SAl Viro warn_plog(log, "Deprecated parameter '%s'", param->key); 11831d921c7SDavid Howells 11931d921c7SDavid Howells /* Try to turn the type we were given into the type desired by the 12031d921c7SDavid Howells * parameter and give an error if we can't. 12131d921c7SDavid Howells */ 122328de528SAl Viro if (is_flag(p)) { 1230f89589aSAl Viro if (param->type != fs_value_is_flag) 1247f5d3814SAl Viro return inval_plog(log, "Unexpected value for '%s'", 1257f5d3814SAl Viro param->key); 12648ce73b1SAl Viro result->boolean = !result->negated; 127328de528SAl Viro } else { 128328de528SAl Viro int ret = p->type(log, p, param, result); 129328de528SAl Viro if (ret) 130328de528SAl Viro return ret; 13131d921c7SDavid Howells } 13231d921c7SDavid Howells return p->opt; 13331d921c7SDavid Howells } 1347f5d3814SAl Viro EXPORT_SYMBOL(__fs_parse); 1357f5d3814SAl Viro 13631d921c7SDavid Howells /** 13731d921c7SDavid Howells * fs_lookup_param - Look up a path referred to by a parameter 13831d921c7SDavid Howells * @fc: The filesystem context to log errors through. 13931d921c7SDavid Howells * @param: The parameter. 14031d921c7SDavid Howells * @want_bdev: T if want a blockdev 141*e3ea75eeSLukas Czerner * @flags: Pathwalk flags passed to filename_lookup() 14231d921c7SDavid Howells * @_path: The result of the lookup 14331d921c7SDavid Howells */ 14431d921c7SDavid Howells int fs_lookup_param(struct fs_context *fc, 14531d921c7SDavid Howells struct fs_parameter *param, 14631d921c7SDavid Howells bool want_bdev, 147*e3ea75eeSLukas Czerner unsigned int flags, 14831d921c7SDavid Howells struct path *_path) 14931d921c7SDavid Howells { 15031d921c7SDavid Howells struct filename *f; 15131d921c7SDavid Howells bool put_f; 15231d921c7SDavid Howells int ret; 15331d921c7SDavid Howells 15431d921c7SDavid Howells switch (param->type) { 15531d921c7SDavid Howells case fs_value_is_string: 15631d921c7SDavid Howells f = getname_kernel(param->string); 15731d921c7SDavid Howells if (IS_ERR(f)) 15831d921c7SDavid Howells return PTR_ERR(f); 15931d921c7SDavid Howells put_f = true; 16031d921c7SDavid Howells break; 16131d921c7SDavid Howells case fs_value_is_filename: 16231d921c7SDavid Howells f = param->name; 16331d921c7SDavid Howells put_f = false; 16431d921c7SDavid Howells break; 16531d921c7SDavid Howells default: 16631d921c7SDavid Howells return invalf(fc, "%s: not usable as path", param->key); 16731d921c7SDavid Howells } 16831d921c7SDavid Howells 16931d921c7SDavid Howells ret = filename_lookup(param->dirfd, f, flags, _path, NULL); 17031d921c7SDavid Howells if (ret < 0) { 17131d921c7SDavid Howells errorf(fc, "%s: Lookup failure for '%s'", param->key, f->name); 17231d921c7SDavid Howells goto out; 17331d921c7SDavid Howells } 17431d921c7SDavid Howells 17531d921c7SDavid Howells if (want_bdev && 17631d921c7SDavid Howells !S_ISBLK(d_backing_inode(_path->dentry)->i_mode)) { 17731d921c7SDavid Howells path_put(_path); 17831d921c7SDavid Howells _path->dentry = NULL; 17931d921c7SDavid Howells _path->mnt = NULL; 18031d921c7SDavid Howells errorf(fc, "%s: Non-blockdev passed as '%s'", 18131d921c7SDavid Howells param->key, f->name); 18231d921c7SDavid Howells ret = -ENOTBLK; 18331d921c7SDavid Howells } 18431d921c7SDavid Howells 18531d921c7SDavid Howells out: 18631d921c7SDavid Howells if (put_f) 18731d921c7SDavid Howells putname(f); 18831d921c7SDavid Howells return ret; 18931d921c7SDavid Howells } 19031d921c7SDavid Howells EXPORT_SYMBOL(fs_lookup_param); 19131d921c7SDavid Howells 19297383c74SLuo Jiaxing static int fs_param_bad_value(struct p_log *log, struct fs_parameter *param) 193328de528SAl Viro { 194328de528SAl Viro return inval_plog(log, "Bad value for '%s'", param->key); 195328de528SAl Viro } 196328de528SAl Viro 197328de528SAl Viro int fs_param_is_bool(struct p_log *log, const struct fs_parameter_spec *p, 198328de528SAl Viro struct fs_parameter *param, struct fs_parse_result *result) 199328de528SAl Viro { 200328de528SAl Viro int b; 201328de528SAl Viro if (param->type != fs_value_is_string) 202328de528SAl Viro return fs_param_bad_value(log, param); 2036abfaaf1SLukas Czerner if (!*param->string && (p->flags & fs_param_can_be_empty)) 2046abfaaf1SLukas Czerner return 0; 205328de528SAl Viro b = lookup_constant(bool_names, param->string, -1); 206328de528SAl Viro if (b == -1) 207328de528SAl Viro return fs_param_bad_value(log, param); 208328de528SAl Viro result->boolean = b; 209328de528SAl Viro return 0; 210328de528SAl Viro } 211328de528SAl Viro EXPORT_SYMBOL(fs_param_is_bool); 212328de528SAl Viro 213328de528SAl Viro int fs_param_is_u32(struct p_log *log, const struct fs_parameter_spec *p, 214328de528SAl Viro struct fs_parameter *param, struct fs_parse_result *result) 215328de528SAl Viro { 216328de528SAl Viro int base = (unsigned long)p->data; 2176abfaaf1SLukas Czerner if (param->type != fs_value_is_string) 2186abfaaf1SLukas Czerner return fs_param_bad_value(log, param); 2196abfaaf1SLukas Czerner if (!*param->string && (p->flags & fs_param_can_be_empty)) 2206abfaaf1SLukas Czerner return 0; 2216abfaaf1SLukas Czerner if (kstrtouint(param->string, base, &result->uint_32) < 0) 222328de528SAl Viro return fs_param_bad_value(log, param); 223328de528SAl Viro return 0; 224328de528SAl Viro } 225328de528SAl Viro EXPORT_SYMBOL(fs_param_is_u32); 226328de528SAl Viro 227328de528SAl Viro int fs_param_is_s32(struct p_log *log, const struct fs_parameter_spec *p, 228328de528SAl Viro struct fs_parameter *param, struct fs_parse_result *result) 229328de528SAl Viro { 2306abfaaf1SLukas Czerner if (param->type != fs_value_is_string) 2316abfaaf1SLukas Czerner return fs_param_bad_value(log, param); 2326abfaaf1SLukas Czerner if (!*param->string && (p->flags & fs_param_can_be_empty)) 2336abfaaf1SLukas Czerner return 0; 2346abfaaf1SLukas Czerner if (kstrtoint(param->string, 0, &result->int_32) < 0) 235328de528SAl Viro return fs_param_bad_value(log, param); 236328de528SAl Viro return 0; 237328de528SAl Viro } 238328de528SAl Viro EXPORT_SYMBOL(fs_param_is_s32); 239328de528SAl Viro 240328de528SAl Viro int fs_param_is_u64(struct p_log *log, const struct fs_parameter_spec *p, 241328de528SAl Viro struct fs_parameter *param, struct fs_parse_result *result) 242328de528SAl Viro { 2436abfaaf1SLukas Czerner if (param->type != fs_value_is_string) 2446abfaaf1SLukas Czerner return fs_param_bad_value(log, param); 2456abfaaf1SLukas Czerner if (!*param->string && (p->flags & fs_param_can_be_empty)) 2466abfaaf1SLukas Czerner return 0; 2476abfaaf1SLukas Czerner if (kstrtoull(param->string, 0, &result->uint_64) < 0) 248328de528SAl Viro return fs_param_bad_value(log, param); 249328de528SAl Viro return 0; 250328de528SAl Viro } 251328de528SAl Viro EXPORT_SYMBOL(fs_param_is_u64); 252328de528SAl Viro 253328de528SAl Viro int fs_param_is_enum(struct p_log *log, const struct fs_parameter_spec *p, 254328de528SAl Viro struct fs_parameter *param, struct fs_parse_result *result) 255328de528SAl Viro { 256328de528SAl Viro const struct constant_table *c; 257328de528SAl Viro if (param->type != fs_value_is_string) 258328de528SAl Viro return fs_param_bad_value(log, param); 2596abfaaf1SLukas Czerner if (!*param->string && (p->flags & fs_param_can_be_empty)) 2606abfaaf1SLukas Czerner return 0; 261328de528SAl Viro c = __lookup_constant(p->data, param->string); 262328de528SAl Viro if (!c) 263328de528SAl Viro return fs_param_bad_value(log, param); 264328de528SAl Viro result->uint_32 = c->value; 265328de528SAl Viro return 0; 266328de528SAl Viro } 267328de528SAl Viro EXPORT_SYMBOL(fs_param_is_enum); 268328de528SAl Viro 269328de528SAl Viro int fs_param_is_string(struct p_log *log, const struct fs_parameter_spec *p, 270328de528SAl Viro struct fs_parameter *param, struct fs_parse_result *result) 271328de528SAl Viro { 2726abfaaf1SLukas Czerner if (param->type != fs_value_is_string || 2736abfaaf1SLukas Czerner (!*param->string && !(p->flags & fs_param_can_be_empty))) 274328de528SAl Viro return fs_param_bad_value(log, param); 275328de528SAl Viro return 0; 276328de528SAl Viro } 277328de528SAl Viro EXPORT_SYMBOL(fs_param_is_string); 278328de528SAl Viro 279328de528SAl Viro int fs_param_is_blob(struct p_log *log, const struct fs_parameter_spec *p, 280328de528SAl Viro struct fs_parameter *param, struct fs_parse_result *result) 281328de528SAl Viro { 282328de528SAl Viro if (param->type != fs_value_is_blob) 283328de528SAl Viro return fs_param_bad_value(log, param); 284328de528SAl Viro return 0; 285328de528SAl Viro } 286328de528SAl Viro EXPORT_SYMBOL(fs_param_is_blob); 287328de528SAl Viro 288328de528SAl Viro int fs_param_is_fd(struct p_log *log, const struct fs_parameter_spec *p, 289328de528SAl Viro struct fs_parameter *param, struct fs_parse_result *result) 290328de528SAl Viro { 291328de528SAl Viro switch (param->type) { 292328de528SAl Viro case fs_value_is_string: 2936abfaaf1SLukas Czerner if ((!*param->string && !(p->flags & fs_param_can_be_empty)) || 2946abfaaf1SLukas Czerner kstrtouint(param->string, 0, &result->uint_32) < 0) 295328de528SAl Viro break; 296328de528SAl Viro if (result->uint_32 <= INT_MAX) 297328de528SAl Viro return 0; 298328de528SAl Viro break; 299328de528SAl Viro case fs_value_is_file: 300328de528SAl Viro result->uint_32 = param->dirfd; 301328de528SAl Viro if (result->uint_32 <= INT_MAX) 302328de528SAl Viro return 0; 303328de528SAl Viro break; 304328de528SAl Viro default: 305328de528SAl Viro break; 306328de528SAl Viro } 307328de528SAl Viro return fs_param_bad_value(log, param); 308328de528SAl Viro } 309328de528SAl Viro EXPORT_SYMBOL(fs_param_is_fd); 310328de528SAl Viro 311328de528SAl Viro int fs_param_is_blockdev(struct p_log *log, const struct fs_parameter_spec *p, 312328de528SAl Viro struct fs_parameter *param, struct fs_parse_result *result) 313328de528SAl Viro { 314328de528SAl Viro return 0; 315328de528SAl Viro } 316328de528SAl Viro EXPORT_SYMBOL(fs_param_is_blockdev); 317328de528SAl Viro 318328de528SAl Viro int fs_param_is_path(struct p_log *log, const struct fs_parameter_spec *p, 319328de528SAl Viro struct fs_parameter *param, struct fs_parse_result *result) 320328de528SAl Viro { 321328de528SAl Viro return 0; 322328de528SAl Viro } 323328de528SAl Viro EXPORT_SYMBOL(fs_param_is_path); 324328de528SAl Viro 32531d921c7SDavid Howells #ifdef CONFIG_VALIDATE_FS_PARSER 32631d921c7SDavid Howells /** 32731d921c7SDavid Howells * validate_constant_table - Validate a constant table 32831d921c7SDavid Howells * @tbl: The constant table to validate. 32931d921c7SDavid Howells * @tbl_size: The size of the table. 33031d921c7SDavid Howells * @low: The lowest permissible value. 33131d921c7SDavid Howells * @high: The highest permissible value. 33231d921c7SDavid Howells * @special: One special permissible value outside of the range. 33331d921c7SDavid Howells */ 33431d921c7SDavid Howells bool validate_constant_table(const struct constant_table *tbl, size_t tbl_size, 33531d921c7SDavid Howells int low, int high, int special) 33631d921c7SDavid Howells { 33731d921c7SDavid Howells size_t i; 33831d921c7SDavid Howells bool good = true; 33931d921c7SDavid Howells 34031d921c7SDavid Howells if (tbl_size == 0) { 34131d921c7SDavid Howells pr_warn("VALIDATE C-TBL: Empty\n"); 34231d921c7SDavid Howells return true; 34331d921c7SDavid Howells } 34431d921c7SDavid Howells 34531d921c7SDavid Howells for (i = 0; i < tbl_size; i++) { 34631d921c7SDavid Howells if (!tbl[i].name) { 34731d921c7SDavid Howells pr_err("VALIDATE C-TBL[%zu]: Null\n", i); 34831d921c7SDavid Howells good = false; 34931d921c7SDavid Howells } else if (i > 0 && tbl[i - 1].name) { 35031d921c7SDavid Howells int c = strcmp(tbl[i-1].name, tbl[i].name); 35131d921c7SDavid Howells 35231d921c7SDavid Howells if (c == 0) { 35331d921c7SDavid Howells pr_err("VALIDATE C-TBL[%zu]: Duplicate %s\n", 35431d921c7SDavid Howells i, tbl[i].name); 35531d921c7SDavid Howells good = false; 35631d921c7SDavid Howells } 35731d921c7SDavid Howells if (c > 0) { 35831d921c7SDavid Howells pr_err("VALIDATE C-TBL[%zu]: Missorted %s>=%s\n", 35931d921c7SDavid Howells i, tbl[i-1].name, tbl[i].name); 36031d921c7SDavid Howells good = false; 36131d921c7SDavid Howells } 36231d921c7SDavid Howells } 36331d921c7SDavid Howells 36431d921c7SDavid Howells if (tbl[i].value != special && 36531d921c7SDavid Howells (tbl[i].value < low || tbl[i].value > high)) { 36631d921c7SDavid Howells pr_err("VALIDATE C-TBL[%zu]: %s->%d const out of range (%d-%d)\n", 36731d921c7SDavid Howells i, tbl[i].name, tbl[i].value, low, high); 36831d921c7SDavid Howells good = false; 36931d921c7SDavid Howells } 37031d921c7SDavid Howells } 37131d921c7SDavid Howells 37231d921c7SDavid Howells return good; 37331d921c7SDavid Howells } 37431d921c7SDavid Howells 37531d921c7SDavid Howells /** 37631d921c7SDavid Howells * fs_validate_description - Validate a parameter description 37721ae3ad1SRandy Dunlap * @name: The parameter name to search for. 37831d921c7SDavid Howells * @desc: The parameter description to validate. 37931d921c7SDavid Howells */ 38096cafb9cSEric Sandeen bool fs_validate_description(const char *name, 381d7167b14SAl Viro const struct fs_parameter_spec *desc) 38231d921c7SDavid Howells { 38331d921c7SDavid Howells const struct fs_parameter_spec *param, *p2; 3842710c957SAl Viro bool good = true; 38531d921c7SDavid Howells 386d7167b14SAl Viro for (param = desc; param->name; param++) { 38731d921c7SDavid Howells /* Check for duplicate parameter names */ 388d7167b14SAl Viro for (p2 = desc; p2 < param; p2++) { 38931d921c7SDavid Howells if (strcmp(param->name, p2->name) == 0) { 39048ce73b1SAl Viro if (is_flag(param) != is_flag(p2)) 39148ce73b1SAl Viro continue; 39231d921c7SDavid Howells pr_err("VALIDATE %s: PARAM[%s]: Duplicate\n", 39331d921c7SDavid Howells name, param->name); 39431d921c7SDavid Howells good = false; 39531d921c7SDavid Howells } 39631d921c7SDavid Howells } 39731d921c7SDavid Howells } 39831d921c7SDavid Howells return good; 39931d921c7SDavid Howells } 40031d921c7SDavid Howells #endif /* CONFIG_VALIDATE_FS_PARSER */ 401