Skip to content
Snippets Groups Projects
shmem.c 63.8 KiB
Newer Older
			/*
			 * NUL-terminate this option: unfortunately,
			 * mount options form a comma-separated list,
			 * but mpol's nodelist may also contain commas.
			 */
			options = strchr(options, ',');
			if (options == NULL)
				break;
			options++;
			if (!isdigit(*options)) {
				options[-1] = '\0';
				break;
			}
		}
Linus Torvalds's avatar
Linus Torvalds committed
		if (!*this_char)
			continue;
		if ((value = strchr(this_char,'=')) != NULL) {
			*value++ = 0;
		} else {
			printk(KERN_ERR
			    "tmpfs: No value for mount option '%s'\n",
			    this_char);
			return 1;
		}

		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 (*rest)
				goto bad_val;
Andrew Morton's avatar
Andrew Morton committed
			sbinfo->max_blocks =
				DIV_ROUND_UP(size, PAGE_CACHE_SIZE);
Linus Torvalds's avatar
Linus Torvalds committed
		} else if (!strcmp(this_char,"nr_blocks")) {
Andrew Morton's avatar
Andrew Morton committed
			sbinfo->max_blocks = memparse(value, &rest);
Linus Torvalds's avatar
Linus Torvalds committed
			if (*rest)
				goto bad_val;
		} else if (!strcmp(this_char,"nr_inodes")) {
Andrew Morton's avatar
Andrew Morton committed
			sbinfo->max_inodes = memparse(value, &rest);
Linus Torvalds's avatar
Linus Torvalds committed
			if (*rest)
				goto bad_val;
		} else if (!strcmp(this_char,"mode")) {
Andrew Morton's avatar
Andrew Morton committed
			if (remount)
Linus Torvalds's avatar
Linus Torvalds committed
				continue;
Andrew Morton's avatar
Andrew Morton committed
			sbinfo->mode = simple_strtoul(value, &rest, 8) & 07777;
Linus Torvalds's avatar
Linus Torvalds committed
			if (*rest)
				goto bad_val;
		} else if (!strcmp(this_char,"uid")) {
Andrew Morton's avatar
Andrew Morton committed
			if (remount)
Linus Torvalds's avatar
Linus Torvalds committed
				continue;
Andrew Morton's avatar
Andrew Morton committed
			sbinfo->uid = simple_strtoul(value, &rest, 0);
Linus Torvalds's avatar
Linus Torvalds committed
			if (*rest)
				goto bad_val;
		} else if (!strcmp(this_char,"gid")) {
Andrew Morton's avatar
Andrew Morton committed
			if (remount)
Linus Torvalds's avatar
Linus Torvalds committed
				continue;
Andrew Morton's avatar
Andrew Morton committed
			sbinfo->gid = simple_strtoul(value, &rest, 0);
Linus Torvalds's avatar
Linus Torvalds committed
			if (*rest)
				goto bad_val;
		} else if (!strcmp(this_char,"mpol")) {
			if (mpol_parse_str(value, &sbinfo->mpol, 1))
Linus Torvalds's avatar
Linus Torvalds committed
		} else {
			printk(KERN_ERR "tmpfs: Bad mount option %s\n",
			       this_char);
			return 1;
		}
	}
	return 0;

bad_val:
	printk(KERN_ERR "tmpfs: Bad value '%s' for mount option '%s'\n",
	       value, this_char);
	return 1;

}

static int shmem_remount_fs(struct super_block *sb, int *flags, char *data)
{
	struct shmem_sb_info *sbinfo = SHMEM_SB(sb);
Andrew Morton's avatar
Andrew Morton committed
	struct shmem_sb_info config = *sbinfo;
	unsigned long inodes;
	int error = -EINVAL;

Andrew Morton's avatar
Andrew Morton committed
	if (shmem_parse_options(data, &config, true))
		return error;
Linus Torvalds's avatar
Linus Torvalds committed

	spin_lock(&sbinfo->stat_lock);
	inodes = sbinfo->max_inodes - sbinfo->free_inodes;
	if (percpu_counter_compare(&sbinfo->used_blocks, config.max_blocks) > 0)
Andrew Morton's avatar
Andrew Morton committed
	if (config.max_inodes < inodes)
	 * 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.
	 */
Andrew Morton's avatar
Andrew Morton committed
	if (config.max_blocks && !sbinfo->max_blocks)
Andrew Morton's avatar
Andrew Morton committed
	if (config.max_inodes && !sbinfo->max_inodes)
Andrew Morton's avatar
Andrew Morton committed
	sbinfo->max_blocks  = config.max_blocks;
	sbinfo->max_inodes  = config.max_inodes;
	sbinfo->free_inodes = config.max_inodes - inodes;

	mpol_put(sbinfo->mpol);
	sbinfo->mpol        = config.mpol;	/* transfers initial ref */
out:
	spin_unlock(&sbinfo->stat_lock);
	return error;
Linus Torvalds's avatar
Linus Torvalds committed
}
Andrew Morton's avatar
Andrew Morton committed

static int shmem_show_options(struct seq_file *seq, struct vfsmount *vfs)
{
	struct shmem_sb_info *sbinfo = SHMEM_SB(vfs->mnt_sb);

	if (sbinfo->max_blocks != shmem_default_max_blocks())
		seq_printf(seq, ",size=%luk",
			sbinfo->max_blocks << (PAGE_CACHE_SHIFT - 10));
	if (sbinfo->max_inodes != shmem_default_max_inodes())
		seq_printf(seq, ",nr_inodes=%lu", sbinfo->max_inodes);
	if (sbinfo->mode != (S_IRWXUGO | S_ISVTX))
		seq_printf(seq, ",mode=%03o", sbinfo->mode);
	if (sbinfo->uid != 0)
		seq_printf(seq, ",uid=%u", sbinfo->uid);
	if (sbinfo->gid != 0)
		seq_printf(seq, ",gid=%u", sbinfo->gid);
	shmem_show_mpol(seq, sbinfo->mpol);
Andrew Morton's avatar
Andrew Morton committed
	return 0;
}
#endif /* CONFIG_TMPFS */
Linus Torvalds's avatar
Linus Torvalds committed

static void shmem_put_super(struct super_block *sb)
{
	struct shmem_sb_info *sbinfo = SHMEM_SB(sb);

	percpu_counter_destroy(&sbinfo->used_blocks);
	kfree(sbinfo);
Linus Torvalds's avatar
Linus Torvalds committed
	sb->s_fs_info = NULL;
}

int shmem_fill_super(struct super_block *sb, void *data, int silent)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct inode *inode;
	struct dentry *root;
	struct shmem_sb_info *sbinfo;
Andrew Morton's avatar
Andrew Morton committed
	int err = -ENOMEM;

	/* Round up to L1_CACHE_BYTES to resist false sharing */
	sbinfo = kzalloc(max((int)sizeof(struct shmem_sb_info),
Andrew Morton's avatar
Andrew Morton committed
				L1_CACHE_BYTES), GFP_KERNEL);
	if (!sbinfo)
		return -ENOMEM;

	sbinfo->mode = S_IRWXUGO | S_ISVTX;
	sbinfo->uid = current_fsuid();
	sbinfo->gid = current_fsgid();
Andrew Morton's avatar
Andrew Morton committed
	sb->s_fs_info = sbinfo;
Linus Torvalds's avatar
Linus Torvalds committed

#ifdef CONFIG_TMPFS
Linus Torvalds's avatar
Linus Torvalds committed
	/*
	 * Per default we only allow half of the physical ram per
	 * tmpfs instance, limiting inodes to one per page of lowmem;
	 * but the internal instance is left unlimited.
	 */
	if (!(sb->s_flags & MS_NOUSER)) {
Andrew Morton's avatar
Andrew Morton committed
		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;
		}
Linus Torvalds's avatar
Linus Torvalds committed
	}
	sb->s_export_op = &shmem_export_ops;
Linus Torvalds's avatar
Linus Torvalds committed
#else
	sb->s_flags |= MS_NOUSER;
#endif

	spin_lock_init(&sbinfo->stat_lock);
	if (percpu_counter_init(&sbinfo->used_blocks, 0))
		goto failed;
Andrew Morton's avatar
Andrew Morton committed
	sbinfo->free_inodes = sbinfo->max_inodes;
	sb->s_maxbytes = MAX_LFS_FILESIZE;
Linus Torvalds's avatar
Linus Torvalds committed
	sb->s_blocksize = PAGE_CACHE_SIZE;
	sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
	sb->s_magic = TMPFS_MAGIC;
	sb->s_op = &shmem_ops;
#ifdef CONFIG_TMPFS_XATTR
	sb->s_xattr = shmem_xattr_handlers;
#endif
#ifdef CONFIG_TMPFS_POSIX_ACL
	sb->s_flags |= MS_POSIXACL;
#endif
	inode = shmem_get_inode(sb, NULL, S_IFDIR | sbinfo->mode, 0, VM_NORESERVE);
Linus Torvalds's avatar
Linus Torvalds committed
	if (!inode)
		goto failed;
Andrew Morton's avatar
Andrew Morton committed
	inode->i_uid = sbinfo->uid;
	inode->i_gid = sbinfo->gid;
Linus Torvalds's avatar
Linus Torvalds committed
	root = d_alloc_root(inode);
	if (!root)
		goto failed_iput;
	sb->s_root = root;
	return 0;

failed_iput:
	iput(inode);
failed:
	shmem_put_super(sb);
	return err;
}

static struct kmem_cache *shmem_inode_cachep;
Linus Torvalds's avatar
Linus Torvalds committed

static struct inode *shmem_alloc_inode(struct super_block *sb)
{
	struct shmem_inode_info *info;
	info = kmem_cache_alloc(shmem_inode_cachep, GFP_KERNEL);
	if (!info)
Linus Torvalds's avatar
Linus Torvalds committed
		return NULL;
	return &info->vfs_inode;
static void shmem_destroy_callback(struct rcu_head *head)
Nick Piggin's avatar
Nick Piggin committed
{
	struct inode *inode = container_of(head, struct inode, i_rcu);
	kmem_cache_free(shmem_inode_cachep, SHMEM_I(inode));
}

Linus Torvalds's avatar
Linus Torvalds committed
static void shmem_destroy_inode(struct inode *inode)
{
	if ((inode->i_mode & S_IFMT) == S_IFREG)
Linus Torvalds's avatar
Linus Torvalds committed
		mpol_free_shared_policy(&SHMEM_I(inode)->policy);
	call_rcu(&inode->i_rcu, shmem_destroy_callback);
static void shmem_init_inode(void *foo)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct shmem_inode_info *info = foo;
	inode_init_once(&info->vfs_inode);
static int shmem_init_inodecache(void)
Linus Torvalds's avatar
Linus Torvalds committed
{
	shmem_inode_cachep = kmem_cache_create("shmem_inode_cache",
				sizeof(struct shmem_inode_info),
				0, SLAB_PANIC, shmem_init_inode);
Linus Torvalds's avatar
Linus Torvalds committed
	return 0;
}

static void shmem_destroy_inodecache(void)
Linus Torvalds's avatar
Linus Torvalds committed
{
	kmem_cache_destroy(shmem_inode_cachep);
static const struct address_space_operations shmem_aops = {
Linus Torvalds's avatar
Linus Torvalds committed
	.writepage	= shmem_writepage,
	.set_page_dirty	= __set_page_dirty_no_writeback,
Linus Torvalds's avatar
Linus Torvalds committed
#ifdef CONFIG_TMPFS
Nick Piggin's avatar
Nick Piggin committed
	.write_begin	= shmem_write_begin,
	.write_end	= shmem_write_end,
Linus Torvalds's avatar
Linus Torvalds committed
#endif
	.migratepage	= migrate_page,
	.error_remove_page = generic_error_remove_page,
static const struct file_operations shmem_file_operations = {
Linus Torvalds's avatar
Linus Torvalds committed
	.mmap		= shmem_mmap,
#ifdef CONFIG_TMPFS
	.llseek		= generic_file_llseek,
Hugh Dickins's avatar
Hugh Dickins committed
	.read		= do_sync_read,
	.write		= do_sync_write,
Hugh Dickins's avatar
Hugh Dickins committed
	.aio_read	= shmem_file_aio_read,
	.aio_write	= generic_file_aio_write,
	.fsync		= noop_fsync,
	.splice_read	= shmem_file_splice_read,
	.splice_write	= generic_file_splice_write,
Linus Torvalds's avatar
Linus Torvalds committed
#endif
};

static const struct inode_operations shmem_inode_operations = {
	.setattr	= shmem_setattr,
	.truncate_range	= shmem_truncate_range,
#ifdef CONFIG_TMPFS_XATTR
	.setxattr	= shmem_setxattr,
	.getxattr	= shmem_getxattr,
	.listxattr	= shmem_listxattr,
	.removexattr	= shmem_removexattr,
#endif
static const struct inode_operations shmem_dir_inode_operations = {
Linus Torvalds's avatar
Linus Torvalds committed
#ifdef CONFIG_TMPFS
	.create		= shmem_create,
	.lookup		= simple_lookup,
	.link		= shmem_link,
	.unlink		= shmem_unlink,
	.symlink	= shmem_symlink,
	.mkdir		= shmem_mkdir,
	.rmdir		= shmem_rmdir,
	.mknod		= shmem_mknod,
	.rename		= shmem_rename,
#endif
#ifdef CONFIG_TMPFS_XATTR
	.setxattr	= shmem_setxattr,
	.getxattr	= shmem_getxattr,
	.listxattr	= shmem_listxattr,
	.removexattr	= shmem_removexattr,
#endif
#ifdef CONFIG_TMPFS_POSIX_ACL
	.setattr	= shmem_setattr,
static const struct inode_operations shmem_special_inode_operations = {
#ifdef CONFIG_TMPFS_XATTR
	.setxattr	= shmem_setxattr,
	.getxattr	= shmem_getxattr,
	.listxattr	= shmem_listxattr,
	.removexattr	= shmem_removexattr,
#endif
#ifdef CONFIG_TMPFS_POSIX_ACL
	.setattr	= shmem_setattr,
static const struct super_operations shmem_ops = {
Linus Torvalds's avatar
Linus Torvalds committed
	.alloc_inode	= shmem_alloc_inode,
	.destroy_inode	= shmem_destroy_inode,
#ifdef CONFIG_TMPFS
	.statfs		= shmem_statfs,
	.remount_fs	= shmem_remount_fs,
Andrew Morton's avatar
Andrew Morton committed
	.show_options	= shmem_show_options,
Linus Torvalds's avatar
Linus Torvalds committed
#endif
	.evict_inode	= shmem_evict_inode,
Linus Torvalds's avatar
Linus Torvalds committed
	.drop_inode	= generic_delete_inode,
	.put_super	= shmem_put_super,
};

static const struct vm_operations_struct shmem_vm_ops = {
Linus Torvalds's avatar
Linus Torvalds committed
#ifdef CONFIG_NUMA
	.set_policy     = shmem_set_policy,
	.get_policy     = shmem_get_policy,
#endif
};

Al Viro's avatar
Al Viro committed
static struct dentry *shmem_mount(struct file_system_type *fs_type,
	int flags, const char *dev_name, void *data)
Linus Torvalds's avatar
Linus Torvalds committed
{
Al Viro's avatar
Al Viro committed
	return mount_nodev(fs_type, flags, data, shmem_fill_super);
static struct file_system_type shmem_fs_type = {
Linus Torvalds's avatar
Linus Torvalds committed
	.owner		= THIS_MODULE,
	.name		= "tmpfs",
Al Viro's avatar
Al Viro committed
	.mount		= shmem_mount,
Linus Torvalds's avatar
Linus Torvalds committed
	.kill_sb	= kill_litter_super,
};

int __init shmem_init(void)
Linus Torvalds's avatar
Linus Torvalds committed
{
	int error;

Peter Zijlstra's avatar
Peter Zijlstra committed
	error = bdi_init(&shmem_backing_dev_info);
	if (error)
		goto out4;

	error = shmem_init_inodecache();
Linus Torvalds's avatar
Linus Torvalds committed
	if (error)
		goto out3;

	error = register_filesystem(&shmem_fs_type);
Linus Torvalds's avatar
Linus Torvalds committed
	if (error) {
		printk(KERN_ERR "Could not register tmpfs\n");
		goto out2;
	}
	shm_mnt = vfs_kern_mount(&shmem_fs_type, MS_NOUSER,
				 shmem_fs_type.name, NULL);
Linus Torvalds's avatar
Linus Torvalds committed
	if (IS_ERR(shm_mnt)) {
		error = PTR_ERR(shm_mnt);
		printk(KERN_ERR "Could not kern_mount tmpfs\n");
		goto out1;
	}
	return 0;

out1:
	unregister_filesystem(&shmem_fs_type);
Linus Torvalds's avatar
Linus Torvalds committed
out2:
	shmem_destroy_inodecache();
Linus Torvalds's avatar
Linus Torvalds committed
out3:
Peter Zijlstra's avatar
Peter Zijlstra committed
	bdi_destroy(&shmem_backing_dev_info);
out4:
Linus Torvalds's avatar
Linus Torvalds committed
	shm_mnt = ERR_PTR(error);
	return error;
}

#else /* !CONFIG_SHMEM */

/*
 * tiny-shmem: simple shmemfs and tmpfs using ramfs code
 *
 * This is intended for small system where the benefits of the full
 * shmem code (swap-backed and resource-limited) are outweighed by
 * their complexity. On systems without swap this code should be
 * effectively equivalent, but much lighter weight.
 */

#include <linux/ramfs.h>

static struct file_system_type shmem_fs_type = {
	.name		= "tmpfs",
Al Viro's avatar
Al Viro committed
	.mount		= ramfs_mount,
	.kill_sb	= kill_litter_super,
};

int __init shmem_init(void)
	BUG_ON(register_filesystem(&shmem_fs_type) != 0);
	shm_mnt = kern_mount(&shmem_fs_type);
	BUG_ON(IS_ERR(shm_mnt));

	return 0;
}

int shmem_unuse(swp_entry_t swap, struct page *page)
Hugh Dickins's avatar
Hugh Dickins committed
int shmem_lock(struct file *file, int lock, struct user_struct *user)
{
	return 0;
}

void shmem_truncate_range(struct inode *inode, loff_t lstart, loff_t lend)
	truncate_inode_pages_range(inode->i_mapping, lstart, lend);
}
EXPORT_SYMBOL_GPL(shmem_truncate_range);

#define shmem_vm_ops				generic_file_vm_ops
#define shmem_file_operations			ramfs_file_operations
#define shmem_get_inode(sb, dir, mode, dev, flags)	ramfs_get_inode(sb, dir, mode, dev)
#define shmem_acct_size(flags, size)		0
#define shmem_unacct_size(flags, size)		do {} while (0)

#endif /* CONFIG_SHMEM */

/* common code */
Linus Torvalds's avatar
Linus Torvalds committed

Linus Torvalds's avatar
Linus Torvalds committed
 * shmem_file_setup - get an unlinked file living in tmpfs
 * @name: name for dentry (to be seen in /proc/<pid>/maps
 * @size: size to be set for the file
 * @flags: VM_NORESERVE suppresses pre-accounting of the entire object size
Linus Torvalds's avatar
Linus Torvalds committed
 */
struct file *shmem_file_setup(const char *name, loff_t size, unsigned long flags)
Linus Torvalds's avatar
Linus Torvalds committed
{
	int error;
	struct file *file;
	struct inode *inode;
	struct path path;
	struct dentry *root;
Linus Torvalds's avatar
Linus Torvalds committed
	struct qstr this;

	if (IS_ERR(shm_mnt))
		return (void *)shm_mnt;

	if (size < 0 || size > MAX_LFS_FILESIZE)
Linus Torvalds's avatar
Linus Torvalds committed
		return ERR_PTR(-EINVAL);

	if (shmem_acct_size(flags, size))
		return ERR_PTR(-ENOMEM);

	error = -ENOMEM;
	this.name = name;
	this.len = strlen(name);
	this.hash = 0; /* will go */
	root = shm_mnt->mnt_root;
	path.dentry = d_alloc(root, &this);
	if (!path.dentry)
Linus Torvalds's avatar
Linus Torvalds committed
		goto put_memory;
	path.mnt = mntget(shm_mnt);
Linus Torvalds's avatar
Linus Torvalds committed

	error = -ENOSPC;
	inode = shmem_get_inode(root->d_sb, NULL, S_IFREG | S_IRWXUGO, 0, flags);
Linus Torvalds's avatar
Linus Torvalds committed
	if (!inode)
		goto put_dentry;
Linus Torvalds's avatar
Linus Torvalds committed

	d_instantiate(path.dentry, inode);
Linus Torvalds's avatar
Linus Torvalds committed
	inode->i_size = size;
	clear_nlink(inode);	/* It is unlinked */
#ifndef CONFIG_MMU
	error = ramfs_nommu_expand_for_mapping(inode, size);
	if (error)
		goto put_dentry;

	error = -ENFILE;
	file = alloc_file(&path, FMODE_WRITE | FMODE_READ,
		  &shmem_file_operations);
	if (!file)
		goto put_dentry;

Linus Torvalds's avatar
Linus Torvalds committed
	return file;

put_dentry:
	path_put(&path);
Linus Torvalds's avatar
Linus Torvalds committed
put_memory:
	shmem_unacct_size(flags, size);
	return ERR_PTR(error);
}
EXPORT_SYMBOL_GPL(shmem_file_setup);
Linus Torvalds's avatar
Linus Torvalds committed

Linus Torvalds's avatar
Linus Torvalds committed
 * shmem_zero_setup - setup a shared anonymous mapping
 * @vma: the vma to be mmapped is prepared by do_mmap_pgoff
 */
int shmem_zero_setup(struct vm_area_struct *vma)
{
	struct file *file;
	loff_t size = vma->vm_end - vma->vm_start;

	file = shmem_file_setup("dev/zero", size, vma->vm_flags);
	if (IS_ERR(file))
		return PTR_ERR(file);

	if (vma->vm_file)
		fput(vma->vm_file);
	vma->vm_file = file;
	vma->vm_ops = &shmem_vm_ops;
	vma->vm_flags |= VM_CAN_NONLINEAR;
Linus Torvalds's avatar
Linus Torvalds committed
	return 0;
}

/**
 * shmem_read_mapping_page_gfp - read into page cache, using specified page allocation flags.
 * @mapping:	the page's address_space
 * @index:	the page index
 * @gfp:	the page allocator flags to use if allocating
 *
 * This behaves as a tmpfs "read_cache_page_gfp(mapping, index, gfp)",
 * with any new page allocations done using the specified allocation flags.
 * But read_cache_page_gfp() uses the ->readpage() method: which does not
 * suit tmpfs, since it may have pages in swapcache, and needs to find those
 * for itself; although drivers/gpu/drm i915 and ttm rely upon this support.
 *
 * i915_gem_object_get_pages_gtt() mixes __GFP_NORETRY | __GFP_NOWARN in
 * with the mapping_gfp_mask(), to avoid OOMing the machine unnecessarily.
 */
struct page *shmem_read_mapping_page_gfp(struct address_space *mapping,
					 pgoff_t index, gfp_t gfp)
{
#ifdef CONFIG_SHMEM
	struct inode *inode = mapping->host;
	struct page *page;
	int error;

	BUG_ON(mapping->a_ops != &shmem_aops);
	error = shmem_getpage_gfp(inode, index, &page, SGP_CACHE, gfp, NULL);
	if (error)
		page = ERR_PTR(error);
	else
		unlock_page(page);
	return page;
#else
	/*
	 * The tiny !SHMEM case uses ramfs without swap
	 */
	return read_cache_page_gfp(mapping, index, gfp);
}
EXPORT_SYMBOL_GPL(shmem_read_mapping_page_gfp);