SMB3: Allow SMB3 FSCTL queries to be sent to server from tools
[linux-2.6-block.git] / fs / cifs / smb2ops.c
index 32dde87feaa94f261c2d00d4ae9f7ebf3d3cb247..1022a3771e140d819e767ba5a75677a88d65f911 100644 (file)
@@ -1330,7 +1330,8 @@ smb2_ioctl_query_info(const unsigned int xid,
        struct smb_query_info __user *pqi;
        int rc = 0;
        int flags = 0;
-       struct smb2_query_info_rsp *rsp = NULL;
+       struct smb2_query_info_rsp *qi_rsp = NULL;
+       struct smb2_ioctl_rsp *io_rsp = NULL;
        void *buffer = NULL;
        struct smb_rqst rqst[3];
        int resp_buftype[3];
@@ -1340,6 +1341,7 @@ smb2_ioctl_query_info(const unsigned int xid,
        u8 oplock = SMB2_OPLOCK_LEVEL_NONE;
        struct cifs_fid fid;
        struct kvec qi_iov[1];
+       struct kvec io_iov[SMB2_IOCTL_IOV_SIZE];
        struct kvec close_iov[1];
 
        memset(rqst, 0, sizeof(rqst));
@@ -1394,8 +1396,16 @@ smb2_ioctl_query_info(const unsigned int xid,
                /* Can eventually relax perm check since server enforces too */
                if (!capable(CAP_SYS_ADMIN))
                        rc = -EPERM;
-               else  /* TBD: Add code to compound FSCTL */
-                       rc = -EOPNOTSUPP;
+               else  {
+                       memset(&io_iov, 0, sizeof(io_iov));
+                       rqst[1].rq_iov = io_iov;
+                       rqst[1].rq_nvec = SMB2_IOCTL_IOV_SIZE;
+
+                       rc = SMB2_ioctl_init(tcon, &rqst[1],
+                                            COMPOUND_FID, COMPOUND_FID,
+                                            qi.info_type, true, NULL,
+                                            0);
+               }
        } else if (qi.flags == PASSTHRU_QUERY_INFO) {
                memset(&qi_iov, 0, sizeof(qi_iov));
                rqst[1].rq_iov = qi_iov;
@@ -1430,24 +1440,44 @@ smb2_ioctl_query_info(const unsigned int xid,
                                resp_buftype, rsp_iov);
        if (rc)
                goto iqinf_exit;
-       pqi = (struct smb_query_info __user *)arg;
-       rsp = (struct smb2_query_info_rsp *)rsp_iov[1].iov_base;
-       if (le32_to_cpu(rsp->OutputBufferLength) < qi.input_buffer_length)
-               qi.input_buffer_length = le32_to_cpu(rsp->OutputBufferLength);
-       if (copy_to_user(&pqi->input_buffer_length, &qi.input_buffer_length,
-                        sizeof(qi.input_buffer_length))) {
-               rc = -EFAULT;
-               goto iqinf_exit;
-       }
-       if (copy_to_user(pqi + 1, rsp->Buffer, qi.input_buffer_length)) {
-               rc = -EFAULT;
-               goto iqinf_exit;
+       if (qi.flags & PASSTHRU_FSCTL) {
+               pqi = (struct smb_query_info __user *)arg;
+               io_rsp = (struct smb2_ioctl_rsp *)rsp_iov[1].iov_base;
+               if (le32_to_cpu(io_rsp->OutputCount) < qi.input_buffer_length)
+                       qi.input_buffer_length = le32_to_cpu(io_rsp->OutputCount);
+               if (copy_to_user(&pqi->input_buffer_length, &qi.input_buffer_length,
+                                sizeof(qi.input_buffer_length))) {
+                       rc = -EFAULT;
+                       goto iqinf_exit;
+               }
+               if (copy_to_user(pqi + 1, &io_rsp[1], qi.input_buffer_length)) {
+                       rc = -EFAULT;
+                       goto iqinf_exit;
+               }
+       } else {
+               pqi = (struct smb_query_info __user *)arg;
+               qi_rsp = (struct smb2_query_info_rsp *)rsp_iov[1].iov_base;
+               if (le32_to_cpu(qi_rsp->OutputBufferLength) < qi.input_buffer_length)
+                       qi.input_buffer_length = le32_to_cpu(qi_rsp->OutputBufferLength);
+               if (copy_to_user(&pqi->input_buffer_length, &qi.input_buffer_length,
+                                sizeof(qi.input_buffer_length))) {
+                       rc = -EFAULT;
+                       goto iqinf_exit;
+               }
+               if (copy_to_user(pqi + 1, qi_rsp->Buffer, qi.input_buffer_length)) {
+                       rc = -EFAULT;
+                       goto iqinf_exit;
+               }
        }
 
  iqinf_exit:
        kfree(buffer);
        SMB2_open_free(&rqst[0]);
-       SMB2_query_info_free(&rqst[1]);
+       if (qi.flags & PASSTHRU_FSCTL)
+               SMB2_ioctl_free(&rqst[1]);
+       else
+               SMB2_query_info_free(&rqst[1]);
+
        SMB2_close_free(&rqst[2]);
        free_rsp_buf(resp_buftype[0], rsp_iov[0].iov_base);
        free_rsp_buf(resp_buftype[1], rsp_iov[1].iov_base);
@@ -2718,6 +2748,7 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
        struct cifsFileInfo *cfile = file->private_data;
        long rc = -EOPNOTSUPP;
        unsigned int xid;
+       __le64 eof;
 
        xid = get_xid();
 
@@ -2777,9 +2808,18 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
                        return rc;
                }
 
-               rc = smb2_set_sparse(xid, tcon, cfile, inode, false);
+               smb2_set_sparse(xid, tcon, cfile, inode, false);
+               rc = 0;
+       } else {
+               smb2_set_sparse(xid, tcon, cfile, inode, false);
+               rc = 0;
+               if (i_size_read(inode) < off + len) {
+                       eof = cpu_to_le64(off + len);
+                       rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid,
+                                         cfile->fid.volatile_fid, cfile->pid,
+                                         &eof);
+               }
        }
-       /* BB: else ... in future add code to extend file and set sparse */
 
        if (rc)
                trace_smb3_falloc_err(xid, cfile->fid.persistent_fid, tcon->tid,
@@ -3767,6 +3807,104 @@ smb2_next_header(char *buf)
        return le32_to_cpu(hdr->NextCommand);
 }
 
+static int
+smb2_make_node(unsigned int xid, struct inode *inode,
+              struct dentry *dentry, struct cifs_tcon *tcon,
+              char *full_path, umode_t mode, dev_t dev)
+{
+       struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
+       int rc = -EPERM;
+       int create_options = CREATE_NOT_DIR | CREATE_OPTION_SPECIAL;
+       FILE_ALL_INFO *buf = NULL;
+       struct cifs_io_parms io_parms;
+       __u32 oplock = 0;
+       struct cifs_fid fid;
+       struct cifs_open_parms oparms;
+       unsigned int bytes_written;
+       struct win_dev *pdev;
+       struct kvec iov[2];
+
+       /*
+        * Check if mounted with mount parm 'sfu' mount parm.
+        * SFU emulation should work with all servers, but only
+        * supports block and char device (no socket & fifo),
+        * and was used by default in earlier versions of Windows
+        */
+       if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_UNX_EMUL))
+               goto out;
+
+       /*
+        * TODO: Add ability to create instead via reparse point. Windows (e.g.
+        * their current NFS server) uses this approach to expose special files
+        * over SMB2/SMB3 and Samba will do this with SMB3.1.1 POSIX Extensions
+        */
+
+       if (!S_ISCHR(mode) && !S_ISBLK(mode))
+               goto out;
+
+       cifs_dbg(FYI, "sfu compat create special file\n");
+
+       buf = kmalloc(sizeof(FILE_ALL_INFO), GFP_KERNEL);
+       if (buf == NULL) {
+               rc = -ENOMEM;
+               goto out;
+       }
+
+       if (backup_cred(cifs_sb))
+               create_options |= CREATE_OPEN_BACKUP_INTENT;
+
+       oparms.tcon = tcon;
+       oparms.cifs_sb = cifs_sb;
+       oparms.desired_access = GENERIC_WRITE;
+       oparms.create_options = create_options;
+       oparms.disposition = FILE_CREATE;
+       oparms.path = full_path;
+       oparms.fid = &fid;
+       oparms.reconnect = false;
+
+       if (tcon->ses->server->oplocks)
+               oplock = REQ_OPLOCK;
+       else
+               oplock = 0;
+       rc = tcon->ses->server->ops->open(xid, &oparms, &oplock, buf);
+       if (rc)
+               goto out;
+
+       /*
+        * BB Do not bother to decode buf since no local inode yet to put
+        * timestamps in, but we can reuse it safely.
+        */
+
+       pdev = (struct win_dev *)buf;
+       io_parms.pid = current->tgid;
+       io_parms.tcon = tcon;
+       io_parms.offset = 0;
+       io_parms.length = sizeof(struct win_dev);
+       iov[1].iov_base = buf;
+       iov[1].iov_len = sizeof(struct win_dev);
+       if (S_ISCHR(mode)) {
+               memcpy(pdev->type, "IntxCHR", 8);
+               pdev->major = cpu_to_le64(MAJOR(dev));
+               pdev->minor = cpu_to_le64(MINOR(dev));
+               rc = tcon->ses->server->ops->sync_write(xid, &fid, &io_parms,
+                                                       &bytes_written, iov, 1);
+       } else if (S_ISBLK(mode)) {
+               memcpy(pdev->type, "IntxBLK", 8);
+               pdev->major = cpu_to_le64(MAJOR(dev));
+               pdev->minor = cpu_to_le64(MINOR(dev));
+               rc = tcon->ses->server->ops->sync_write(xid, &fid, &io_parms,
+                                                       &bytes_written, iov, 1);
+       }
+       tcon->ses->server->ops->close(xid, tcon, &fid);
+       d_drop(dentry);
+
+       /* FIXME: add code here to set EAs */
+out:
+       kfree(buf);
+       return rc;
+}
+
+
 struct smb_version_operations smb20_operations = {
        .compare_fids = smb2_compare_fids,
        .setup_request = smb2_setup_request,
@@ -3861,6 +3999,7 @@ struct smb_version_operations smb20_operations = {
 #endif /* CIFS_ACL */
        .next_header = smb2_next_header,
        .ioctl_query_info = smb2_ioctl_query_info,
+       .make_node = smb2_make_node,
 };
 
 struct smb_version_operations smb21_operations = {
@@ -3959,6 +4098,7 @@ struct smb_version_operations smb21_operations = {
 #endif /* CIFS_ACL */
        .next_header = smb2_next_header,
        .ioctl_query_info = smb2_ioctl_query_info,
+       .make_node = smb2_make_node,
 };
 
 struct smb_version_operations smb30_operations = {
@@ -4066,6 +4206,7 @@ struct smb_version_operations smb30_operations = {
 #endif /* CIFS_ACL */
        .next_header = smb2_next_header,
        .ioctl_query_info = smb2_ioctl_query_info,
+       .make_node = smb2_make_node,
 };
 
 struct smb_version_operations smb311_operations = {
@@ -4174,6 +4315,7 @@ struct smb_version_operations smb311_operations = {
 #endif /* CIFS_ACL */
        .next_header = smb2_next_header,
        .ioctl_query_info = smb2_ioctl_query_info,
+       .make_node = smb2_make_node,
 };
 
 struct smb_version_values smb20_values = {