diff options
Diffstat (limited to 'mm/shmem.c')
| -rw-r--r-- | mm/shmem.c | 399 | 
1 files changed, 255 insertions, 144 deletions
diff --git a/mm/shmem.c b/mm/shmem.c index 2bed4761f279..cd570cc79c76 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -37,6 +37,7 @@  #include <linux/khugepaged.h>  #include <linux/hugetlb.h>  #include <linux/frontswap.h> +#include <linux/fs_parser.h>  #include <asm/tlbflush.h> /* for arch/microblaze update_mmu_cache() */ @@ -107,6 +108,20 @@ struct shmem_falloc {  	pgoff_t nr_unswapped;	/* how often writepage refused to swap out */  }; +struct shmem_options { +	unsigned long long blocks; +	unsigned long long inodes; +	struct mempolicy *mpol; +	kuid_t uid; +	kgid_t gid; +	umode_t mode; +	int huge; +	int seen; +#define SHMEM_SEEN_BLOCKS 1 +#define SHMEM_SEEN_INODES 2 +#define SHMEM_SEEN_HUGE 4 +}; +  #ifdef CONFIG_TMPFS  static unsigned long shmem_default_max_blocks(void)  { @@ -594,7 +609,7 @@ static int shmem_add_to_page_cache(struct page *page,  {  	XA_STATE_ORDER(xas, &mapping->i_pages, index, compound_order(page));  	unsigned long i = 0; -	unsigned long nr = 1UL << compound_order(page); +	unsigned long nr = compound_nr(page);  	VM_BUG_ON_PAGE(PageTail(page), page);  	VM_BUG_ON_PAGE(index != round_down(index, nr), page); @@ -616,7 +631,7 @@ static int shmem_add_to_page_cache(struct page *page,  		if (xas_error(&xas))  			goto unlock;  next: -		xas_store(&xas, page + i); +		xas_store(&xas, page);  		if (++i < nr) {  			xas_next(&xas);  			goto next; @@ -1466,7 +1481,7 @@ static struct page *shmem_alloc_hugepage(gfp_t gfp,  	shmem_pseudo_vma_init(&pvma, info, hindex);  	page = alloc_pages_vma(gfp | __GFP_COMP | __GFP_NORETRY | __GFP_NOWARN, -			HPAGE_PMD_ORDER, &pvma, 0, numa_node_id()); +			HPAGE_PMD_ORDER, &pvma, 0, numa_node_id(), true);  	shmem_pseudo_vma_destroy(&pvma);  	if (page)  		prep_transhuge_page(page); @@ -1719,7 +1734,7 @@ unlock:   * vm. If we swap it in we mark it dirty since we also free the swap   * entry since a page cannot live in both the swap and page cache.   * - * fault_mm and fault_type are only supplied by shmem_fault: + * vmf and fault_type are only supplied by shmem_fault:   * otherwise they are NULL.   */  static int shmem_getpage_gfp(struct inode *inode, pgoff_t index, @@ -1869,7 +1884,7 @@ alloc_nohuge:  	lru_cache_add_anon(page);  	spin_lock_irq(&info->lock); -	info->alloced += 1 << compound_order(page); +	info->alloced += compound_nr(page);  	inode->i_blocks += BLOCKS_PER_PAGE << compound_order(page);  	shmem_recalc_inode(inode);  	spin_unlock_irq(&info->lock); @@ -1910,7 +1925,7 @@ clear:  		struct page *head = compound_head(page);  		int i; -		for (i = 0; i < (1 << compound_order(head)); i++) { +		for (i = 0; i < compound_nr(head); i++) {  			clear_highpage(head + i);  			flush_dcache_page(head + i);  		} @@ -1937,7 +1952,7 @@ clear:  	 * Error recovery.  	 */  unacct: -	shmem_inode_unacct_blocks(inode, 1 << compound_order(page)); +	shmem_inode_unacct_blocks(inode, compound_nr(page));  	if (PageTransHuge(page)) {  		unlock_page(page); @@ -3349,16 +3364,126 @@ static const struct export_operations shmem_export_ops = {  	.fh_to_dentry	= shmem_fh_to_dentry,  }; -static int shmem_parse_options(char *options, struct shmem_sb_info *sbinfo, -			       bool remount) +enum shmem_param { +	Opt_gid, +	Opt_huge, +	Opt_mode, +	Opt_mpol, +	Opt_nr_blocks, +	Opt_nr_inodes, +	Opt_size, +	Opt_uid, +}; + +static const struct fs_parameter_spec shmem_param_specs[] = { +	fsparam_u32   ("gid",		Opt_gid), +	fsparam_enum  ("huge",		Opt_huge), +	fsparam_u32oct("mode",		Opt_mode), +	fsparam_string("mpol",		Opt_mpol), +	fsparam_string("nr_blocks",	Opt_nr_blocks), +	fsparam_string("nr_inodes",	Opt_nr_inodes), +	fsparam_string("size",		Opt_size), +	fsparam_u32   ("uid",		Opt_uid), +	{} +}; + +static const struct fs_parameter_enum shmem_param_enums[] = { +	{ Opt_huge,	"never",	SHMEM_HUGE_NEVER }, +	{ Opt_huge,	"always",	SHMEM_HUGE_ALWAYS }, +	{ Opt_huge,	"within_size",	SHMEM_HUGE_WITHIN_SIZE }, +	{ Opt_huge,	"advise",	SHMEM_HUGE_ADVISE }, +	{} +}; + +const struct fs_parameter_description shmem_fs_parameters = { +	.name		= "tmpfs", +	.specs		= shmem_param_specs, +	.enums		= shmem_param_enums, +}; + +static int shmem_parse_one(struct fs_context *fc, struct fs_parameter *param)  { -	char *this_char, *value, *rest; -	struct mempolicy *mpol = NULL; -	uid_t uid; -	gid_t gid; +	struct shmem_options *ctx = fc->fs_private; +	struct fs_parse_result result; +	unsigned long long size; +	char *rest; +	int opt; + +	opt = fs_parse(fc, &shmem_fs_parameters, param, &result); +	if (opt < 0) +		return opt; + +	switch (opt) { +	case Opt_size: +		size = memparse(param->string, &rest); +		if (*rest == '%') { +			size <<= PAGE_SHIFT; +			size *= totalram_pages(); +			do_div(size, 100); +			rest++; +		} +		if (*rest) +			goto bad_value; +		ctx->blocks = DIV_ROUND_UP(size, PAGE_SIZE); +		ctx->seen |= SHMEM_SEEN_BLOCKS; +		break; +	case Opt_nr_blocks: +		ctx->blocks = memparse(param->string, &rest); +		if (*rest) +			goto bad_value; +		ctx->seen |= SHMEM_SEEN_BLOCKS; +		break; +	case Opt_nr_inodes: +		ctx->inodes = memparse(param->string, &rest); +		if (*rest) +			goto bad_value; +		ctx->seen |= SHMEM_SEEN_INODES; +		break; +	case Opt_mode: +		ctx->mode = result.uint_32 & 07777; +		break; +	case Opt_uid: +		ctx->uid = make_kuid(current_user_ns(), result.uint_32); +		if (!uid_valid(ctx->uid)) +			goto bad_value; +		break; +	case Opt_gid: +		ctx->gid = make_kgid(current_user_ns(), result.uint_32); +		if (!gid_valid(ctx->gid)) +			goto bad_value; +		break; +	case Opt_huge: +		ctx->huge = result.uint_32; +		if (ctx->huge != SHMEM_HUGE_NEVER && +		    !(IS_ENABLED(CONFIG_TRANSPARENT_HUGE_PAGECACHE) && +		      has_transparent_hugepage())) +			goto unsupported_parameter; +		ctx->seen |= SHMEM_SEEN_HUGE; +		break; +	case Opt_mpol: +		if (IS_ENABLED(CONFIG_NUMA)) { +			mpol_put(ctx->mpol); +			ctx->mpol = NULL; +			if (mpol_parse_str(param->string, &ctx->mpol)) +				goto bad_value; +			break; +		} +		goto unsupported_parameter; +	} +	return 0; + +unsupported_parameter: +	return invalf(fc, "tmpfs: Unsupported parameter '%s'", param->key); +bad_value: +	return invalf(fc, "tmpfs: Bad value for '%s'", param->key); +} + +static int shmem_parse_options(struct fs_context *fc, void *data) +{ +	char *options = data;  	while (options != NULL) { -		this_char = options; +		char *this_char = options;  		for (;;) {  			/*  			 * NUL-terminate this option: unfortunately, @@ -3374,139 +3499,83 @@ static int shmem_parse_options(char *options, struct shmem_sb_info *sbinfo,  				break;  			}  		} -		if (!*this_char) -			continue; -		if ((value = strchr(this_char,'=')) != NULL) { -			*value++ = 0; -		} else { -			pr_err("tmpfs: No value for mount option '%s'\n", -			       this_char); -			goto error; -		} - -		if (!strcmp(this_char,"size")) { -			unsigned long long size; -			size = memparse(value,&rest); -			if (*rest == '%') { -				size <<= PAGE_SHIFT; -				size *= totalram_pages(); -				do_div(size, 100); -				rest++; +		if (*this_char) { +			char *value = strchr(this_char,'='); +			size_t len = 0; +			int err; + +			if (value) { +				*value++ = '\0'; +				len = strlen(value);  			} -			if (*rest) -				goto bad_val; -			sbinfo->max_blocks = -				DIV_ROUND_UP(size, PAGE_SIZE); -		} else if (!strcmp(this_char,"nr_blocks")) { -			sbinfo->max_blocks = memparse(value, &rest); -			if (*rest) -				goto bad_val; -		} else if (!strcmp(this_char,"nr_inodes")) { -			sbinfo->max_inodes = memparse(value, &rest); -			if (*rest) -				goto bad_val; -		} else if (!strcmp(this_char,"mode")) { -			if (remount) -				continue; -			sbinfo->mode = simple_strtoul(value, &rest, 8) & 07777; -			if (*rest) -				goto bad_val; -		} else if (!strcmp(this_char,"uid")) { -			if (remount) -				continue; -			uid = simple_strtoul(value, &rest, 0); -			if (*rest) -				goto bad_val; -			sbinfo->uid = make_kuid(current_user_ns(), uid); -			if (!uid_valid(sbinfo->uid)) -				goto bad_val; -		} else if (!strcmp(this_char,"gid")) { -			if (remount) -				continue; -			gid = simple_strtoul(value, &rest, 0); -			if (*rest) -				goto bad_val; -			sbinfo->gid = make_kgid(current_user_ns(), gid); -			if (!gid_valid(sbinfo->gid)) -				goto bad_val; -#ifdef CONFIG_TRANSPARENT_HUGE_PAGECACHE -		} else if (!strcmp(this_char, "huge")) { -			int huge; -			huge = shmem_parse_huge(value); -			if (huge < 0) -				goto bad_val; -			if (!has_transparent_hugepage() && -					huge != SHMEM_HUGE_NEVER) -				goto bad_val; -			sbinfo->huge = huge; -#endif -#ifdef CONFIG_NUMA -		} else if (!strcmp(this_char,"mpol")) { -			mpol_put(mpol); -			mpol = NULL; -			if (mpol_parse_str(value, &mpol)) -				goto bad_val; -#endif -		} else { -			pr_err("tmpfs: Bad mount option %s\n", this_char); -			goto error; +			err = vfs_parse_fs_string(fc, this_char, value, len); +			if (err < 0) +				return err;  		}  	} -	sbinfo->mpol = mpol;  	return 0; - -bad_val: -	pr_err("tmpfs: Bad value '%s' for mount option '%s'\n", -	       value, this_char); -error: -	mpol_put(mpol); -	return 1; -  } -static int shmem_remount_fs(struct super_block *sb, int *flags, char *data) +/* + * Reconfigure a shmem filesystem. + * + * Note that we disallow change from limited->unlimited blocks/inodes while any + * are in use; but we must separately disallow unlimited->limited, because in + * that case we have no record of how much is already in use. + */ +static int shmem_reconfigure(struct fs_context *fc)  { -	struct shmem_sb_info *sbinfo = SHMEM_SB(sb); -	struct shmem_sb_info config = *sbinfo; +	struct shmem_options *ctx = fc->fs_private; +	struct shmem_sb_info *sbinfo = SHMEM_SB(fc->root->d_sb);  	unsigned long inodes; -	int error = -EINVAL; - -	config.mpol = NULL; -	if (shmem_parse_options(data, &config, true)) -		return error; +	const char *err;  	spin_lock(&sbinfo->stat_lock);  	inodes = sbinfo->max_inodes - sbinfo->free_inodes; -	if (percpu_counter_compare(&sbinfo->used_blocks, config.max_blocks) > 0) -		goto out; -	if (config.max_inodes < inodes) -		goto out; -	/* -	 * Those tests disallow limited->unlimited while any are in use; -	 * but we must separately disallow unlimited->limited, because -	 * in that case we have no record of how much is already in use. -	 */ -	if (config.max_blocks && !sbinfo->max_blocks) -		goto out; -	if (config.max_inodes && !sbinfo->max_inodes) -		goto out; +	if ((ctx->seen & SHMEM_SEEN_BLOCKS) && ctx->blocks) { +		if (!sbinfo->max_blocks) { +			err = "Cannot retroactively limit size"; +			goto out; +		} +		if (percpu_counter_compare(&sbinfo->used_blocks, +					   ctx->blocks) > 0) { +			err = "Too small a size for current use"; +			goto out; +		} +	} +	if ((ctx->seen & SHMEM_SEEN_INODES) && ctx->inodes) { +		if (!sbinfo->max_inodes) { +			err = "Cannot retroactively limit inodes"; +			goto out; +		} +		if (ctx->inodes < inodes) { +			err = "Too few inodes for current use"; +			goto out; +		} +	} -	error = 0; -	sbinfo->huge = config.huge; -	sbinfo->max_blocks  = config.max_blocks; -	sbinfo->max_inodes  = config.max_inodes; -	sbinfo->free_inodes = config.max_inodes - inodes; +	if (ctx->seen & SHMEM_SEEN_HUGE) +		sbinfo->huge = ctx->huge; +	if (ctx->seen & SHMEM_SEEN_BLOCKS) +		sbinfo->max_blocks  = ctx->blocks; +	if (ctx->seen & SHMEM_SEEN_INODES) { +		sbinfo->max_inodes  = ctx->inodes; +		sbinfo->free_inodes = ctx->inodes - inodes; +	}  	/*  	 * Preserve previous mempolicy unless mpol remount option was specified.  	 */ -	if (config.mpol) { +	if (ctx->mpol) {  		mpol_put(sbinfo->mpol); -		sbinfo->mpol = config.mpol;	/* transfers initial ref */ +		sbinfo->mpol = ctx->mpol;	/* transfers initial ref */ +		ctx->mpol = NULL;  	} +	spin_unlock(&sbinfo->stat_lock); +	return 0;  out:  	spin_unlock(&sbinfo->stat_lock); -	return error; +	return invalf(fc, "tmpfs: %s", err);  }  static int shmem_show_options(struct seq_file *seq, struct dentry *root) @@ -3547,8 +3616,9 @@ static void shmem_put_super(struct super_block *sb)  	sb->s_fs_info = NULL;  } -int shmem_fill_super(struct super_block *sb, void *data, int silent) +static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)  { +	struct shmem_options *ctx = fc->fs_private;  	struct inode *inode;  	struct shmem_sb_info *sbinfo;  	int err = -ENOMEM; @@ -3559,9 +3629,6 @@ int shmem_fill_super(struct super_block *sb, void *data, int silent)  	if (!sbinfo)  		return -ENOMEM; -	sbinfo->mode = 0777 | S_ISVTX; -	sbinfo->uid = current_fsuid(); -	sbinfo->gid = current_fsgid();  	sb->s_fs_info = sbinfo;  #ifdef CONFIG_TMPFS @@ -3571,12 +3638,10 @@ int shmem_fill_super(struct super_block *sb, void *data, int silent)  	 * but the internal instance is left unlimited.  	 */  	if (!(sb->s_flags & SB_KERNMOUNT)) { -		sbinfo->max_blocks = shmem_default_max_blocks(); -		sbinfo->max_inodes = shmem_default_max_inodes(); -		if (shmem_parse_options(data, sbinfo, false)) { -			err = -EINVAL; -			goto failed; -		} +		if (!(ctx->seen & SHMEM_SEEN_BLOCKS)) +			ctx->blocks = shmem_default_max_blocks(); +		if (!(ctx->seen & SHMEM_SEEN_INODES)) +			ctx->inodes = shmem_default_max_inodes();  	} else {  		sb->s_flags |= SB_NOUSER;  	} @@ -3585,11 +3650,18 @@ int shmem_fill_super(struct super_block *sb, void *data, int silent)  #else  	sb->s_flags |= SB_NOUSER;  #endif +	sbinfo->max_blocks = ctx->blocks; +	sbinfo->free_inodes = sbinfo->max_inodes = ctx->inodes; +	sbinfo->uid = ctx->uid; +	sbinfo->gid = ctx->gid; +	sbinfo->mode = ctx->mode; +	sbinfo->huge = ctx->huge; +	sbinfo->mpol = ctx->mpol; +	ctx->mpol = NULL;  	spin_lock_init(&sbinfo->stat_lock);  	if (percpu_counter_init(&sbinfo->used_blocks, 0, GFP_KERNEL))  		goto failed; -	sbinfo->free_inodes = sbinfo->max_inodes;  	spin_lock_init(&sbinfo->shrinklist_lock);  	INIT_LIST_HEAD(&sbinfo->shrinklist); @@ -3622,6 +3694,31 @@ failed:  	return err;  } +static int shmem_get_tree(struct fs_context *fc) +{ +	return get_tree_nodev(fc, shmem_fill_super); +} + +static void shmem_free_fc(struct fs_context *fc) +{ +	struct shmem_options *ctx = fc->fs_private; + +	if (ctx) { +		mpol_put(ctx->mpol); +		kfree(ctx); +	} +} + +static const struct fs_context_operations shmem_fs_context_ops = { +	.free			= shmem_free_fc, +	.get_tree		= shmem_get_tree, +#ifdef CONFIG_TMPFS +	.parse_monolithic	= shmem_parse_options, +	.parse_param		= shmem_parse_one, +	.reconfigure		= shmem_reconfigure, +#endif +}; +  static struct kmem_cache *shmem_inode_cachep;  static struct inode *shmem_alloc_inode(struct super_block *sb) @@ -3738,7 +3835,6 @@ static const struct super_operations shmem_ops = {  	.destroy_inode	= shmem_destroy_inode,  #ifdef CONFIG_TMPFS  	.statfs		= shmem_statfs, -	.remount_fs	= shmem_remount_fs,  	.show_options	= shmem_show_options,  #endif  	.evict_inode	= shmem_evict_inode, @@ -3759,16 +3855,30 @@ static const struct vm_operations_struct shmem_vm_ops = {  #endif  }; -static struct dentry *shmem_mount(struct file_system_type *fs_type, -	int flags, const char *dev_name, void *data) +int shmem_init_fs_context(struct fs_context *fc)  { -	return mount_nodev(fs_type, flags, data, shmem_fill_super); +	struct shmem_options *ctx; + +	ctx = kzalloc(sizeof(struct shmem_options), GFP_KERNEL); +	if (!ctx) +		return -ENOMEM; + +	ctx->mode = 0777 | S_ISVTX; +	ctx->uid = current_fsuid(); +	ctx->gid = current_fsgid(); + +	fc->fs_private = ctx; +	fc->ops = &shmem_fs_context_ops; +	return 0;  }  static struct file_system_type shmem_fs_type = {  	.owner		= THIS_MODULE,  	.name		= "tmpfs", -	.mount		= shmem_mount, +	.init_fs_context = shmem_init_fs_context, +#ifdef CONFIG_TMPFS +	.parameters	= &shmem_fs_parameters, +#endif  	.kill_sb	= kill_litter_super,  	.fs_flags	= FS_USERNS_MOUNT,  }; @@ -3912,7 +4022,8 @@ bool shmem_huge_enabled(struct vm_area_struct *vma)  static struct file_system_type shmem_fs_type = {  	.name		= "tmpfs", -	.mount		= ramfs_mount, +	.init_fs_context = ramfs_init_fs_context, +	.parameters	= &ramfs_fs_parameters,  	.kill_sb	= kill_litter_super,  	.fs_flags	= FS_USERNS_MOUNT,  };  |