// SPDX-License-Identifier: GPL-2.0-only #include <linux/fs.h>#include <linux/module.h>#include <linux/namei.h>#include <linux/fs_context.h>#include <linux/fs_parser.h>#include <linux/posix_acl_xattr.h>#include <linux/seq_file.h>#include <linux/xattr.h>#include "overlayfs.h"#include "params.h" static bool ovl_redirect_dir_def = IS_ENABLED(CONFIG_OVERLAY_FS_REDIRECT_DIR);module_param_named(redirect_dir, ovl_redirect_dir_def, bool, 0644);MODULE_PARM_DESC(redirect_dir, "Default to on or off for the redirect_dir feature"); static bool ovl_redirect_always_follow = IS_ENABLED(CONFIG_OVERLAY_FS_REDIRECT_ALWAYS_FOLLOW);module_param_named(redirect_always_follow, ovl_redirect_always_follow, bool, 0644);MODULE_PARM_DESC(redirect_always_follow, "Follow redirects even if redirect_dir feature is turned off"); static bool ovl_xino_auto_def = IS_ENABLED(CONFIG_OVERLAY_FS_XINO_AUTO);module_param_named(xino_auto, ovl_xino_auto_def, bool, 0644);MODULE_PARM_DESC(xino_auto, "Auto enable xino feature"); static bool ovl_index_def = IS_ENABLED(CONFIG_OVERLAY_FS_INDEX);module_param_named(index, ovl_index_def, bool, 0644);MODULE_PARM_DESC(index, "Default to on or off for the inodes index feature"); static bool ovl_nfs_export_def = IS_ENABLED(CONFIG_OVERLAY_FS_NFS_EXPORT);module_param_named(nfs_export, ovl_nfs_export_def, bool, 0644);MODULE_PARM_DESC(nfs_export, "Default to on or off for the NFS export feature"); static bool ovl_metacopy_def = IS_ENABLED(CONFIG_OVERLAY_FS_METACOPY);module_param_named(metacopy, ovl_metacopy_def, bool, 0644);MODULE_PARM_DESC(metacopy, "Default to on or off for the metadata only copy up feature"); enum ovl_opt { Opt_lowerdir, Opt_lowerdir_add, Opt_datadir_add, Opt_upperdir, Opt_workdir, Opt_default_permissions, Opt_redirect_dir, Opt_index, Opt_uuid, Opt_nfs_export, Opt_userxattr, Opt_xino, Opt_metacopy, Opt_verity, Opt_fsync, Opt_volatile, Opt_override_creds,}; static const struct constant_table ovl_parameter_bool[] = { { "on", true }, { "off", false }, {}}; static const struct constant_table ovl_parameter_uuid[] = { { "off", OVL_UUID_OFF }, { "null", OVL_UUID_NULL }, { "auto", OVL_UUID_AUTO }, { "on", OVL_UUID_ON }, {}}; static const char *ovl_uuid_mode(struct ovl_config *config){ return ovl_parameter_uuid[config->uuid].name;} static int ovl_uuid_def(void){ return OVL_UUID_AUTO;} static const struct constant_table ovl_parameter_xino[] = { { "off", OVL_XINO_OFF }, { "auto", OVL_XINO_AUTO }, { "on", OVL_XINO_ON }, {}}; const char *ovl_xino_mode(struct ovl_config *config){ return ovl_parameter_xino[config->xino].name;} static int ovl_xino_def(void){ return ovl_xino_auto_def ? OVL_XINO_AUTO : OVL_XINO_OFF;} const struct constant_table ovl_parameter_redirect_dir[] = { { "off", OVL_REDIRECT_OFF }, { "follow", OVL_REDIRECT_FOLLOW }, { "nofollow", OVL_REDIRECT_NOFOLLOW }, { "on", OVL_REDIRECT_ON }, {}}; static const char *ovl_redirect_mode(struct ovl_config *config){ return ovl_parameter_redirect_dir[config->redirect_mode].name;} static int ovl_redirect_mode_def(void){ return ovl_redirect_dir_def ? OVL_REDIRECT_ON : ovl_redirect_always_follow ? OVL_REDIRECT_FOLLOW : OVL_REDIRECT_NOFOLLOW;} static const struct constant_table ovl_parameter_verity[] = { { "off", OVL_VERITY_OFF }, { "on", OVL_VERITY_ON }, { "require", OVL_VERITY_REQUIRE }, {}}; static const char *ovl_verity_mode(struct ovl_config *config){ return ovl_parameter_verity[config->verity_mode].name;} static int ovl_verity_mode_def(void){ return OVL_VERITY_OFF;} static const struct constant_table ovl_parameter_fsync[] = { { "volatile", OVL_FSYNC_VOLATILE }, { "auto", OVL_FSYNC_AUTO }, { "strict", OVL_FSYNC_STRICT }, {}}; static const char *ovl_fsync_mode(struct ovl_config *config){ return ovl_parameter_fsync[config->fsync_mode].name;} static int ovl_fsync_mode_def(void){ return OVL_FSYNC_AUTO;} const struct fs_parameter_spec ovl_parameter_spec[] = { fsparam_string_empty("lowerdir", Opt_lowerdir), fsparam_file_or_string("lowerdir+", Opt_lowerdir_add), fsparam_file_or_string("datadir+", Opt_datadir_add), fsparam_file_or_string("upperdir", Opt_upperdir), fsparam_file_or_string("workdir", Opt_workdir), fsparam_flag("default_permissions", Opt_default_permissions), fsparam_enum("redirect_dir", Opt_redirect_dir, ovl_parameter_redirect_dir), fsparam_enum("index", Opt_index, ovl_parameter_bool), fsparam_enum("uuid", Opt_uuid, ovl_parameter_uuid), fsparam_enum("nfs_export", Opt_nfs_export, ovl_parameter_bool), fsparam_flag("userxattr", Opt_userxattr), fsparam_enum("xino", Opt_xino, ovl_parameter_xino), fsparam_enum("metacopy", Opt_metacopy, ovl_parameter_bool), fsparam_enum("verity", Opt_verity, ovl_parameter_verity), fsparam_enum("fsync", Opt_fsync, ovl_parameter_fsync), fsparam_flag("volatile", Opt_volatile), fsparam_flag_no("override_creds", Opt_override_creds), {}}; static char *ovl_next_opt(char **s){ char *sbegin = *s; char *p; if (sbegin == NULL) return NULL; for (p = sbegin; *p; p++) { if (*p == '\\') { p++; if (!*p) break; } else if (*p == ',') { *p = '\0'; *s = p + 1; return sbegin; } } *s = NULL; return sbegin;} static int ovl_parse_monolithic(struct fs_context *fc, void *data){ return vfs_parse_monolithic_sep(fc, data, ovl_next_opt);} static ssize_t ovl_parse_param_split_lowerdirs(char *str){ ssize_t nr_layers = 1, nr_colons = 0; char *s, *d; for (s = d = str;; s++, d++) { if (*s == '\\') { /* keep esc chars in split lowerdir */ *d++ = *s++; } else if (*s == ':') { bool next_colon = (*(s + 1) == ':'); nr_colons++; if (nr_colons == 2 && next_colon) { pr_err("only single ':' or double '::' sequences of unescaped colons in lowerdir mount option allowed.\n"); return -EINVAL; } /* count layers, not colons */ if (!next_colon) nr_layers++; *d = '\0'; continue; } *d = *s; if (!*s) { /* trailing colons */ if (nr_colons) { pr_err("unescaped trailing colons in lowerdir mount option.\n"); return -EINVAL; } break; } nr_colons = 0; } return nr_layers;} static int ovl_mount_dir_noesc(const char *name, struct path *path){ int err = -EINVAL; if (!*name) { pr_err("empty lowerdir\n"); goto out; } err = kern_path(name, LOOKUP_FOLLOW, path); if (err) { pr_err("failed to resolve '%s': %i\n", name, err); goto out; } return 0; out: return err;} static void ovl_unescape(char *s){ char *d = s; for (;; s++, d++) { if (*s == '\\') s++; *d = *s; if (!*s) break; }} static int ovl_mount_dir(const char *name, struct path *path){ int err = -ENOMEM; char *tmp = kstrdup(name, GFP_KERNEL); if (tmp) { ovl_unescape(tmp); err = ovl_mount_dir_noesc(tmp, path); kfree(tmp); } return err;} static int ovl_mount_dir_check(struct fs_context *fc, const struct path *path, enum ovl_opt layer, const char *name, bool upper){ bool is_casefolded = ovl_dentry_casefolded(path->dentry); struct ovl_fs_context *ctx = fc->fs_private; struct ovl_fs *ofs = fc->s_fs_info; if (!d_is_dir(path->dentry)) return invalfc(fc, "%s is not a directory", name); /* * Allow filesystems that are case-folding capable but deny composing * ovl stack from inconsistent case-folded directories. */ if (!ctx->casefold_set) { ofs->casefold = is_casefolded; ctx->casefold_set = true; } if (ofs->casefold != is_casefolded) { return invalfc(fc, "case-%ssensitive directory on %s is inconsistent", is_casefolded ? "in" : "", name); } if (ovl_dentry_weird(path->dentry)) return invalfc(fc, "filesystem on %s not supported", name); /* * Check whether upper path is read-only here to report failures * early. Don't forget to recheck when the superblock is created * as the mount attributes could change. */ if (upper) { if (path->dentry->d_flags & DCACHE_OP_REAL) return invalfc(fc, "filesystem on %s not supported as upperdir", name); if (__mnt_is_readonly(path->mnt)) return invalfc(fc, "filesystem on %s is read-only", name); } else { if (ctx->lowerdir_all && layer != Opt_lowerdir) return invalfc(fc, "lowerdir+ and datadir+ cannot follow lowerdir"); if (ctx->nr_data && layer == Opt_lowerdir_add) return invalfc(fc, "regular lower layers cannot follow data layers"); if (ctx->nr == OVL_MAX_STACK) return invalfc(fc, "too many lower directories, limit is %d", OVL_MAX_STACK); } return 0;} static int ovl_ctx_realloc_lower(struct fs_context *fc){ struct ovl_fs_context *ctx = fc->fs_private; struct ovl_fs_context_layer *l; size_t nr; if (ctx->nr < ctx->capacity) return 0; nr = min_t(size_t, max(4096 / sizeof(*l), ctx->capacity * 2), OVL_MAX_STACK); l = krealloc_array(ctx->lower, nr, sizeof(*l), GFP_KERNEL_ACCOUNT); if (!l) return -ENOMEM; ctx->lower = l; ctx->capacity = nr; return 0;} static void ovl_add_layer(struct fs_context *fc, enum ovl_opt layer, struct path *path, char **pname){ struct ovl_fs *ofs = fc->s_fs_info; struct ovl_config *config = &ofs->config; struct ovl_fs_context *ctx = fc->fs_private; struct ovl_fs_context_layer *l; switch (layer) { case Opt_workdir: swap(config->workdir, *pname); swap(ctx->work, *path); break; case Opt_upperdir: swap(config->upperdir, *pname); swap(ctx->upper, *path); break; case Opt_datadir_add: ctx->nr_data++; fallthrough; case Opt_lowerdir: fallthrough; case Opt_lowerdir_add: WARN_ON(ctx->nr >= ctx->capacity); l = &ctx->lower[ctx->nr++]; memset(l, 0, sizeof(*l)); swap(l->name, *pname); swap(l->path, *path); break; default: WARN_ON(1); }} static inline bool is_upper_layer(enum ovl_opt layer){ return layer == Opt_upperdir || layer == Opt_workdir;} /* Handle non-file descriptor-based layer options that require path lookup. */static inline int ovl_kern_path(const char *layer_name, struct path *layer_path, enum ovl_opt layer){ int err; switch (layer) { case Opt_upperdir: fallthrough; case Opt_workdir: fallthrough; case Opt_lowerdir: err = ovl_mount_dir(layer_name, layer_path); break; case Opt_lowerdir_add: fallthrough; case Opt_datadir_add: err = ovl_mount_dir_noesc(layer_name, layer_path); break; default: WARN_ON_ONCE(true); err = -EINVAL; } return err;} static int ovl_do_parse_layer(struct fs_context *fc, const char *layer_name, struct path *layer_path, enum ovl_opt layer){ char *name __free(kfree) = kstrdup(layer_name, GFP_KERNEL); bool upper; int err = 0; if (!name) return -ENOMEM; upper = is_upper_layer(layer); err = ovl_mount_dir_check(fc, layer_path, layer, name, upper); if (err) return err; if (!upper) { err = ovl_ctx_realloc_lower(fc); if (err) return err; } /* Store the user provided path string in ctx to show in mountinfo */ ovl_add_layer(fc, layer, layer_path, &name); return err;} static int ovl_parse_layer(struct fs_context *fc, struct fs_parameter *param, enum ovl_opt layer){ struct path layer_path __free(path_put) = {}; int err = 0; switch (param->type) { case fs_value_is_string: err = ovl_kern_path(param->string, &layer_path, layer); if (err) return err; err = ovl_do_parse_layer(fc, param->string, &layer_path, layer); break; case fs_value_is_file: { char *buf __free(kfree); char *layer_name; buf = kmalloc(PATH_MAX, GFP_KERNEL_ACCOUNT); if (!buf) return -ENOMEM; layer_path = param->file->f_path; path_get(&layer_path); layer_name = d_path(&layer_path, buf, PATH_MAX); if (IS_ERR(layer_name)) return PTR_ERR(layer_name); err = ovl_do_parse_layer(fc, layer_name, &layer_path, layer); break; } default: WARN_ON_ONCE(true); err = -EINVAL; } return err;} static void ovl_reset_lowerdirs(struct ovl_fs_context *ctx){ struct ovl_fs_context_layer *l = ctx->lower; // Reset old user provided lowerdir string kfree(ctx->lowerdir_all);Lines 1–500 of 1104. Load more
// SPDX-License-Identifier: GPL-2.0-only #include <linux/fs.h> #include <linux/module.h> #include <linux/namei.h> #include <linux/fs_context.h> #include <linux/fs_parser.h> #include <linux/posix_acl_xattr.h> #include <linux/seq_file.h> #include <linux/xattr.h> #include "overlayfs.h" #include "params.h" static bool ovl_redirect_dir_def = IS_ENABLED(CONFIG_OVERLAY_FS_REDIRECT_DIR); module_param_named(redirect_dir, ovl_redirect_dir_def, bool, 0644); MODULE_PARM_DESC(redirect_dir, "Default to on or off for the redirect_dir feature"); static bool ovl_redirect_always_follow = IS_ENABLED(CONFIG_OVERLAY_FS_REDIRECT_ALWAYS_FOLLOW); module_param_named(redirect_always_follow, ovl_redirect_always_follow, bool, 0644); MODULE_PARM_DESC(redirect_always_follow, "Follow redirects even if redirect_dir feature is turned off"); static bool ovl_xino_auto_def = IS_ENABLED(CONFIG_OVERLAY_FS_XINO_AUTO); module_param_named(xino_auto, ovl_xino_auto_def, bool, 0644); MODULE_PARM_DESC(xino_auto, "Auto enable xino feature"); static bool ovl_index_def = IS_ENABLED(CONFIG_OVERLAY_FS_INDEX); module_param_named(index, ovl_index_def, bool, 0644); MODULE_PARM_DESC(index, "Default to on or off for the inodes index feature"); static bool ovl_nfs_export_def = IS_ENABLED(CONFIG_OVERLAY_FS_NFS_EXPORT); module_param_named(nfs_export, ovl_nfs_export_def, bool, 0644); MODULE_PARM_DESC(nfs_export, "Default to on or off for the NFS export feature"); static bool ovl_metacopy_def = IS_ENABLED(CONFIG_OVERLAY_FS_METACOPY); module_param_named(metacopy, ovl_metacopy_def, bool, 0644); MODULE_PARM_DESC(metacopy, "Default to on or off for the metadata only copy up feature"); enum ovl_opt { Opt_lowerdir, Opt_lowerdir_add, Opt_datadir_add, Opt_upperdir, Opt_workdir, Opt_default_permissions, Opt_redirect_dir, Opt_index, Opt_uuid, Opt_nfs_export, Opt_userxattr, Opt_xino, Opt_metacopy, Opt_verity, Opt_fsync, Opt_volatile, Opt_override_creds, }; static const struct constant_table ovl_parameter_bool[] = { { "on", true }, { "off", false }, {} }; static const struct constant_table ovl_parameter_uuid[] = { { "off", OVL_UUID_OFF }, { "null", OVL_UUID_NULL }, { "auto", OVL_UUID_AUTO }, { "on", OVL_UUID_ON }, {} }; static const char *ovl_uuid_mode(struct ovl_config *config) { return ovl_parameter_uuid[config->uuid].name; } static int ovl_uuid_def(void) { return OVL_UUID_AUTO; } static const struct constant_table ovl_parameter_xino[] = { { "off", OVL_XINO_OFF }, { "auto", OVL_XINO_AUTO }, { "on", OVL_XINO_ON }, {} }; const char *ovl_xino_mode(struct ovl_config *config) { return ovl_parameter_xino[config->xino].name; } static int ovl_xino_def(void) { return ovl_xino_auto_def ? OVL_XINO_AUTO : OVL_XINO_OFF; } const struct constant_table ovl_parameter_redirect_dir[] = { { "off", OVL_REDIRECT_OFF }, { "follow", OVL_REDIRECT_FOLLOW }, { "nofollow", OVL_REDIRECT_NOFOLLOW }, { "on", OVL_REDIRECT_ON }, {} }; static const char *ovl_redirect_mode(struct ovl_config *config) { return ovl_parameter_redirect_dir[config->redirect_mode].name; } static int ovl_redirect_mode_def(void) { return ovl_redirect_dir_def ? OVL_REDIRECT_ON : ovl_redirect_always_follow ? OVL_REDIRECT_FOLLOW : OVL_REDIRECT_NOFOLLOW; } static const struct constant_table ovl_parameter_verity[] = { { "off", OVL_VERITY_OFF }, { "on", OVL_VERITY_ON }, { "require", OVL_VERITY_REQUIRE }, {} }; static const char *ovl_verity_mode(struct ovl_config *config) { return ovl_parameter_verity[config->verity_mode].name; } static int ovl_verity_mode_def(void) { return OVL_VERITY_OFF; } static const struct constant_table ovl_parameter_fsync[] = { { "volatile", OVL_FSYNC_VOLATILE }, { "auto", OVL_FSYNC_AUTO }, { "strict", OVL_FSYNC_STRICT }, {} }; static const char *ovl_fsync_mode(struct ovl_config *config) { return ovl_parameter_fsync[config->fsync_mode].name; } static int ovl_fsync_mode_def(void) { return OVL_FSYNC_AUTO; } const struct fs_parameter_spec ovl_parameter_spec[] = { fsparam_string_empty("lowerdir", Opt_lowerdir), fsparam_file_or_string("lowerdir+", Opt_lowerdir_add), fsparam_file_or_string("datadir+", Opt_datadir_add), fsparam_file_or_string("upperdir", Opt_upperdir), fsparam_file_or_string("workdir", Opt_workdir), fsparam_flag("default_permissions", Opt_default_permissions), fsparam_enum("redirect_dir", Opt_redirect_dir, ovl_parameter_redirect_dir), fsparam_enum("index", Opt_index, ovl_parameter_bool), fsparam_enum("uuid", Opt_uuid, ovl_parameter_uuid), fsparam_enum("nfs_export", Opt_nfs_export, ovl_parameter_bool), fsparam_flag("userxattr", Opt_userxattr), fsparam_enum("xino", Opt_xino, ovl_parameter_xino), fsparam_enum("metacopy", Opt_metacopy, ovl_parameter_bool), fsparam_enum("verity", Opt_verity, ovl_parameter_verity), fsparam_enum("fsync", Opt_fsync, ovl_parameter_fsync), fsparam_flag("volatile", Opt_volatile), fsparam_flag_no("override_creds", Opt_override_creds), {} }; static char *ovl_next_opt(char **s) { char *sbegin = *s; char *p; if (sbegin == NULL) return NULL; for (p = sbegin; *p; p++) { if (*p == '\\') { p++; if (!*p) break; } else if (*p == ',') { *p = '\0'; *s = p + 1; return sbegin; } } *s = NULL; return sbegin; } static int ovl_parse_monolithic(struct fs_context *fc, void *data) { return vfs_parse_monolithic_sep(fc, data, ovl_next_opt); } static ssize_t ovl_parse_param_split_lowerdirs(char *str) { ssize_t nr_layers = 1, nr_colons = 0; char *s, *d; for (s = d = str;; s++, d++) { if (*s == '\\') { /* keep esc chars in split lowerdir */ *d++ = *s++; } else if (*s == ':') { bool next_colon = (*(s + 1) == ':'); nr_colons++; if (nr_colons == 2 && next_colon) { pr_err("only single ':' or double '::' sequences of unescaped colons in lowerdir mount option allowed.\n"); return -EINVAL; } /* count layers, not colons */ if (!next_colon) nr_layers++; *d = '\0'; continue; } *d = *s; if (!*s) { /* trailing colons */ if (nr_colons) { pr_err("unescaped trailing colons in lowerdir mount option.\n"); return -EINVAL; } break; } nr_colons = 0; } return nr_layers; } static int ovl_mount_dir_noesc(const char *name, struct path *path) { int err = -EINVAL; if (!*name) { pr_err("empty lowerdir\n"); goto out; } err = kern_path(name, LOOKUP_FOLLOW, path); if (err) { pr_err("failed to resolve '%s': %i\n", name, err); goto out; } return 0; out: return err; } static void ovl_unescape(char *s) { char *d = s; for (;; s++, d++) { if (*s == '\\') s++; *d = *s; if (!*s) break; } } static int ovl_mount_dir(const char *name, struct path *path) { int err = -ENOMEM; char *tmp = kstrdup(name, GFP_KERNEL); if (tmp) { ovl_unescape(tmp); err = ovl_mount_dir_noesc(tmp, path); kfree(tmp); } return err; } static int ovl_mount_dir_check(struct fs_context *fc, const struct path *path, enum ovl_opt layer, const char *name, bool upper) { bool is_casefolded = ovl_dentry_casefolded(path->dentry); struct ovl_fs_context *ctx = fc->fs_private; struct ovl_fs *ofs = fc->s_fs_info; if (!d_is_dir(path->dentry)) return invalfc(fc, "%s is not a directory", name); /* * Allow filesystems that are case-folding capable but deny composing * ovl stack from inconsistent case-folded directories. */ if (!ctx->casefold_set) { ofs->casefold = is_casefolded; ctx->casefold_set = true; } if (ofs->casefold != is_casefolded) { return invalfc(fc, "case-%ssensitive directory on %s is inconsistent", is_casefolded ? "in" : "", name); } if (ovl_dentry_weird(path->dentry)) return invalfc(fc, "filesystem on %s not supported", name); /* * Check whether upper path is read-only here to report failures * early. Don't forget to recheck when the superblock is created * as the mount attributes could change. */ if (upper) { if (path->dentry->d_flags & DCACHE_OP_REAL) return invalfc(fc, "filesystem on %s not supported as upperdir", name); if (__mnt_is_readonly(path->mnt)) return invalfc(fc, "filesystem on %s is read-only", name); } else { if (ctx->lowerdir_all && layer != Opt_lowerdir) return invalfc(fc, "lowerdir+ and datadir+ cannot follow lowerdir"); if (ctx->nr_data && layer == Opt_lowerdir_add) return invalfc(fc, "regular lower layers cannot follow data layers"); if (ctx->nr == OVL_MAX_STACK) return invalfc(fc, "too many lower directories, limit is %d", OVL_MAX_STACK); } return 0; } static int ovl_ctx_realloc_lower(struct fs_context *fc) { struct ovl_fs_context *ctx = fc->fs_private; struct ovl_fs_context_layer *l; size_t nr; if (ctx->nr < ctx->capacity) return 0; nr = min_t(size_t, max(4096 / sizeof(*l), ctx->capacity * 2), OVL_MAX_STACK); l = krealloc_array(ctx->lower, nr, sizeof(*l), GFP_KERNEL_ACCOUNT); if (!l) return -ENOMEM; ctx->lower = l; ctx->capacity = nr; return 0; } static void ovl_add_layer(struct fs_context *fc, enum ovl_opt layer, struct path *path, char **pname) { struct ovl_fs *ofs = fc->s_fs_info; struct ovl_config *config = &ofs->config; struct ovl_fs_context *ctx = fc->fs_private; struct ovl_fs_context_layer *l; switch (layer) { case Opt_workdir: swap(config->workdir, *pname); swap(ctx->work, *path); break; case Opt_upperdir: swap(config->upperdir, *pname); swap(ctx->upper, *path); break; case Opt_datadir_add: ctx->nr_data++; fallthrough; case Opt_lowerdir: fallthrough; case Opt_lowerdir_add: WARN_ON(ctx->nr >= ctx->capacity); l = &ctx->lower[ctx->nr++]; memset(l, 0, sizeof(*l)); swap(l->name, *pname); swap(l->path, *path); break; default: WARN_ON(1); } } static inline bool is_upper_layer(enum ovl_opt layer) { return layer == Opt_upperdir || layer == Opt_workdir; } /* Handle non-file descriptor-based layer options that require path lookup. */ static inline int ovl_kern_path(const char *layer_name, struct path *layer_path, enum ovl_opt layer) { int err; switch (layer) { case Opt_upperdir: fallthrough; case Opt_workdir: fallthrough; case Opt_lowerdir: err = ovl_mount_dir(layer_name, layer_path); break; case Opt_lowerdir_add: fallthrough; case Opt_datadir_add: err = ovl_mount_dir_noesc(layer_name, layer_path); break; default: WARN_ON_ONCE(true); err = -EINVAL; } return err; } static int ovl_do_parse_layer(struct fs_context *fc, const char *layer_name, struct path *layer_path, enum ovl_opt layer) { char *name __free(kfree) = kstrdup(layer_name, GFP_KERNEL); bool upper; int err = 0; if (!name) return -ENOMEM; upper = is_upper_layer(layer); err = ovl_mount_dir_check(fc, layer_path, layer, name, upper); if (err) return err; if (!upper) { err = ovl_ctx_realloc_lower(fc); if (err) return err; } /* Store the user provided path string in ctx to show in mountinfo */ ovl_add_layer(fc, layer, layer_path, &name); return err; } static int ovl_parse_layer(struct fs_context *fc, struct fs_parameter *param, enum ovl_opt layer) { struct path layer_path __free(path_put) = {}; int err = 0; switch (param->type) { case fs_value_is_string: err = ovl_kern_path(param->string, &layer_path, layer); if (err) return err; err = ovl_do_parse_layer(fc, param->string, &layer_path, layer); break; case fs_value_is_file: { char *buf __free(kfree); char *layer_name; buf = kmalloc(PATH_MAX, GFP_KERNEL_ACCOUNT); if (!buf) return -ENOMEM; layer_path = param->file->f_path; path_get(&layer_path); layer_name = d_path(&layer_path, buf, PATH_MAX); if (IS_ERR(layer_name)) return PTR_ERR(layer_name); err = ovl_do_parse_layer(fc, layer_name, &layer_path, layer); break; } default: WARN_ON_ONCE(true); err = -EINVAL; } return err; } static void ovl_reset_lowerdirs(struct ovl_fs_context *ctx) { struct ovl_fs_context_layer *l = ctx->lower; // Reset old user provided lowerdir string kfree(ctx->lowerdir_all);