Commit 3e7d6303 authored by Paulo Alcantara's avatar Paulo Alcantara Committed by Steve French
Browse files

smb: client: add support for O_TMPFILE



Implement O_TMPFILE support for SMB2+ in the CIFS client.

Signed-off-by: default avatarPaulo Alcantara (Red Hat) <pc@manguebit.org>
Cc: David Howells <dhowells@redhat.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: linux-fsdevel@vger.kernel.org
Cc: linux-cifs@vger.kernel.org
Signed-off-by: default avatarSteve French <stfrench@microsoft.com>
parent 30a59ddd
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -124,6 +124,9 @@ MODULE_PARM_DESC(dir_cache_timeout, "Number of seconds to cache directory conten
/* Module-wide total cached dirents (in bytes) across all tcons */
atomic64_t cifs_dircache_bytes_used = ATOMIC64_INIT(0);

atomic_t cifs_sillycounter;
atomic_t cifs_tmpcounter;

/*
 * Write-only module parameter to drop all cached directory entries across
 * all CIFS mounts. Echo a non-zero value to trigger.
@@ -1199,6 +1202,7 @@ MODULE_ALIAS("smb3");
const struct inode_operations cifs_dir_inode_ops = {
	.create = cifs_create,
	.atomic_open = cifs_atomic_open,
	.tmpfile = cifs_tmpfile,
	.lookup = cifs_lookup,
	.getattr = cifs_getattr,
	.unlink = cifs_unlink,
+19 −0
Original line number Diff line number Diff line
@@ -13,6 +13,9 @@

#define ROOT_I 2

extern atomic_t cifs_sillycounter;
extern atomic_t cifs_tmpcounter;

/*
 * ino_t is 32-bits on 32-bit arch. We have to squash the 64-bit value down
 * so that it will fit. We use hash_64 to convert the value to 31 bits, and
@@ -53,6 +56,8 @@ int cifs_create(struct mnt_idmap *idmap, struct inode *inode,
		struct dentry *direntry, umode_t mode, bool excl);
int cifs_atomic_open(struct inode *inode, struct dentry *direntry,
		     struct file *file, unsigned int oflags, umode_t mode);
int cifs_tmpfile(struct mnt_idmap *idmap, struct inode *dir,
		 struct file *file, umode_t mode);
struct dentry *cifs_lookup(struct inode *parent_dir_inode,
			   struct dentry *direntry, unsigned int flags);
int cifs_unlink(struct inode *dir, struct dentry *dentry);
@@ -142,6 +147,20 @@ struct smb3_fs_context;
struct dentry *cifs_smb3_do_mount(struct file_system_type *fs_type, int flags,
				  struct smb3_fs_context *old_ctx);

char *cifs_silly_fullpath(struct dentry *dentry);

#define CIFS_TMPNAME_PREFIX        ".__smbfile_tmp"
#define CIFS_TMPNAME_PREFIX_LEN    ((int)sizeof(CIFS_TMPNAME_PREFIX) - 1)
#define CIFS_TMPNAME_COUNTER_LEN   ((int)sizeof(cifs_tmpcounter) * 2)
#define CIFS_TMPNAME_LEN \
	(CIFS_TMPNAME_PREFIX_LEN + CIFS_TMPNAME_COUNTER_LEN)

#define CIFS_SILLYNAME_PREFIX	    ".__smbfile_silly"
#define CIFS_SILLYNAME_PREFIX_LEN  ((int)sizeof(CIFS_SILLYNAME_PREFIX) - 1)
#define CIFS_SILLYNAME_COUNTER_LEN ((int)sizeof(cifs_sillycounter) * 2)
#define CIFS_SILLYNAME_LEN \
	(CIFS_SILLYNAME_PREFIX_LEN + CIFS_SILLYNAME_COUNTER_LEN)

#ifdef CONFIG_CIFS_NFSD_EXPORT
extern const struct export_operations cifs_export_ops;
#endif /* CONFIG_CIFS_NFSD_EXPORT */
+13 −10
Original line number Diff line number Diff line
@@ -1534,9 +1534,16 @@ int cifs_file_set_size(const unsigned int xid, struct dentry *dentry,
#define CIFS_CACHE_RW_FLG	(CIFS_CACHE_READ_FLG | CIFS_CACHE_WRITE_FLG)
#define CIFS_CACHE_RHW_FLG	(CIFS_CACHE_RW_FLG | CIFS_CACHE_HANDLE_FLG)

/*
 * One of these for each file inode
 */
enum cifs_inode_flags {
	CIFS_INODE_PENDING_OPLOCK_BREAK,	/* oplock break in progress */
	CIFS_INODE_PENDING_WRITERS,		/* Writes in progress */
	CIFS_INODE_FLAG_UNUSED,			/* Unused flag */
	CIFS_INO_DELETE_PENDING,		/* delete pending on server */
	CIFS_INO_INVALID_MAPPING,		/* pagecache is invalid */
	CIFS_INO_LOCK,				/* lock bit for synchronization */
	CIFS_INO_TMPFILE,			/* for O_TMPFILE inodes */
	CIFS_INO_CLOSE_ON_LOCK,			/* Not to defer the close when lock is set */
};

struct cifsInodeInfo {
	struct netfs_inode netfs; /* Netfslib context and vfs inode */
@@ -1554,13 +1561,6 @@ struct cifsInodeInfo {
	__u32 cifsAttrs; /* e.g. DOS archive bit, sparse, compressed, system */
	unsigned int oplock;		/* oplock/lease level we have */
	__u16 epoch;		/* used to track lease state changes */
#define CIFS_INODE_PENDING_OPLOCK_BREAK   (0) /* oplock break in progress */
#define CIFS_INODE_PENDING_WRITERS	  (1) /* Writes in progress */
#define CIFS_INODE_FLAG_UNUSED		  (2) /* Unused flag */
#define CIFS_INO_DELETE_PENDING		  (3) /* delete pending on server */
#define CIFS_INO_INVALID_MAPPING	  (4) /* pagecache is invalid */
#define CIFS_INO_LOCK			  (5) /* lock bit for synchronization */
#define CIFS_INO_CLOSE_ON_LOCK            (7) /* Not to defer the close when lock is set */
	unsigned long flags;
	spinlock_t writers_lock;
	unsigned int writers;		/* Number of writers on this inode */
@@ -2259,6 +2259,7 @@ struct smb2_compound_vars {
	struct kvec qi_iov;
	struct kvec io_iov[SMB2_IOCTL_IOV_SIZE];
	struct kvec si_iov[SMB2_SET_INFO_IOV_SIZE];
	struct kvec hl_iov[SMB2_SET_INFO_IOV_SIZE];
	struct kvec unlink_iov[SMB2_SET_INFO_IOV_SIZE];
	struct kvec rename_iov[SMB2_SET_INFO_IOV_SIZE];
	struct kvec close_iov;
@@ -2383,6 +2384,8 @@ static inline int cifs_open_create_options(unsigned int oflags, int opts)
		opts |= CREATE_WRITE_THROUGH;
	if (oflags & O_DIRECT)
		opts |= CREATE_NO_BUFFER;
	if (oflags & O_TMPFILE)
		opts |= CREATE_DELETE_ON_CLOSE;
	return opts;
}

+2 −1
Original line number Diff line number Diff line
@@ -141,7 +141,8 @@ struct cifsFileInfo *find_writable_file(struct cifsInodeInfo *cifs_inode,
int __cifs_get_writable_file(struct cifsInodeInfo *cifs_inode,
			     unsigned int find_flags, unsigned int open_flags,
			     struct cifsFileInfo **ret_file);
int cifs_get_writable_path(struct cifs_tcon *tcon, const char *name, int flags,
int cifs_get_writable_path(struct cifs_tcon *tcon, const char *name,
			   struct inode *inode, int flags,
			   struct cifsFileInfo **ret_file);
struct cifsFileInfo *__find_readable_file(struct cifsInodeInfo *cifs_inode,
					  unsigned int find_flags,
+267 −46
Original line number Diff line number Diff line
@@ -172,20 +172,44 @@ check_name(struct dentry *direntry, struct cifs_tcon *tcon)
	return 0;
}

static char *alloc_parent_path(struct dentry *dentry, size_t namelen)
{
	struct cifs_sb_info *cifs_sb = CIFS_SB(dentry);
	void *page = alloc_dentry_path();
	const char *path;
	size_t size;
	char *npath;

/* Inode operations in similar order to how they appear in Linux file fs.h */
	path = build_path_from_dentry(dentry->d_parent, page);
	if (IS_ERR(path)) {
		npath = ERR_CAST(path);
		goto out;
	}

	size = strlen(path) + namelen + 2;
	npath = kmalloc(size, GFP_KERNEL);
	if (!npath)
		npath = ERR_PTR(-ENOMEM);
	else
		scnprintf(npath, size, "%s%c", path, CIFS_DIR_SEP(cifs_sb));
out:
	free_dentry_path(page);
	return npath;
}

static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned int xid,
			  struct tcon_link *tlink, unsigned int oflags, umode_t mode, __u32 *oplock,
			  struct cifs_fid *fid, struct cifs_open_info_data *buf)
/* Inode operations in similar order to how they appear in Linux file fs.h */
static int __cifs_do_create(struct inode *dir, struct dentry *direntry,
			    const char *full_path, unsigned int xid,
			    struct tcon_link *tlink, unsigned int oflags,
			    umode_t mode, __u32 *oplock, struct cifs_fid *fid,
			    struct cifs_open_info_data *buf,
			    struct inode **inode)
{
	int rc = -ENOENT;
	int create_options = CREATE_NOT_DIR;
	int desired_access;
	struct cifs_sb_info *cifs_sb = CIFS_SB(inode);
	struct cifs_sb_info *cifs_sb = CIFS_SB(dir);
	struct cifs_tcon *tcon = tlink_tcon(tlink);
	const char *full_path;
	void *page = alloc_dentry_path();
	struct inode *newinode = NULL;
	unsigned int sbflags = cifs_sb_flags(cifs_sb);
	int disposition;
@@ -199,21 +223,15 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned
	if (tcon->ses->server->oplocks)
		*oplock = REQ_OPLOCK;

	full_path = build_path_from_dentry(direntry, page);
	if (IS_ERR(full_path)) {
		rc = PTR_ERR(full_path);
		goto out;
	}

	/* If we're caching, we need to be able to fill in around partial writes. */
	if (cifs_fscache_enabled(inode) && (oflags & O_ACCMODE) == O_WRONLY)
	if (cifs_fscache_enabled(dir) && (oflags & O_ACCMODE) == O_WRONLY)
		rdwr_for_fscache = 1;

#ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY
	if (tcon->unix_ext && cap_unix(tcon->ses) && !tcon->broken_posix_open &&
	    (CIFS_UNIX_POSIX_PATH_OPS_CAP &
			le64_to_cpu(tcon->fsUnixInfo.Capability))) {
		rc = cifs_posix_open(full_path, &newinode, inode->i_sb, mode,
		rc = cifs_posix_open(full_path, &newinode, dir->i_sb, mode,
				     oflags, oplock, &fid->netfid, xid);
		switch (rc) {
		case 0:
@@ -225,8 +243,7 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned
			if (S_ISDIR(newinode->i_mode)) {
				CIFSSMBClose(xid, tcon, fid->netfid);
				iput(newinode);
				rc = -EISDIR;
				goto out;
				return -EISDIR;
			}

			if (!S_ISREG(newinode->i_mode)) {
@@ -269,7 +286,7 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned
			break;

		default:
			goto out;
			return rc;
		}
		/*
		 * fallthrough to retry, using older open call, this is case
@@ -287,26 +304,30 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned
		desired_access |= GENERIC_WRITE;
	if (rdwr_for_fscache == 1)
		desired_access |= GENERIC_READ;
	if (oflags & O_TMPFILE)
		desired_access |= DELETE;

	disposition = FILE_OVERWRITE_IF;
	if ((oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
	if (oflags & O_CREAT) {
		if (oflags & O_EXCL)
			disposition = FILE_CREATE;
	else if ((oflags & (O_CREAT | O_TRUNC)) == (O_CREAT | O_TRUNC))
		else if (oflags & O_TRUNC)
			disposition = FILE_OVERWRITE_IF;
	else if ((oflags & O_CREAT) == O_CREAT)
		disposition = FILE_OPEN_IF;
		else
			disposition = FILE_OPEN_IF;
	} else if (oflags & O_TMPFILE) {
		disposition = FILE_CREATE;
	} else {
		cifs_dbg(FYI, "Create flag not set in create function\n");
	}

	/*
	 * BB add processing to set equivalent of mode - e.g. via CreateX with
	 * ACLs
	 */

	if (!server->ops->open) {
		rc = -ENOSYS;
		goto out;
	}
	if (!server->ops->open)
		return -EOPNOTSUPP;

	create_options |= cifs_open_create_options(oflags, create_options);
	/*
@@ -358,10 +379,10 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned
			rdwr_for_fscache = 2;
			goto retry_open;
		}
		goto out;
		return rc;
	}
	if (rdwr_for_fscache == 2)
		cifs_invalidate_cache(inode, FSCACHE_INVAL_DIO_WRITE);
		cifs_invalidate_cache(dir, FSCACHE_INVAL_DIO_WRITE);

#ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY
	/*
@@ -379,8 +400,8 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned

		if (sbflags & CIFS_MOUNT_SET_UID) {
			args.uid = current_fsuid();
			if (inode->i_mode & S_ISGID)
				args.gid = inode->i_gid;
			if (dir->i_mode & S_ISGID)
				args.gid = dir->i_gid;
			else
				args.gid = current_fsgid();
		} else {
@@ -402,14 +423,14 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned
cifs_create_get_file_info:
	/* server might mask mode so we have to query for it */
	if (tcon->unix_ext)
		rc = cifs_get_inode_info_unix(&newinode, full_path, inode->i_sb,
		rc = cifs_get_inode_info_unix(&newinode, full_path, dir->i_sb,
					      xid);
	else {
#else
	{
#endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */
		/* TODO: Add support for calling POSIX query info here, but passing in fid */
		rc = cifs_get_inode_info(&newinode, full_path, buf, inode->i_sb, xid, fid);
		rc = cifs_get_inode_info(&newinode, full_path, buf, dir->i_sb, xid, fid);
		if (newinode) {
			if (server->ops->set_lease_key)
				server->ops->set_lease_key(newinode, fid);
@@ -418,8 +439,8 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned
					newinode->i_mode = mode;
				if (sbflags & CIFS_MOUNT_SET_UID) {
					newinode->i_uid = current_fsuid();
					if (inode->i_mode & S_ISGID)
						newinode->i_gid = inode->i_gid;
					if (dir->i_mode & S_ISGID)
						newinode->i_gid = dir->i_gid;
					else
						newinode->i_gid = current_fsgid();
				}
@@ -436,17 +457,13 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned
		goto out_err;
	}

	if (newinode)
		if (S_ISDIR(newinode->i_mode)) {
	if (newinode && S_ISDIR(newinode->i_mode)) {
		rc = -EISDIR;
		goto out_err;
	}

	d_drop(direntry);
	d_add(direntry, newinode);

out:
	free_dentry_path(page);
	*inode = newinode;
	return rc;

out_err:
@@ -454,9 +471,41 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned
		server->ops->close(xid, tcon, fid);
	if (newinode)
		iput(newinode);
	goto out;
	return rc;
}

static int cifs_do_create(struct inode *dir, struct dentry *direntry,
			  unsigned int xid, struct tcon_link *tlink,
			  unsigned int oflags, umode_t mode,
			  __u32 *oplock, struct cifs_fid *fid,
			  struct cifs_open_info_data *buf)
{
	void *page = alloc_dentry_path();
	const char *full_path;
	struct inode *inode;
	int rc;

	full_path = build_path_from_dentry(direntry, page);
	if (IS_ERR(full_path)) {
		rc = PTR_ERR(full_path);
	} else {
		rc = __cifs_do_create(dir, direntry, full_path, xid,
				      tlink, oflags, mode, oplock,
				      fid, buf, &inode);
		if (!rc)
			d_add(direntry, inode);
	}
	free_dentry_path(page);
	return rc;
}


/*
 * Look up, create and open a CIFS file.
 *
 * The initial dentry state is in-lookup or hashed-negative.  On success, dentry
 * will become hashed-positive by calling d_drop() & d_add(), respectively.
 */
int
cifs_atomic_open(struct inode *inode, struct dentry *direntry,
		 struct file *file, unsigned int oflags, umode_t mode)
@@ -569,6 +618,12 @@ cifs_atomic_open(struct inode *inode, struct dentry *direntry,
	return rc;
}

/*
 * Create a CIFS file.
 *
 * The initial dentry state is hashed-negative.  On success, dentry will become
 * hashed-positive by calling d_drop() & d_add(), respectively.
 */
int cifs_create(struct mnt_idmap *idmap, struct inode *inode,
		struct dentry *direntry, umode_t mode, bool excl)
{
@@ -959,6 +1014,172 @@ static int cifs_ci_compare(const struct dentry *dentry,
	return 0;
}

static int set_hidden_attr(const unsigned int xid,
			   struct TCP_Server_Info *server,
			   struct file *file)
{
	struct dentry *dentry = file->f_path.dentry;
	struct cifsInodeInfo *cinode = CIFS_I(d_inode(dentry));
	FILE_BASIC_INFO fi = {
		.Attributes = cpu_to_le32(cinode->cifsAttrs |
					  ATTR_HIDDEN),
	};
	void *page = alloc_dentry_path();
	const char *full_path;
	int rc;

	full_path = build_path_from_dentry(dentry, page);
	if (IS_ERR(full_path))
		rc = PTR_ERR(full_path);
	else
		rc =  server->ops->set_file_info(d_inode(dentry),
						 full_path, &fi, xid);
	free_dentry_path(page);
	return rc;
}

/*
 * Create a hidden temporary CIFS file with delete-on-close bit set.
 *
 * The initial dentry state is unhashed-negative.  On success, dentry will
 * become unhashed-positive by calling d_drop() & d_instantiate(), respectively.
 */
int cifs_tmpfile(struct mnt_idmap *idmap, struct inode *dir,
		 struct file *file, umode_t mode)
{
	struct dentry *dentry = file->f_path.dentry;
	struct cifs_sb_info *cifs_sb = CIFS_SB(dir);
	size_t size = CIFS_TMPNAME_LEN + 1;
	int retries = 0, max_retries = 16;
	struct TCP_Server_Info *server;
	struct cifs_pending_open open;
	struct cifsFileInfo *cfile;
	struct cifs_fid fid = {};
	struct tcon_link *tlink;
	struct cifs_tcon *tcon;
	unsigned int sbflags;
	struct inode *inode;
	char *path, *name;
	unsigned int xid;
	__u32 oplock;
	int rc;

	if (unlikely(cifs_forced_shutdown(cifs_sb)))
		return smb_EIO(smb_eio_trace_forced_shutdown);

	tlink = cifs_sb_tlink(cifs_sb);
	if (IS_ERR(tlink))
		return PTR_ERR(tlink);
	tcon = tlink_tcon(tlink);
	server = tcon->ses->server;

	xid = get_xid();

	if (server->vals->protocol_id < SMB20_PROT_ID) {
		cifs_dbg(VFS | ONCE, "O_TMPFILE is supported only in SMB2+\n");
		rc = -EOPNOTSUPP;
		goto out;
	}

	if (server->ops->new_lease_key)
		server->ops->new_lease_key(&fid);
	cifs_add_pending_open(&fid, tlink, &open);

	path = alloc_parent_path(dentry, size - 1);
	if (IS_ERR(path)) {
		cifs_del_pending_open(&open);
		rc = PTR_ERR(path);
		goto out;
	}

	name = path + strlen(path);
	do {
		scnprintf(name, size,
			  CIFS_TMPNAME_PREFIX "%0*x",
			  CIFS_TMPNAME_COUNTER_LEN,
			  atomic_inc_return(&cifs_tmpcounter));
		rc = __cifs_do_create(dir, dentry, path, xid, tlink,
				      file->f_flags, mode, &oplock,
				      &fid, NULL, &inode);
		if (!rc) {
			set_nlink(inode, 0);
			mark_inode_dirty(inode);
			d_mark_tmpfile_name(file, &QSTR_LEN(name, size - 1));
			d_instantiate(dentry, inode);
			break;
		}
	} while (unlikely(rc == -EEXIST) && ++retries < max_retries);

	kfree(path);
	if (rc) {
		cifs_del_pending_open(&open);
		goto out;
	}

	rc = finish_open(file, dentry, generic_file_open);
	if (rc)
		goto err_open;

	sbflags = cifs_sb_flags(cifs_sb);
	if ((file->f_flags & O_DIRECT) && (sbflags & CIFS_MOUNT_STRICT_IO)) {
		if (sbflags & CIFS_MOUNT_NO_BRL)
			file->f_op = &cifs_file_direct_nobrl_ops;
		else
			file->f_op = &cifs_file_direct_ops;
	}

	cfile = cifs_new_fileinfo(&fid, file, tlink, oplock, NULL);
	if (!cfile) {
		rc = -ENOMEM;
		goto err_open;
	}

	rc = set_hidden_attr(xid, server, file);
	if (rc)
		goto out;

	fscache_use_cookie(cifs_inode_cookie(file_inode(file)),
			   file->f_mode & FMODE_WRITE);
out:
	cifs_put_tlink(tlink);
	free_xid(xid);
	return rc;
err_open:
	cifs_del_pending_open(&open);
	if (server->ops->close)
		server->ops->close(xid, tcon, &fid);
	goto out;
}

char *cifs_silly_fullpath(struct dentry *dentry)
{
	unsigned char name[CIFS_SILLYNAME_LEN + 1];
	int retries = 0, max_retries = 16;
	size_t namesize = sizeof(name);
	struct dentry *sdentry = NULL;
	char *path;

	do {
		dput(sdentry);
		scnprintf(name, namesize,
			  CIFS_SILLYNAME_PREFIX "%0*x",
			  CIFS_SILLYNAME_COUNTER_LEN,
			  atomic_inc_return(&cifs_sillycounter));
		sdentry = lookup_noperm(&QSTR(name), dentry->d_parent);
		if (IS_ERR(sdentry))
			return ERR_CAST(sdentry);
		if (d_is_negative(sdentry)) {
			dput(sdentry);
			path = alloc_parent_path(dentry, CIFS_SILLYNAME_LEN);
			if (!IS_ERR(path))
				strcat(path, name);
			return path;
		}
	} while (++retries < max_retries);
	dput(sdentry);
	return ERR_PTR(-EBUSY);
}

const struct dentry_operations cifs_ci_dentry_ops = {
	.d_revalidate = cifs_d_revalidate,
	.d_hash = cifs_ci_hash,
Loading