[WIP] Add O_TMPFILE support for SMB3
diff mbox series

Message ID CAH2r5mvCh+vek8up8dbJ6+StVPPcdQx_-7mT+bQYvSVbuRnZZw@mail.gmail.com
State New
Headers show
Series
  • [WIP] Add O_TMPFILE support for SMB3
Related show

Commit Message

Steve French Dec. 5, 2019, 4:21 a.m. UTC
There are various testcases (xfstests) and apps that require support
for O_TMPFILE and there is a similar concept in SMB3 on the wire ...
but the Linux VFS calls create (for a tmpfile) and then open ... which
is a problem (since tmpfiles would disappear after create/close in SMB
if created with "DELETE_ON_CLOSE" ...)

Here is a work in progress patch that gets part way to O_TMPFILE
support over SMB3 ... any ideas how to get past the problem with the
open/create of O_TMPFILE not being atomic (causing opencreate then
close then open ...)

See attached

Patch
diff mbox series

diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c
index 5d3e63aff253..3f7a18743ab3 100644
--- a/fs/cifs/cifsfs.c
+++ b/fs/cifs/cifsfs.c
@@ -1045,6 +1045,7 @@  const struct inode_operations cifs_dir_inode_ops = {
 	.setattr = cifs_setattr,
 	.symlink = cifs_symlink,
 	.mknod   = cifs_mknod,
+	.tmpfile = smb3_tmpfile,
 	.listxattr = cifs_listxattr,
 };
 
diff --git a/fs/cifs/cifsfs.h b/fs/cifs/cifsfs.h
index b59dc7478130..f3aa78bbf145 100644
--- a/fs/cifs/cifsfs.h
+++ b/fs/cifs/cifsfs.h
@@ -71,6 +71,7 @@  extern struct dentry *cifs_lookup(struct inode *, struct dentry *,
 extern int cifs_unlink(struct inode *dir, struct dentry *dentry);
 extern int cifs_hardlink(struct dentry *, struct inode *, struct dentry *);
 extern int cifs_mknod(struct inode *, struct dentry *, umode_t, dev_t);
+extern int smb3_tmpfile(struct inode *, struct dentry *, umode_t);
 extern int cifs_mkdir(struct inode *, struct dentry *, umode_t);
 extern int cifs_rmdir(struct inode *, struct dentry *);
 extern int cifs_rename2(struct inode *, struct dentry *, struct inode *,
diff --git a/fs/cifs/cifspdu.h b/fs/cifs/cifspdu.h
index 79d842e7240c..a4e12e655e61 100644
--- a/fs/cifs/cifspdu.h
+++ b/fs/cifs/cifspdu.h
@@ -374,6 +374,7 @@ 
 #define CREATE_OPTIONS_MASK     0x007FFFFF
 #define CREATE_OPTION_READONLY	0x10000000
 #define CREATE_OPTION_SPECIAL   0x20000000   /* system. NB not sent over wire */
+#define CREATE_OPTION_TMP	0x40000000   /* set hidden/temporary as attrs */
 
 /* ImpersonationLevel flags */
 #define SECURITY_ANONYMOUS      0
diff --git a/fs/cifs/dir.c b/fs/cifs/dir.c
index f3b79012ff29..6fd95c13cc2a 100644
--- a/fs/cifs/dir.c
+++ b/fs/cifs/dir.c
@@ -220,12 +220,15 @@  check_name(struct dentry *direntry, struct cifs_tcon *tcon)
 }
 
 
+/* minimum supported name length in old systems would be 8.3 names, 12 bytes */
+#define SHORT_NAME_LEN 12  /* 8 + 1 (for dot) + 3 */
+
 /* Inode operations in similar order to how they appear in Linux file fs.h */
 
 static int
 cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned int xid,
 	       struct tcon_link *tlink, unsigned oflags, umode_t mode,
-	       __u32 *oplock, struct cifs_fid *fid)
+	       __u32 *oplock, struct cifs_fid *fid, bool tmp)
 {
 	int rc = -ENOENT;
 	int create_options = CREATE_NOT_DIR;
@@ -243,10 +246,21 @@  cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned int xid,
 	if (tcon->ses->server->oplocks)
 		*oplock = REQ_OPLOCK;
 
-	full_path = build_path_from_dentry(direntry);
+	if (tmp)
+		full_path = kmalloc(SHORT_NAME_LEN + 1 /* for trailing null */,
+				    GFP_KERNEL);
+	else
+		full_path = build_path_from_dentry(direntry);
 	if (!full_path)
 		return -ENOMEM;
 
+	/* fill in 8.3 (maximum size on some old file systems) random name */
+	if (tmp) {
+		scnprintf(full_path, SHORT_NAME_LEN, "%lu", jiffies);
+		full_path[SHORT_NAME_LEN] = 0; /* trailing null */
+		full_path[8] = '.';  /* 8.3 names have . as eighth character */
+	}
+
 	if (tcon->unix_ext && cap_unix(tcon->ses) && !tcon->broken_posix_open &&
 	    (CIFS_UNIX_POSIX_PATH_OPS_CAP &
 			le64_to_cpu(tcon->fsUnixInfo.Capability))) {
@@ -321,6 +335,8 @@  cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned int xid,
 		desired_access |= GENERIC_READ; /* is this too little? */
 	if (OPEN_FMODE(oflags) & FMODE_WRITE)
 		desired_access |= GENERIC_WRITE;
+	if (tmp)
+		desired_access |= DELETE;
 
 	disposition = FILE_OVERWRITE_IF;
 	if ((oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
@@ -448,6 +464,7 @@  cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned int xid,
 	d_add(direntry, newinode);
 
 out:
+	cifs_dbg(VFS, "exiting cifs_do_create with rc %d\n",rc); /* BB REMOVEME */
 	kfree(buf);
 	kfree(full_path);
 	return rc;
@@ -527,7 +544,7 @@  cifs_atomic_open(struct inode *inode, struct dentry *direntry,
 	cifs_add_pending_open(&fid, tlink, &open);
 
 	rc = cifs_do_create(inode, direntry, xid, tlink, oflags, mode,
-			    &oplock, &fid);
+			    &oplock, &fid, false);
 
 	if (rc) {
 		cifs_del_pending_open(&open);
@@ -603,7 +620,47 @@  int cifs_create(struct inode *inode, struct dentry *direntry, umode_t mode,
 		server->ops->new_lease_key(&fid);
 
 	rc = cifs_do_create(inode, direntry, xid, tlink, oflags, mode,
-			    &oplock, &fid);
+			    &oplock, &fid, false);
+	if (!rc && server->ops->close)
+		server->ops->close(xid, tcon, &fid);
+
+
+	cifs_put_tlink(tlink);
+out_free_xid:
+	free_xid(xid);
+	return rc;
+}
+
+/*
+ * O_TMPFILE can be simulated over SMB3 using ATTR_TEMPORARY
+ */
+int smb3_tmpfile(struct inode *dir, struct dentry *dentry, umode_t mode)
+{
+	int rc = -EOPNOTSUPP;
+	unsigned int xid = get_xid();
+	unsigned oflags = O_EXCL | O_CREAT | O_RDWR;
+	struct tcon_link *tlink;
+	struct cifs_tcon *tcon;
+	struct TCP_Server_Info *server;
+	struct cifs_fid fid;
+	__u32 oplock;
+
+	cifs_dbg(FYI, "create tmpfile - dentry = 0x%p with mode 0x%x\n",
+		 dentry, mode);
+
+	tlink = cifs_sb_tlink(CIFS_SB(dir->i_sb));
+	rc = PTR_ERR(tlink);
+	if (IS_ERR(tlink))
+		goto out_free_xid;
+
+	tcon = tlink_tcon(tlink);
+	server = tcon->ses->server;
+
+	if (server->ops->new_lease_key)
+		server->ops->new_lease_key(&fid);
+
+	rc = cifs_do_create(dir, dentry, xid, tlink, oflags, mode,
+			    &oplock, &fid, true);
 	if (!rc && server->ops->close)
 		server->ops->close(xid, tcon, &fid);
 
diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c
index 187a5ce68806..8d191da41973 100644
--- a/fs/cifs/smb2pdu.c
+++ b/fs/cifs/smb2pdu.c
@@ -2455,6 +2455,8 @@  SMB2_open_init(struct cifs_tcon *tcon, struct smb_rqst *rqst, __u8 *oplock,
 		file_attributes |= ATTR_READONLY;
 	if (oparms->create_options & CREATE_OPTION_SPECIAL)
 		file_attributes |= ATTR_SYSTEM;
+	if (oparms->create_options & CREATE_OPTION_TMP)
+		file_attributes |= ATTR_TEMPORARY | ATTR_HIDDEN;
 
 	req->ImpersonationLevel = IL_IMPERSONATION;
 	req->DesiredAccess = cpu_to_le32(oparms->desired_access);