Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

vfs: Implement a filesystem superblock creation/configuration context

[AV - unfuck kern_mount_data(); we want non-NULL ->mnt_ns on long-living
mounts]
[AV - reordering fs/namespace.c is badly overdue, but let's keep it
separate from that series]
[AV - drop simple_pin_fs() change]
[AV - clean vfs_kern_mount() failure exits up]

Implement a filesystem context concept to be used during superblock
creation for mount and superblock reconfiguration for remount.

The mounting procedure then becomes:

(1) Allocate new fs_context context.

(2) Configure the context.

(3) Create superblock.

(4) Query the superblock.

(5) Create a mount for the superblock.

(6) Destroy the context.

Rather than calling fs_type->mount(), an fs_context struct is created and
fs_type->init_fs_context() is called to set it up. Pointers exist for the
filesystem and LSM to hang their private data off.

A set of operations has to be set by ->init_fs_context() to provide
freeing, duplication, option parsing, binary data parsing, validation,
mounting and superblock filling.

Legacy filesystems are supported by the provision of a set of legacy
fs_context operations that build up a list of mount options and then invoke
fs_type->mount() from within the fs_context ->get_tree() operation. This
allows all filesystems to be accessed using fs_context.

It should be noted that, whilst this patch adds a lot of lines of code,
there is quite a bit of duplication with existing code that can be
eliminated should all filesystems be converted over.

Signed-off-by: David Howells <dhowells@redhat.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

authored by

David Howells and committed by
Al Viro
3e1aeb00 846e5662

+319 -17
+4
fs/filesystems.c
··· 16 16 #include <linux/module.h> 17 17 #include <linux/slab.h> 18 18 #include <linux/uaccess.h> 19 + #include <linux/fs_parser.h> 19 20 20 21 /* 21 22 * Handling of filesystem drivers list. ··· 73 72 { 74 73 int res = 0; 75 74 struct file_system_type ** p; 75 + 76 + if (fs->parameters && !fs_validate_description(fs->parameters)) 77 + return -EINVAL; 76 78 77 79 BUG_ON(strchr(fs->name, '.')); 78 80 if (fs->next)
+299 -1
fs/fs_context.c
··· 12 12 13 13 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 14 14 #include <linux/fs_context.h> 15 + #include <linux/fs_parser.h> 15 16 #include <linux/fs.h> 16 17 #include <linux/mount.h> 17 18 #include <linux/nsproxy.h> ··· 26 25 #include "mount.h" 27 26 #include "internal.h" 28 27 28 + enum legacy_fs_param { 29 + LEGACY_FS_UNSET_PARAMS, 30 + LEGACY_FS_MONOLITHIC_PARAMS, 31 + LEGACY_FS_INDIVIDUAL_PARAMS, 32 + }; 33 + 29 34 struct legacy_fs_context { 30 35 char *legacy_data; /* Data page for legacy filesystems */ 31 36 size_t data_size; 37 + enum legacy_fs_param param_type; 32 38 }; 33 39 34 40 static int legacy_init_fs_context(struct fs_context *fc); 41 + 42 + static const struct constant_table common_set_sb_flag[] = { 43 + { "dirsync", SB_DIRSYNC }, 44 + { "lazytime", SB_LAZYTIME }, 45 + { "mand", SB_MANDLOCK }, 46 + { "posixacl", SB_POSIXACL }, 47 + { "ro", SB_RDONLY }, 48 + { "sync", SB_SYNCHRONOUS }, 49 + }; 50 + 51 + static const struct constant_table common_clear_sb_flag[] = { 52 + { "async", SB_SYNCHRONOUS }, 53 + { "nolazytime", SB_LAZYTIME }, 54 + { "nomand", SB_MANDLOCK }, 55 + { "rw", SB_RDONLY }, 56 + { "silent", SB_SILENT }, 57 + }; 58 + 59 + static const char *const forbidden_sb_flag[] = { 60 + "bind", 61 + "dev", 62 + "exec", 63 + "move", 64 + "noatime", 65 + "nodev", 66 + "nodiratime", 67 + "noexec", 68 + "norelatime", 69 + "nostrictatime", 70 + "nosuid", 71 + "private", 72 + "rec", 73 + "relatime", 74 + "remount", 75 + "shared", 76 + "slave", 77 + "strictatime", 78 + "suid", 79 + "unbindable", 80 + }; 81 + 82 + /* 83 + * Check for a common mount option that manipulates s_flags. 84 + */ 85 + static int vfs_parse_sb_flag(struct fs_context *fc, const char *key) 86 + { 87 + unsigned int token; 88 + unsigned int i; 89 + 90 + for (i = 0; i < ARRAY_SIZE(forbidden_sb_flag); i++) 91 + if (strcmp(key, forbidden_sb_flag[i]) == 0) 92 + return -EINVAL; 93 + 94 + token = lookup_constant(common_set_sb_flag, key, 0); 95 + if (token) { 96 + fc->sb_flags |= token; 97 + fc->sb_flags_mask |= token; 98 + return 0; 99 + } 100 + 101 + token = lookup_constant(common_clear_sb_flag, key, 0); 102 + if (token) { 103 + fc->sb_flags &= ~token; 104 + fc->sb_flags_mask |= token; 105 + return 0; 106 + } 107 + 108 + return -ENOPARAM; 109 + } 110 + 111 + /** 112 + * vfs_parse_fs_param - Add a single parameter to a superblock config 113 + * @fc: The filesystem context to modify 114 + * @param: The parameter 115 + * 116 + * A single mount option in string form is applied to the filesystem context 117 + * being set up. Certain standard options (for example "ro") are translated 118 + * into flag bits without going to the filesystem. The active security module 119 + * is allowed to observe and poach options. Any other options are passed over 120 + * to the filesystem to parse. 121 + * 122 + * This may be called multiple times for a context. 123 + * 124 + * Returns 0 on success and a negative error code on failure. In the event of 125 + * failure, supplementary error information may have been set. 126 + */ 127 + int vfs_parse_fs_param(struct fs_context *fc, struct fs_parameter *param) 128 + { 129 + int ret; 130 + 131 + if (!param->key) 132 + return invalf(fc, "Unnamed parameter\n"); 133 + 134 + ret = vfs_parse_sb_flag(fc, param->key); 135 + if (ret != -ENOPARAM) 136 + return ret; 137 + 138 + ret = security_fs_context_parse_param(fc, param); 139 + if (ret != -ENOPARAM) 140 + /* Param belongs to the LSM or is disallowed by the LSM; so 141 + * don't pass to the FS. 142 + */ 143 + return ret; 144 + 145 + if (fc->ops->parse_param) { 146 + ret = fc->ops->parse_param(fc, param); 147 + if (ret != -ENOPARAM) 148 + return ret; 149 + } 150 + 151 + /* If the filesystem doesn't take any arguments, give it the 152 + * default handling of source. 153 + */ 154 + if (strcmp(param->key, "source") == 0) { 155 + if (param->type != fs_value_is_string) 156 + return invalf(fc, "VFS: Non-string source"); 157 + if (fc->source) 158 + return invalf(fc, "VFS: Multiple sources"); 159 + fc->source = param->string; 160 + param->string = NULL; 161 + return 0; 162 + } 163 + 164 + return invalf(fc, "%s: Unknown parameter '%s'", 165 + fc->fs_type->name, param->key); 166 + } 167 + EXPORT_SYMBOL(vfs_parse_fs_param); 168 + 169 + /** 170 + * vfs_parse_fs_string - Convenience function to just parse a string. 171 + */ 172 + int vfs_parse_fs_string(struct fs_context *fc, const char *key, 173 + const char *value, size_t v_size) 174 + { 175 + int ret; 176 + 177 + struct fs_parameter param = { 178 + .key = key, 179 + .type = fs_value_is_string, 180 + .size = v_size, 181 + }; 182 + 183 + if (v_size > 0) { 184 + param.string = kmemdup_nul(value, v_size, GFP_KERNEL); 185 + if (!param.string) 186 + return -ENOMEM; 187 + } 188 + 189 + ret = vfs_parse_fs_param(fc, &param); 190 + kfree(param.string); 191 + return ret; 192 + } 193 + EXPORT_SYMBOL(vfs_parse_fs_string); 194 + 195 + /** 196 + * generic_parse_monolithic - Parse key[=val][,key[=val]]* mount data 197 + * @ctx: The superblock configuration to fill in. 198 + * @data: The data to parse 199 + * 200 + * Parse a blob of data that's in key[=val][,key[=val]]* form. This can be 201 + * called from the ->monolithic_mount_data() fs_context operation. 202 + * 203 + * Returns 0 on success or the error returned by the ->parse_option() fs_context 204 + * operation on failure. 205 + */ 206 + int generic_parse_monolithic(struct fs_context *fc, void *data) 207 + { 208 + char *options = data, *key; 209 + int ret = 0; 210 + 211 + if (!options) 212 + return 0; 213 + 214 + ret = security_sb_eat_lsm_opts(options, &fc->security); 215 + if (ret) 216 + return ret; 217 + 218 + while ((key = strsep(&options, ",")) != NULL) { 219 + if (*key) { 220 + size_t v_len = 0; 221 + char *value = strchr(key, '='); 222 + 223 + if (value) { 224 + if (value == key) 225 + continue; 226 + *value++ = 0; 227 + v_len = strlen(value); 228 + } 229 + ret = vfs_parse_fs_string(fc, key, value, v_len); 230 + if (ret < 0) 231 + break; 232 + } 233 + } 234 + 235 + return ret; 236 + } 237 + EXPORT_SYMBOL(generic_parse_monolithic); 35 238 36 239 /** 37 240 * alloc_fs_context - Create a filesystem context. ··· 371 166 */ 372 167 static void legacy_fs_context_free(struct fs_context *fc) 373 168 { 374 - kfree(fc->fs_private); 169 + struct legacy_fs_context *ctx = fc->fs_private; 170 + 171 + if (ctx) { 172 + if (ctx->param_type == LEGACY_FS_INDIVIDUAL_PARAMS) 173 + kfree(ctx->legacy_data); 174 + kfree(ctx); 175 + } 176 + } 177 + 178 + /* 179 + * Add a parameter to a legacy config. We build up a comma-separated list of 180 + * options. 181 + */ 182 + static int legacy_parse_param(struct fs_context *fc, struct fs_parameter *param) 183 + { 184 + struct legacy_fs_context *ctx = fc->fs_private; 185 + unsigned int size = ctx->data_size; 186 + size_t len = 0; 187 + 188 + if (strcmp(param->key, "source") == 0) { 189 + if (param->type != fs_value_is_string) 190 + return invalf(fc, "VFS: Legacy: Non-string source"); 191 + if (fc->source) 192 + return invalf(fc, "VFS: Legacy: Multiple sources"); 193 + fc->source = param->string; 194 + param->string = NULL; 195 + return 0; 196 + } 197 + 198 + if ((fc->fs_type->fs_flags & FS_HAS_SUBTYPE) && 199 + strcmp(param->key, "subtype") == 0) { 200 + if (param->type != fs_value_is_string) 201 + return invalf(fc, "VFS: Legacy: Non-string subtype"); 202 + if (fc->subtype) 203 + return invalf(fc, "VFS: Legacy: Multiple subtype"); 204 + fc->subtype = param->string; 205 + param->string = NULL; 206 + return 0; 207 + } 208 + 209 + if (ctx->param_type == LEGACY_FS_MONOLITHIC_PARAMS) 210 + return invalf(fc, "VFS: Legacy: Can't mix monolithic and individual options"); 211 + 212 + switch (param->type) { 213 + case fs_value_is_string: 214 + len = 1 + param->size; 215 + /* Fall through */ 216 + case fs_value_is_flag: 217 + len += strlen(param->key); 218 + break; 219 + default: 220 + return invalf(fc, "VFS: Legacy: Parameter type for '%s' not supported", 221 + param->key); 222 + } 223 + 224 + if (len > PAGE_SIZE - 2 - size) 225 + return invalf(fc, "VFS: Legacy: Cumulative options too large"); 226 + if (strchr(param->key, ',') || 227 + (param->type == fs_value_is_string && 228 + memchr(param->string, ',', param->size))) 229 + return invalf(fc, "VFS: Legacy: Option '%s' contained comma", 230 + param->key); 231 + if (!ctx->legacy_data) { 232 + ctx->legacy_data = kmalloc(PAGE_SIZE, GFP_KERNEL); 233 + if (!ctx->legacy_data) 234 + return -ENOMEM; 235 + } 236 + 237 + ctx->legacy_data[size++] = ','; 238 + len = strlen(param->key); 239 + memcpy(ctx->legacy_data + size, param->key, len); 240 + size += len; 241 + if (param->type == fs_value_is_string) { 242 + ctx->legacy_data[size++] = '='; 243 + memcpy(ctx->legacy_data + size, param->string, param->size); 244 + size += param->size; 245 + } 246 + ctx->legacy_data[size] = '\0'; 247 + ctx->data_size = size; 248 + ctx->param_type = LEGACY_FS_INDIVIDUAL_PARAMS; 249 + return 0; 375 250 } 376 251 377 252 /* ··· 460 175 static int legacy_parse_monolithic(struct fs_context *fc, void *data) 461 176 { 462 177 struct legacy_fs_context *ctx = fc->fs_private; 178 + 179 + if (ctx->param_type != LEGACY_FS_UNSET_PARAMS) { 180 + pr_warn("VFS: Can't mix monolithic and individual options\n"); 181 + return -EINVAL; 182 + } 183 + 463 184 ctx->legacy_data = data; 185 + ctx->param_type = LEGACY_FS_MONOLITHIC_PARAMS; 464 186 if (!ctx->legacy_data) 465 187 return 0; 188 + 466 189 if (fc->fs_type->fs_flags & FS_BINARY_MOUNTDATA) 467 190 return 0; 468 191 return security_sb_eat_lsm_opts(ctx->legacy_data, &fc->security); ··· 514 221 515 222 const struct fs_context_operations legacy_fs_context_ops = { 516 223 .free = legacy_fs_context_free, 224 + .parse_param = legacy_parse_param, 517 225 .parse_monolithic = legacy_parse_monolithic, 518 226 .get_tree = legacy_get_tree, 519 227 .reconfigure = legacy_reconfigure, ··· 536 242 int parse_monolithic_mount_data(struct fs_context *fc, void *data) 537 243 { 538 244 int (*monolithic_mount_data)(struct fs_context *, void *); 245 + 539 246 monolithic_mount_data = fc->ops->parse_monolithic; 247 + if (!monolithic_mount_data) 248 + monolithic_mount_data = generic_parse_monolithic; 249 + 540 250 return monolithic_mount_data(fc, data); 541 251 }
+9 -16
fs/namespace.c
··· 997 997 int ret = 0; 998 998 999 999 if (!type) 1000 - return ERR_PTR(-ENODEV); 1000 + return ERR_PTR(-EINVAL); 1001 1001 1002 1002 fc = fs_context_for_mount(type, flags); 1003 1003 if (IS_ERR(fc)) 1004 1004 return ERR_CAST(fc); 1005 1005 1006 - if (name) { 1007 - fc->source = kstrdup(name, GFP_KERNEL); 1008 - if (!fc->source) 1009 - ret = -ENOMEM; 1010 - } 1006 + if (name) 1007 + ret = vfs_parse_fs_string(fc, "source", 1008 + name, strlen(name)); 1011 1009 if (!ret) 1012 1010 ret = parse_monolithic_mount_data(fc, data); 1013 1011 if (!ret) ··· 2609 2611 if (IS_ERR(fc)) 2610 2612 return PTR_ERR(fc); 2611 2613 2612 - if (subtype) { 2613 - fc->subtype = kstrdup(subtype, GFP_KERNEL); 2614 - if (!fc->subtype) 2615 - err = -ENOMEM; 2616 - } 2617 - if (!err && name) { 2618 - fc->source = kstrdup(name, GFP_KERNEL); 2619 - if (!fc->source) 2620 - err = -ENOMEM; 2621 - } 2614 + if (subtype) 2615 + err = vfs_parse_fs_string(fc, "subtype", 2616 + subtype, strlen(subtype)); 2617 + if (!err && name) 2618 + err = vfs_parse_fs_string(fc, "source", name, strlen(name)); 2622 2619 if (!err) 2623 2620 err = parse_monolithic_mount_data(fc, data); 2624 2621 if (!err)
+2
include/linux/fs.h
··· 62 62 struct fscrypt_info; 63 63 struct fscrypt_operations; 64 64 struct fs_context; 65 + struct fs_parameter_description; 65 66 66 67 extern void __init inode_init(void); 67 68 extern void __init inode_init_early(void); ··· 2176 2175 #define FS_USERNS_MOUNT 8 /* Can be mounted by userns root */ 2177 2176 #define FS_RENAME_DOES_D_MOVE 32768 /* FS will handle d_move() during rename() internally. */ 2178 2177 int (*init_fs_context)(struct fs_context *); 2178 + const struct fs_parameter_description *parameters; 2179 2179 struct dentry *(*mount) (struct file_system_type *, int, 2180 2180 const char *, void *); 2181 2181 void (*kill_sb) (struct super_block *);
+5
include/linux/fs_context.h
··· 92 92 93 93 struct fs_context_operations { 94 94 void (*free)(struct fs_context *fc); 95 + int (*parse_param)(struct fs_context *fc, struct fs_parameter *param); 95 96 int (*parse_monolithic)(struct fs_context *fc, void *data); 96 97 int (*get_tree)(struct fs_context *fc); 97 98 int (*reconfigure)(struct fs_context *fc); ··· 109 108 extern struct fs_context *fs_context_for_submount(struct file_system_type *fs_type, 110 109 struct dentry *reference); 111 110 111 + extern int vfs_parse_fs_param(struct fs_context *fc, struct fs_parameter *param); 112 + extern int vfs_parse_fs_string(struct fs_context *fc, const char *key, 113 + const char *value, size_t v_size); 114 + extern int generic_parse_monolithic(struct fs_context *fc, void *data); 112 115 extern int vfs_get_tree(struct fs_context *fc); 113 116 extern void put_fs_context(struct fs_context *fc); 114 117