Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 32021982 authored by David Howells's avatar David Howells Committed by Al Viro
Browse files

hugetlbfs: Convert to fs_context



Convert the hugetlbfs to use the fs_context during mount.

Signed-off-by: default avatarDavid Howells <dhowells@redhat.com>
Signed-off-by: default avatarAl Viro <viro@zeniv.linux.org.uk>
parent a1875374
Loading
Loading
Loading
Loading
+200 −158
Original line number Diff line number Diff line
@@ -27,7 +27,7 @@
#include <linux/backing-dev.h>
#include <linux/hugetlb.h>
#include <linux/pagevec.h>
#include <linux/parser.h>
#include <linux/fs_parser.h>
#include <linux/mman.h>
#include <linux/slab.h>
#include <linux/dnotify.h>
@@ -45,11 +45,17 @@ const struct file_operations hugetlbfs_file_operations;
static const struct inode_operations hugetlbfs_dir_inode_operations;
static const struct inode_operations hugetlbfs_inode_operations;

struct hugetlbfs_config {
enum hugetlbfs_size_type { NO_SIZE, SIZE_STD, SIZE_PERCENT };

struct hugetlbfs_fs_context {
	struct hstate		*hstate;
	unsigned long long	max_size_opt;
	unsigned long long	min_size_opt;
	long			max_hpages;
	long			nr_inodes;
	long			min_hpages;
	enum hugetlbfs_size_type max_val_type;
	enum hugetlbfs_size_type min_val_type;
	kuid_t			uid;
	kgid_t			gid;
	umode_t			mode;
@@ -57,22 +63,30 @@ struct hugetlbfs_config {

int sysctl_hugetlb_shm_group;

enum {
	Opt_size, Opt_nr_inodes,
	Opt_mode, Opt_uid, Opt_gid,
	Opt_pagesize, Opt_min_size,
	Opt_err,
enum hugetlb_param {
	Opt_gid,
	Opt_min_size,
	Opt_mode,
	Opt_nr_inodes,
	Opt_pagesize,
	Opt_size,
	Opt_uid,
};

static const match_table_t tokens = {
	{Opt_size,	"size=%s"},
	{Opt_nr_inodes,	"nr_inodes=%s"},
	{Opt_mode,	"mode=%o"},
	{Opt_uid,	"uid=%u"},
	{Opt_gid,	"gid=%u"},
	{Opt_pagesize,	"pagesize=%s"},
	{Opt_min_size,	"min_size=%s"},
	{Opt_err,	NULL},
static const struct fs_parameter_spec hugetlb_param_specs[] = {
	fsparam_u32   ("gid",		Opt_gid),
	fsparam_string("min_size",	Opt_min_size),
	fsparam_u32   ("mode",		Opt_mode),
	fsparam_string("nr_inodes",	Opt_nr_inodes),
	fsparam_string("pagesize",	Opt_pagesize),
	fsparam_string("size",		Opt_size),
	fsparam_u32   ("uid",		Opt_uid),
	{}
};

static const struct fs_parameter_description hugetlb_fs_parameters = {
	.name		= "hugetlbfs",
	.specs		= hugetlb_param_specs,
};

#ifdef CONFIG_NUMA
@@ -708,16 +722,16 @@ static int hugetlbfs_setattr(struct dentry *dentry, struct iattr *attr)
}

static struct inode *hugetlbfs_get_root(struct super_block *sb,
					struct hugetlbfs_config *config)
					struct hugetlbfs_fs_context *ctx)
{
	struct inode *inode;

	inode = new_inode(sb);
	if (inode) {
		inode->i_ino = get_next_ino();
		inode->i_mode = S_IFDIR | config->mode;
		inode->i_uid = config->uid;
		inode->i_gid = config->gid;
		inode->i_mode = S_IFDIR | ctx->mode;
		inode->i_uid = ctx->uid;
		inode->i_gid = ctx->gid;
		inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode);
		inode->i_op = &hugetlbfs_dir_inode_operations;
		inode->i_fop = &simple_dir_operations;
@@ -1081,8 +1095,6 @@ static const struct super_operations hugetlbfs_ops = {
	.show_options	= hugetlbfs_show_options,
};

enum hugetlbfs_size_type { NO_SIZE, SIZE_STD, SIZE_PERCENT };

/*
 * Convert size option passed from command line to number of huge pages
 * in the pool specified by hstate.  Size option could be in bytes
@@ -1105,170 +1117,151 @@ hugetlbfs_size_to_hpages(struct hstate *h, unsigned long long size_opt,
	return size_opt;
}

static int
hugetlbfs_parse_options(char *options, struct hugetlbfs_config *pconfig)
/*
 * Parse one mount parameter.
 */
static int hugetlbfs_parse_param(struct fs_context *fc, struct fs_parameter *param)
{
	char *p, *rest;
	substring_t args[MAX_OPT_ARGS];
	int option;
	unsigned long long max_size_opt = 0, min_size_opt = 0;
	enum hugetlbfs_size_type max_val_type = NO_SIZE, min_val_type = NO_SIZE;

	if (!options)
		return 0;
	struct hugetlbfs_fs_context *ctx = fc->fs_private;
	struct fs_parse_result result;
	char *rest;
	unsigned long ps;
	int opt;

	while ((p = strsep(&options, ",")) != NULL) {
		int token;
		if (!*p)
			continue;
	opt = fs_parse(fc, &hugetlb_fs_parameters, param, &result);
	if (opt < 0)
		return opt;

		token = match_token(p, tokens, args);
		switch (token) {
	switch (opt) {
	case Opt_uid:
			if (match_int(&args[0], &option))
		ctx->uid = make_kuid(current_user_ns(), result.uint_32);
		if (!uid_valid(ctx->uid))
			goto bad_val;
			pconfig->uid = make_kuid(current_user_ns(), option);
			if (!uid_valid(pconfig->uid))
				goto bad_val;
			break;
		return 0;

	case Opt_gid:
			if (match_int(&args[0], &option))
 				goto bad_val;
			pconfig->gid = make_kgid(current_user_ns(), option);
			if (!gid_valid(pconfig->gid))
		ctx->gid = make_kgid(current_user_ns(), result.uint_32);
		if (!gid_valid(ctx->gid))
			goto bad_val;
			break;
		return 0;

	case Opt_mode:
			if (match_octal(&args[0], &option))
 				goto bad_val;
			pconfig->mode = option & 01777U;
			break;
		ctx->mode = result.uint_32 & 01777U;
		return 0;

		case Opt_size: {
	case Opt_size:
		/* memparse() will accept a K/M/G without a digit */
			if (!isdigit(*args[0].from))
		if (!isdigit(param->string[0]))
			goto bad_val;
			max_size_opt = memparse(args[0].from, &rest);
			max_val_type = SIZE_STD;
		ctx->max_size_opt = memparse(param->string, &rest);
		ctx->max_val_type = SIZE_STD;
		if (*rest == '%')
				max_val_type = SIZE_PERCENT;
			break;
		}
			ctx->max_val_type = SIZE_PERCENT;
		return 0;

	case Opt_nr_inodes:
		/* memparse() will accept a K/M/G without a digit */
			if (!isdigit(*args[0].from))
		if (!isdigit(param->string[0]))
			goto bad_val;
			pconfig->nr_inodes = memparse(args[0].from, &rest);
			break;
		ctx->nr_inodes = memparse(param->string, &rest);
		return 0;

		case Opt_pagesize: {
			unsigned long ps;
			ps = memparse(args[0].from, &rest);
			pconfig->hstate = size_to_hstate(ps);
			if (!pconfig->hstate) {
				pr_err("Unsupported page size %lu MB\n",
					ps >> 20);
	case Opt_pagesize:
		ps = memparse(param->string, &rest);
		ctx->hstate = size_to_hstate(ps);
		if (!ctx->hstate) {
			pr_err("Unsupported page size %lu MB\n", ps >> 20);
			return -EINVAL;
		}
			break;
		}
		return 0;

		case Opt_min_size: {
	case Opt_min_size:
		/* memparse() will accept a K/M/G without a digit */
			if (!isdigit(*args[0].from))
		if (!isdigit(param->string[0]))
			goto bad_val;
			min_size_opt = memparse(args[0].from, &rest);
			min_val_type = SIZE_STD;
		ctx->min_size_opt = memparse(param->string, &rest);
		ctx->min_val_type = SIZE_STD;
		if (*rest == '%')
				min_val_type = SIZE_PERCENT;
			break;
		}
			ctx->min_val_type = SIZE_PERCENT;
		return 0;

	default:
			pr_err("Bad mount option: \"%s\"\n", p);
		return -EINVAL;
			break;
	}

bad_val:
	return invalf(fc, "hugetlbfs: Bad value '%s' for mount option '%s'\n",
		      param->string, param->key);
}

/*
 * Validate the parsed options.
 */
static int hugetlbfs_validate(struct fs_context *fc)
{
	struct hugetlbfs_fs_context *ctx = fc->fs_private;

	/*
	 * Use huge page pool size (in hstate) to convert the size
	 * options to number of huge pages.  If NO_SIZE, -1 is returned.
	 */
	pconfig->max_hpages = hugetlbfs_size_to_hpages(pconfig->hstate,
						max_size_opt, max_val_type);
	pconfig->min_hpages = hugetlbfs_size_to_hpages(pconfig->hstate,
						min_size_opt, min_val_type);
	ctx->max_hpages = hugetlbfs_size_to_hpages(ctx->hstate,
						   ctx->max_size_opt,
						   ctx->max_val_type);
	ctx->min_hpages = hugetlbfs_size_to_hpages(ctx->hstate,
						   ctx->min_size_opt,
						   ctx->min_val_type);

	/*
	 * If max_size was specified, then min_size must be smaller
	 */
	if (max_val_type > NO_SIZE &&
	    pconfig->min_hpages > pconfig->max_hpages) {
		pr_err("minimum size can not be greater than maximum size\n");
	if (ctx->max_val_type > NO_SIZE &&
	    ctx->min_hpages > ctx->max_hpages) {
		pr_err("Minimum size can not be greater than maximum size\n");
		return -EINVAL;
	}

	return 0;

bad_val:
	pr_err("Bad value '%s' for mount option '%s'\n", args[0].from, p);
 	return -EINVAL;
}

static int
hugetlbfs_fill_super(struct super_block *sb, void *data, int silent)
hugetlbfs_fill_super(struct super_block *sb, struct fs_context *fc)
{
	int ret;
	struct hugetlbfs_config config;
	struct hugetlbfs_fs_context *ctx = fc->fs_private;
	struct hugetlbfs_sb_info *sbinfo;

	config.max_hpages = -1; /* No limit on size by default */
	config.nr_inodes = -1; /* No limit on number of inodes by default */
	config.uid = current_fsuid();
	config.gid = current_fsgid();
	config.mode = 0755;
	config.hstate = &default_hstate;
	config.min_hpages = -1; /* No default minimum size */
	ret = hugetlbfs_parse_options(data, &config);
	if (ret)
		return ret;

	sbinfo = kmalloc(sizeof(struct hugetlbfs_sb_info), GFP_KERNEL);
	if (!sbinfo)
		return -ENOMEM;
	sb->s_fs_info = sbinfo;
	sbinfo->hstate = config.hstate;
	spin_lock_init(&sbinfo->stat_lock);
	sbinfo->max_inodes = config.nr_inodes;
	sbinfo->free_inodes = config.nr_inodes;
	sbinfo->hstate		= ctx->hstate;
	sbinfo->max_inodes	= ctx->nr_inodes;
	sbinfo->free_inodes	= ctx->nr_inodes;
	sbinfo->spool		= NULL;
	sbinfo->uid = config.uid;
	sbinfo->gid = config.gid;
	sbinfo->mode = config.mode;
	sbinfo->uid		= ctx->uid;
	sbinfo->gid		= ctx->gid;
	sbinfo->mode		= ctx->mode;

	/*
	 * Allocate and initialize subpool if maximum or minimum size is
	 * specified.  Any needed reservations (for minimim size) are taken
	 * taken when the subpool is created.
	 */
	if (config.max_hpages != -1 || config.min_hpages != -1) {
		sbinfo->spool = hugepage_new_subpool(config.hstate,
							config.max_hpages,
							config.min_hpages);
	if (ctx->max_hpages != -1 || ctx->min_hpages != -1) {
		sbinfo->spool = hugepage_new_subpool(ctx->hstate,
						     ctx->max_hpages,
						     ctx->min_hpages);
		if (!sbinfo->spool)
			goto out_free;
	}
	sb->s_maxbytes = MAX_LFS_FILESIZE;
	sb->s_blocksize = huge_page_size(config.hstate);
	sb->s_blocksize_bits = huge_page_shift(config.hstate);
	sb->s_blocksize = huge_page_size(ctx->hstate);
	sb->s_blocksize_bits = huge_page_shift(ctx->hstate);
	sb->s_magic = HUGETLBFS_MAGIC;
	sb->s_op = &hugetlbfs_ops;
	sb->s_time_gran = 1;
	sb->s_root = d_make_root(hugetlbfs_get_root(sb, &config));
	sb->s_root = d_make_root(hugetlbfs_get_root(sb, ctx));
	if (!sb->s_root)
		goto out_free;
	return 0;
@@ -1278,15 +1271,51 @@ hugetlbfs_fill_super(struct super_block *sb, void *data, int silent)
	return -ENOMEM;
}

static struct dentry *hugetlbfs_mount(struct file_system_type *fs_type,
	int flags, const char *dev_name, void *data)
static int hugetlbfs_get_tree(struct fs_context *fc)
{
	int err = hugetlbfs_validate(fc);
	if (err)
		return err;
	return vfs_get_super(fc, vfs_get_independent_super, hugetlbfs_fill_super);
}

static void hugetlbfs_fs_context_free(struct fs_context *fc)
{
	kfree(fc->fs_private);
}

static const struct fs_context_operations hugetlbfs_fs_context_ops = {
	.free		= hugetlbfs_fs_context_free,
	.parse_param	= hugetlbfs_parse_param,
	.get_tree	= hugetlbfs_get_tree,
};

static int hugetlbfs_init_fs_context(struct fs_context *fc)
{
	return mount_nodev(fs_type, flags, data, hugetlbfs_fill_super);
	struct hugetlbfs_fs_context *ctx;

	ctx = kzalloc(sizeof(struct hugetlbfs_fs_context), GFP_KERNEL);
	if (!ctx)
		return -ENOMEM;

	ctx->max_hpages	= -1; /* No limit on size by default */
	ctx->nr_inodes	= -1; /* No limit on number of inodes by default */
	ctx->uid	= current_fsuid();
	ctx->gid	= current_fsgid();
	ctx->mode	= 0755;
	ctx->hstate	= &default_hstate;
	ctx->min_hpages	= -1; /* No default minimum size */
	ctx->max_val_type = NO_SIZE;
	ctx->min_val_type = NO_SIZE;
	fc->fs_private = ctx;
	fc->ops	= &hugetlbfs_fs_context_ops;
	return 0;
}

static struct file_system_type hugetlbfs_fs_type = {
	.name			= "hugetlbfs",
	.mount		= hugetlbfs_mount,
	.init_fs_context	= hugetlbfs_init_fs_context,
	.parameters		= &hugetlb_fs_parameters,
	.kill_sb		= kill_litter_super,
};

@@ -1372,8 +1401,29 @@ struct file *hugetlb_file_setup(const char *name, size_t size,
	return file;
}

static struct vfsmount *__init mount_one_hugetlbfs(struct hstate *h)
{
	struct fs_context *fc;
	struct vfsmount *mnt;

	fc = fs_context_for_mount(&hugetlbfs_fs_type, SB_KERNMOUNT);
	if (IS_ERR(fc)) {
		mnt = ERR_CAST(fc);
	} else {
		struct hugetlbfs_fs_context *ctx = fc->fs_private;
		ctx->hstate = h;
		mnt = fc_mount(fc);
		put_fs_context(fc);
	}
	if (IS_ERR(mnt))
		pr_err("Cannot mount internal hugetlbfs for page size %uK",
		       1U << (h->order + PAGE_SHIFT - 10));
	return mnt;
}

static int __init init_hugetlbfs_fs(void)
{
	struct vfsmount *mnt;
	struct hstate *h;
	int error;
	int i;
@@ -1396,23 +1446,15 @@ static int __init init_hugetlbfs_fs(void)

	i = 0;
	for_each_hstate(h) {
		char buf[50];
		unsigned ps_kb = 1U << (h->order + PAGE_SHIFT - 10);

		snprintf(buf, sizeof(buf), "pagesize=%uK", ps_kb);
		hugetlbfs_vfsmount[i] = kern_mount_data(&hugetlbfs_fs_type,
							buf);

		if (IS_ERR(hugetlbfs_vfsmount[i])) {
			pr_err("Cannot mount internal hugetlbfs for "
				"page size %uK", ps_kb);
			error = PTR_ERR(hugetlbfs_vfsmount[i]);
			hugetlbfs_vfsmount[i] = NULL;
		mnt = mount_one_hugetlbfs(h);
		if (IS_ERR(mnt) && i == 0) {
			error = PTR_ERR(mnt);
			goto out;
		}
		hugetlbfs_vfsmount[i] = mnt;
		i++;
	}
	/* Non default hstates are optional */
	if (!IS_ERR_OR_NULL(hugetlbfs_vfsmount[default_hstate_idx]))

	return 0;

 out: