Skip to content
Snippets Groups Projects
shmem.c 76.6 KiB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
	int len;
	struct inode *inode;
	struct page *page;
Linus Torvalds's avatar
Linus Torvalds committed
	char *kaddr;
	struct shmem_inode_info *info;

	len = strlen(symname) + 1;
	if (len > PAGE_CACHE_SIZE)
		return -ENAMETOOLONG;

	inode = shmem_get_inode(dir->i_sb, dir, S_IFLNK|S_IRWXUGO, 0, VM_NORESERVE);
Linus Torvalds's avatar
Linus Torvalds committed
	if (!inode)
		return -ENOSPC;

	error = security_inode_init_security(inode, dir, &dentry->d_name,
					     shmem_initxattrs, NULL);
	if (error) {
		if (error != -EOPNOTSUPP) {
			iput(inode);
			return error;
		}
		error = 0;
	}

Linus Torvalds's avatar
Linus Torvalds committed
	info = SHMEM_I(inode);
	inode->i_size = len-1;
	if (len <= SHORT_SYMLINK_LEN) {
		info->symlink = kmemdup(symname, len, GFP_KERNEL);
		if (!info->symlink) {
			iput(inode);
			return -ENOMEM;
		}
		inode->i_op = &shmem_short_symlink_operations;
Linus Torvalds's avatar
Linus Torvalds committed
	} else {
		error = shmem_getpage(inode, 0, &page, SGP_WRITE, NULL);
		if (error) {
			iput(inode);
			return error;
		}
		inode->i_mapping->a_ops = &shmem_aops;
Linus Torvalds's avatar
Linus Torvalds committed
		inode->i_op = &shmem_symlink_inode_operations;
		kaddr = kmap_atomic(page);
Linus Torvalds's avatar
Linus Torvalds committed
		memcpy(kaddr, symname, len);
		kunmap_atomic(kaddr);
		SetPageUptodate(page);
Linus Torvalds's avatar
Linus Torvalds committed
		set_page_dirty(page);
Linus Torvalds's avatar
Linus Torvalds committed
		page_cache_release(page);
	}
	dir->i_size += BOGO_DIRENT_SIZE;
	dir->i_ctime = dir->i_mtime = CURRENT_TIME;
	d_instantiate(dentry, inode);
	dget(dentry);
	return 0;
}

static void *shmem_follow_short_symlink(struct dentry *dentry, struct nameidata *nd)
Linus Torvalds's avatar
Linus Torvalds committed
{
	nd_set_link(nd, SHMEM_I(dentry->d_inode)->symlink);
static void *shmem_follow_link(struct dentry *dentry, struct nameidata *nd)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct page *page = NULL;
	int error = shmem_getpage(dentry->d_inode, 0, &page, SGP_READ, NULL);
	nd_set_link(nd, error ? ERR_PTR(error) : kmap(page));
	if (page)
		unlock_page(page);
static void shmem_put_link(struct dentry *dentry, struct nameidata *nd, void *cookie)
Linus Torvalds's avatar
Linus Torvalds committed
{
	if (!IS_ERR(nd_get_link(nd))) {
		struct page *page = cookie;
Linus Torvalds's avatar
Linus Torvalds committed
		kunmap(page);
		mark_page_accessed(page);
		page_cache_release(page);
	}
}

#ifdef CONFIG_TMPFS_XATTR
 * Superblocks without xattr inode operations may get some security.* xattr
 * support from the LSM "for free". As soon as we have any other xattrs
 * like ACLs, we also need to implement the security.* handlers at
 * filesystem level, though.
 */

/*
 * Callback for security_inode_init_security() for acquiring xattrs.
 */
static int shmem_initxattrs(struct inode *inode,
			    const struct xattr *xattr_array,
			    void *fs_info)
{
	struct shmem_inode_info *info = SHMEM_I(inode);
	const struct xattr *xattr;
	struct simple_xattr *new_xattr;
	size_t len;

	for (xattr = xattr_array; xattr->name != NULL; xattr++) {
		new_xattr = simple_xattr_alloc(xattr->value, xattr->value_len);
		if (!new_xattr)
			return -ENOMEM;

		len = strlen(xattr->name) + 1;
		new_xattr->name = kmalloc(XATTR_SECURITY_PREFIX_LEN + len,
					  GFP_KERNEL);
		if (!new_xattr->name) {
			kfree(new_xattr);
			return -ENOMEM;
		}

		memcpy(new_xattr->name, XATTR_SECURITY_PREFIX,
		       XATTR_SECURITY_PREFIX_LEN);
		memcpy(new_xattr->name + XATTR_SECURITY_PREFIX_LEN,
		       xattr->name, len);

		simple_xattr_list_add(&info->xattrs, new_xattr);
static const struct xattr_handler *shmem_xattr_handlers[] = {
#ifdef CONFIG_TMPFS_POSIX_ACL
	&posix_acl_access_xattr_handler,
	&posix_acl_default_xattr_handler,

static int shmem_xattr_validate(const char *name)
{
	struct { const char *prefix; size_t len; } arr[] = {
		{ XATTR_SECURITY_PREFIX, XATTR_SECURITY_PREFIX_LEN },
		{ XATTR_TRUSTED_PREFIX, XATTR_TRUSTED_PREFIX_LEN }
	};
	int i;

	for (i = 0; i < ARRAY_SIZE(arr); i++) {
		size_t preflen = arr[i].len;
		if (strncmp(name, arr[i].prefix, preflen) == 0) {
			if (!name[preflen])
				return -EINVAL;
			return 0;
		}
	}
	return -EOPNOTSUPP;
}

static ssize_t shmem_getxattr(struct dentry *dentry, const char *name,
			      void *buffer, size_t size)
{
	struct shmem_inode_info *info = SHMEM_I(dentry->d_inode);
	int err;

	/*
	 * If this is a request for a synthetic attribute in the system.*
	 * namespace use the generic infrastructure to resolve a handler
	 * for it via sb->s_xattr.
	 */
	if (!strncmp(name, XATTR_SYSTEM_PREFIX, XATTR_SYSTEM_PREFIX_LEN))
		return generic_getxattr(dentry, name, buffer, size);

	err = shmem_xattr_validate(name);
	if (err)
		return err;

	return simple_xattr_get(&info->xattrs, name, buffer, size);
}

static int shmem_setxattr(struct dentry *dentry, const char *name,
			  const void *value, size_t size, int flags)
{
	struct shmem_inode_info *info = SHMEM_I(dentry->d_inode);
	int err;

	/*
	 * If this is a request for a synthetic attribute in the system.*
	 * namespace use the generic infrastructure to resolve a handler
	 * for it via sb->s_xattr.
	 */
	if (!strncmp(name, XATTR_SYSTEM_PREFIX, XATTR_SYSTEM_PREFIX_LEN))
		return generic_setxattr(dentry, name, value, size, flags);

	err = shmem_xattr_validate(name);
	if (err)
		return err;

	return simple_xattr_set(&info->xattrs, name, value, size, flags);
}

static int shmem_removexattr(struct dentry *dentry, const char *name)
{
	struct shmem_inode_info *info = SHMEM_I(dentry->d_inode);
	int err;

	/*
	 * If this is a request for a synthetic attribute in the system.*
	 * namespace use the generic infrastructure to resolve a handler
	 * for it via sb->s_xattr.
	 */
	if (!strncmp(name, XATTR_SYSTEM_PREFIX, XATTR_SYSTEM_PREFIX_LEN))
		return generic_removexattr(dentry, name);

	err = shmem_xattr_validate(name);
	if (err)
		return err;

	return simple_xattr_remove(&info->xattrs, name);
}

static ssize_t shmem_listxattr(struct dentry *dentry, char *buffer, size_t size)
{
	struct shmem_inode_info *info = SHMEM_I(dentry->d_inode);
	return simple_xattr_list(&info->xattrs, buffer, size);
}
#endif /* CONFIG_TMPFS_XATTR */

static const struct inode_operations shmem_short_symlink_operations = {
	.readlink	= generic_readlink,
	.follow_link	= shmem_follow_short_symlink,
#ifdef CONFIG_TMPFS_XATTR
	.setxattr	= shmem_setxattr,
	.getxattr	= shmem_getxattr,
	.listxattr	= shmem_listxattr,
	.removexattr	= shmem_removexattr,
#endif
};

static const struct inode_operations shmem_symlink_inode_operations = {
	.readlink	= generic_readlink,
	.follow_link	= shmem_follow_link,
	.put_link	= shmem_put_link,
#ifdef CONFIG_TMPFS_XATTR
	.setxattr	= shmem_setxattr,
	.getxattr	= shmem_getxattr,
	.listxattr	= shmem_listxattr,
	.removexattr	= shmem_removexattr,
static struct dentry *shmem_get_parent(struct dentry *child)
{
	return ERR_PTR(-ESTALE);
}

static int shmem_match(struct inode *ino, void *vfh)
{
	__u32 *fh = vfh;
	__u64 inum = fh[2];
	inum = (inum << 32) | fh[1];
	return ino->i_ino == inum && fh[0] == ino->i_generation;
}

Christoph Hellwig's avatar
Christoph Hellwig committed
static struct dentry *shmem_fh_to_dentry(struct super_block *sb,
		struct fid *fid, int fh_len, int fh_type)
Christoph Hellwig's avatar
Christoph Hellwig committed
	struct dentry *dentry = NULL;
Christoph Hellwig's avatar
Christoph Hellwig committed

	if (fh_len < 3)
		return NULL;
	inum = fid->raw[2];
	inum = (inum << 32) | fid->raw[1];

Christoph Hellwig's avatar
Christoph Hellwig committed
	inode = ilookup5(sb, (unsigned long)(inum + fid->raw[0]),
			shmem_match, fid->raw);
Christoph Hellwig's avatar
Christoph Hellwig committed
		dentry = d_find_alias(inode);
Christoph Hellwig's avatar
Christoph Hellwig committed
	return dentry;
Al Viro's avatar
Al Viro committed
static int shmem_encode_fh(struct inode *inode, __u32 *fh, int *len,
				struct inode *parent)
Al Viro's avatar
Al Viro committed
	if (inode_unhashed(inode)) {
		/* Unfortunately insert_inode_hash is not idempotent,
		 * so as we hash inodes here rather than at creation
		 * time, we need a lock to ensure we only try
		 * to do it once
		 */
		static DEFINE_SPINLOCK(lock);
		spin_lock(&lock);
Al Viro's avatar
Al Viro committed
		if (inode_unhashed(inode))
			__insert_inode_hash(inode,
					    inode->i_ino + inode->i_generation);
		spin_unlock(&lock);
	}

	fh[0] = inode->i_generation;
	fh[1] = inode->i_ino;
	fh[2] = ((__u64)inode->i_ino) >> 32;

	*len = 3;
	return 1;
}

static const struct export_operations shmem_export_ops = {
	.get_parent     = shmem_get_parent,
	.encode_fh      = shmem_encode_fh,
Christoph Hellwig's avatar
Christoph Hellwig committed
	.fh_to_dentry	= shmem_fh_to_dentry,
Andrew Morton's avatar
Andrew Morton committed
static int shmem_parse_options(char *options, struct shmem_sb_info *sbinfo,
			       bool remount)
Linus Torvalds's avatar
Linus Torvalds committed
{
	char *this_char, *value, *rest;
	struct mempolicy *mpol = NULL;
Linus Torvalds's avatar
Linus Torvalds committed

	while (options != NULL) {
		this_char = options;
		for (;;) {
			/*
			 * 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);
			goto error;
Linus Torvalds's avatar
Linus Torvalds committed
		}

		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;
			uid = simple_strtoul(value, &rest, 0);
Linus Torvalds's avatar
Linus Torvalds committed
			if (*rest)
				goto bad_val;
			sbinfo->uid = make_kuid(current_user_ns(), uid);
			if (!uid_valid(sbinfo->uid))
				goto bad_val;
Linus Torvalds's avatar
Linus Torvalds committed
		} else if (!strcmp(this_char,"gid")) {
Andrew Morton's avatar
Andrew Morton committed
			if (remount)
Linus Torvalds's avatar
Linus Torvalds committed
				continue;
			gid = simple_strtoul(value, &rest, 0);
Linus Torvalds's avatar
Linus Torvalds committed
			if (*rest)
				goto bad_val;
			sbinfo->gid = make_kgid(current_user_ns(), gid);
			if (!gid_valid(sbinfo->gid))
				goto bad_val;
		} else if (!strcmp(this_char,"mpol")) {
			mpol_put(mpol);
			mpol = NULL;
			if (mpol_parse_str(value, &mpol))
Linus Torvalds's avatar
Linus Torvalds committed
		} else {
			printk(KERN_ERR "tmpfs: Bad mount option %s\n",
			       this_char);
			goto error;
Linus Torvalds's avatar
Linus Torvalds committed
		}
	}
	sbinfo->mpol = mpol;
Linus Torvalds's avatar
Linus Torvalds committed
	return 0;

bad_val:
	printk(KERN_ERR "tmpfs: Bad value '%s' for mount option '%s'\n",
	       value, this_char);
error:
	mpol_put(mpol);
Linus Torvalds's avatar
Linus Torvalds committed
	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;

	config.mpol = NULL;
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;
	/*
	 * Preserve previous mempolicy unless mpol remount option was specified.
	 */
	if (config.mpol) {
		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
}
static int shmem_show_options(struct seq_file *seq, struct dentry *root)
	struct shmem_sb_info *sbinfo = SHMEM_SB(root->d_sb);
Andrew Morton's avatar
Andrew Morton committed

	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=%03ho", sbinfo->mode);
	if (!uid_eq(sbinfo->uid, GLOBAL_ROOT_UID))
		seq_printf(seq, ",uid=%u",
				from_kuid_munged(&init_user_ns, sbinfo->uid));
	if (!gid_eq(sbinfo->gid, GLOBAL_ROOT_GID))
		seq_printf(seq, ",gid=%u",
				from_kgid_munged(&init_user_ns, 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);
	mpol_put(sbinfo->mpol);
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 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_KERNMOUNT)) {
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;
		}
	} else {
		sb->s_flags |= MS_NOUSER;
Linus Torvalds's avatar
Linus Torvalds committed
	}
	sb->s_export_op = &shmem_export_ops;
	sb->s_flags |= MS_NOSEC;
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;
	sb->s_root = d_make_root(inode);
	if (!sb->s_root)
Linus Torvalds's avatar
Linus Torvalds committed
	return 0;

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 (S_ISREG(inode->i_mode))
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		= shmem_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,
	.fallocate	= shmem_fallocate,
Linus Torvalds's avatar
Linus Torvalds committed
#endif
};

static const struct inode_operations shmem_inode_operations = {
	.setattr	= shmem_setattr,
#ifdef CONFIG_TMPFS_XATTR
	.setxattr	= shmem_setxattr,
	.getxattr	= shmem_getxattr,
	.listxattr	= shmem_listxattr,
	.removexattr	= shmem_removexattr,
	.set_acl	= simple_set_acl,
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,
Linus Torvalds's avatar
Linus Torvalds committed
#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,
	.set_acl	= simple_set_acl,
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,
	.set_acl	= simple_set_acl,
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 = {
	.map_pages	= filemap_map_pages,
Linus Torvalds's avatar
Linus Torvalds committed
#ifdef CONFIG_NUMA
	.set_policy     = shmem_set_policy,
	.get_policy     = shmem_get_policy,
#endif
	.remap_pages	= generic_file_remap_pages,
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,
	.fs_flags	= FS_USERNS_MOUNT,
int __init shmem_init(void)
Linus Torvalds's avatar
Linus Torvalds committed
{
	int error;

	/* If rootfs called this, don't re-init */
	if (shmem_inode_cachep)
		return 0;

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 = kern_mount(&shmem_fs_type);
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.
 */

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,
	.fs_flags	= FS_USERNS_MOUNT,
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_unlock_mapping(struct address_space *mapping)
{
}

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

static struct dentry_operations anon_ops = {
static struct file *__shmem_file_setup(const char *name, loff_t size,
				       unsigned long flags, unsigned int i_flags)
Linus Torvalds's avatar
Linus Torvalds committed
{
Al Viro's avatar
Al Viro committed
	struct file *res;
Linus Torvalds's avatar
Linus Torvalds committed
	struct inode *inode;
	struct path path;
Linus Torvalds's avatar
Linus Torvalds committed
	struct qstr this;

	if (IS_ERR(shm_mnt))
Al Viro's avatar
Al Viro committed
		return ERR_CAST(shm_mnt);
Linus Torvalds's avatar
Linus Torvalds committed

	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);

Al Viro's avatar
Al Viro committed
	res = ERR_PTR(-ENOMEM);
Linus Torvalds's avatar
Linus Torvalds committed
	this.name = name;
	this.len = strlen(name);
	this.hash = 0; /* will go */
	sb = shm_mnt->mnt_sb;
	path.dentry = d_alloc_pseudo(sb, &this);
	if (!path.dentry)
Linus Torvalds's avatar
Linus Torvalds committed
		goto put_memory;
	d_set_d_op(path.dentry, &anon_ops);
	path.mnt = mntget(shm_mnt);
Linus Torvalds's avatar
Linus Torvalds committed

Al Viro's avatar
Al Viro committed
	res = ERR_PTR(-ENOSPC);
	inode = shmem_get_inode(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

	inode->i_flags |= i_flags;
	d_instantiate(path.dentry, inode);
Linus Torvalds's avatar
Linus Torvalds committed
	inode->i_size = size;
	clear_nlink(inode);	/* It is unlinked */
Al Viro's avatar
Al Viro committed
	res = ERR_PTR(ramfs_nommu_expand_for_mapping(inode, size));
	if (IS_ERR(res))
		goto put_dentry;

Al Viro's avatar
Al Viro committed
	res = alloc_file(&path, FMODE_WRITE | FMODE_READ,
		  &shmem_file_operations);
Al Viro's avatar
Al Viro committed
	if (IS_ERR(res))
		goto put_dentry;

Al Viro's avatar
Al Viro committed
	return res;
Linus Torvalds's avatar
Linus Torvalds committed

put_dentry:
	path_put(&path);
Linus Torvalds's avatar
Linus Torvalds committed
put_memory:
	shmem_unacct_size(flags, size);
Al Viro's avatar
Al Viro committed
	return res;
Linus Torvalds's avatar
Linus Torvalds committed
}

/**
 * shmem_kernel_file_setup - get an unlinked file living in tmpfs which must be
 * 	kernel internal.  There will be NO LSM permission checks against the
 * 	underlying inode.  So users of this interface must do LSM checks at a
 * 	higher layer.  The one user is the big_key implementation.  LSM checks
 * 	are provided at the key level rather than the inode level.
 * @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
 */
struct file *shmem_kernel_file_setup(const char *name, loff_t size, unsigned long flags)
{
	return __shmem_file_setup(name, size, flags, S_PRIVATE);
}

/**
 * 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
 */
struct file *shmem_file_setup(const char *name, loff_t size, unsigned long flags)
{
	return __shmem_file_setup(name, size, flags, 0);
}
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;
	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);