cifs: add SMB3 change notification support
authorSteve French <stfrench@microsoft.com>
Thu, 6 Feb 2020 12:00:14 +0000 (06:00 -0600)
committerSteve French <stfrench@microsoft.com>
Thu, 6 Feb 2020 15:14:28 +0000 (09:14 -0600)
A commonly used SMB3 feature is change notification, allowing an
app to be notified about changes to a directory. The SMB3
Notify request blocks until the server detects a change to that
directory or its contents that matches the completion flags
that were passed in and the "watch_tree" flag (which indicates
whether subdirectories under this directory should be also
included).  See MS-SMB2 2.2.35 for additional detail.

To use this simply pass in the following structure to ioctl:

 struct __attribute__((__packed__)) smb3_notify {
        uint32_t completion_filter;
        bool    watch_tree;
 } __packed;

 using CIFS_IOC_NOTIFY  0x4005cf09
 or equivalently _IOW(CIFS_IOCTL_MAGIC, 9, struct smb3_notify)

SMB3 change notification is supported by all major servers.
The ioctl will block until the server detects a change to that
directory or its subdirectories (if watch_tree is set).

Signed-off-by: Steve French <stfrench@microsoft.com>
Reviewed-by: Aurelien Aptel <aaptel@suse.com>
Acked-by: Paulo Alcantara (SUSE) <pc@cjr.nz>
fs/cifs/cifs_ioctl.h
fs/cifs/cifsglob.h
fs/cifs/ioctl.c
fs/cifs/smb2ops.c
fs/cifs/smb2pdu.c

index 0f0dc1c..153d5c8 100644 (file)
@@ -65,6 +65,11 @@ struct smb3_key_debug_info {
        __u8    smb3decryptionkey[SMB3_SIGN_KEY_SIZE];
 } __packed;
 
+struct smb3_notify {
+       __u32   completion_filter;
+       bool    watch_tree;
+} __packed;
+
 #define CIFS_IOCTL_MAGIC       0xCF
 #define CIFS_IOC_COPYCHUNK_FILE        _IOW(CIFS_IOCTL_MAGIC, 3, int)
 #define CIFS_IOC_SET_INTEGRITY  _IO(CIFS_IOCTL_MAGIC, 4)
@@ -72,3 +77,4 @@ struct smb3_key_debug_info {
 #define CIFS_ENUMERATE_SNAPSHOTS _IOR(CIFS_IOCTL_MAGIC, 6, struct smb_snapshot_array)
 #define CIFS_QUERY_INFO _IOWR(CIFS_IOCTL_MAGIC, 7, struct smb_query_info)
 #define CIFS_DUMP_KEY _IOWR(CIFS_IOCTL_MAGIC, 8, struct smb3_key_debug_info)
+#define CIFS_IOC_NOTIFY _IOW(CIFS_IOCTL_MAGIC, 9, struct smb3_notify)
index 1205041..de82cfa 100644 (file)
@@ -431,6 +431,8 @@ struct smb_version_operations {
                             struct cifsFileInfo *src_file);
        int (*enum_snapshots)(const unsigned int xid, struct cifs_tcon *tcon,
                             struct cifsFileInfo *src_file, void __user *);
+       int (*notify)(const unsigned int xid, struct file *pfile,
+                            void __user *pbuf);
        int (*query_mf_symlink)(unsigned int, struct cifs_tcon *,
                                struct cifs_sb_info *, const unsigned char *,
                                char *, unsigned int *);
index e4c9350..4a73e63 100644 (file)
@@ -169,6 +169,7 @@ long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg)
        unsigned int xid;
        struct cifsFileInfo *pSMBFile = filep->private_data;
        struct cifs_tcon *tcon;
+       struct cifs_sb_info *cifs_sb;
        __u64   ExtAttrBits = 0;
        __u64   caps;
 
@@ -299,6 +300,21 @@ long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg)
                        else
                                rc = 0;
                        break;
+               case CIFS_IOC_NOTIFY:
+                       if (!S_ISDIR(inode->i_mode)) {
+                               /* Notify can only be done on directories */
+                               rc = -EOPNOTSUPP;
+                               break;
+                       }
+                       cifs_sb = CIFS_SB(inode->i_sb);
+                       tcon = tlink_tcon(cifs_sb_tlink(cifs_sb));
+                       if (tcon && tcon->ses->server->ops->notify) {
+                               rc = tcon->ses->server->ops->notify(xid,
+                                               filep, (void __user *)arg);
+                               cifs_dbg(FYI, "ioctl notify rc %d\n", rc);
+                       } else
+                               rc = -EOPNOTSUPP;
+                       break;
                default:
                        cifs_dbg(FYI, "unsupported ioctl\n");
                        break;
index ac6628e..baa825f 100644 (file)
@@ -2045,6 +2045,66 @@ smb3_enum_snapshots(const unsigned int xid, struct cifs_tcon *tcon,
        return rc;
 }
 
+
+
+static int
+smb3_notify(const unsigned int xid, struct file *pfile,
+           void __user *ioc_buf)
+{
+       struct smb3_notify notify;
+       struct dentry *dentry = pfile->f_path.dentry;
+       struct inode *inode = file_inode(pfile);
+       struct cifs_sb_info *cifs_sb;
+       struct cifs_open_parms oparms;
+       struct cifs_fid fid;
+       struct cifs_tcon *tcon;
+       unsigned char *path = NULL;
+       __le16 *utf16_path = NULL;
+       u8 oplock = SMB2_OPLOCK_LEVEL_NONE;
+       int rc = 0;
+
+       path = build_path_from_dentry(dentry);
+       if (path == NULL)
+               return -ENOMEM;
+
+       cifs_sb = CIFS_SB(inode->i_sb);
+
+       utf16_path = cifs_convert_path_to_utf16(path + 1, cifs_sb);
+       if (utf16_path == NULL) {
+               rc = -ENOMEM;
+               goto notify_exit;
+       }
+
+       if (copy_from_user(&notify, ioc_buf, sizeof(struct smb3_notify))) {
+               rc = -EFAULT;
+               goto notify_exit;
+       }
+
+       tcon = cifs_sb_master_tcon(cifs_sb);
+       oparms.tcon = tcon;
+       oparms.desired_access = FILE_READ_ATTRIBUTES;
+       oparms.disposition = FILE_OPEN;
+       oparms.create_options = cifs_create_options(cifs_sb, 0);
+       oparms.fid = &fid;
+       oparms.reconnect = false;
+
+       rc = SMB2_open(xid, &oparms, utf16_path, &oplock, NULL, NULL, NULL);
+       if (rc)
+               goto notify_exit;
+
+       rc = SMB2_change_notify(xid, tcon, fid.persistent_fid, fid.volatile_fid,
+                               notify.watch_tree, notify.completion_filter);
+
+       SMB2_close(xid, tcon, fid.persistent_fid, fid.volatile_fid);
+
+       cifs_dbg(FYI, "change notify for path %s rc %d\n", path, rc);
+
+notify_exit:
+       kfree(path);
+       kfree(utf16_path);
+       return rc;
+}
+
 static int
 smb2_query_dir_first(const unsigned int xid, struct cifs_tcon *tcon,
                     const char *path, struct cifs_sb_info *cifs_sb,
@@ -4841,6 +4901,7 @@ struct smb_version_operations smb30_operations = {
        .dir_needs_close = smb2_dir_needs_close,
        .fallocate = smb3_fallocate,
        .enum_snapshots = smb3_enum_snapshots,
+       .notify = smb3_notify,
        .init_transform_rq = smb3_init_transform_rq,
        .is_transform_hdr = smb3_is_transform_hdr,
        .receive_transform = smb3_receive_transform,
@@ -4951,6 +5012,7 @@ struct smb_version_operations smb311_operations = {
        .dir_needs_close = smb2_dir_needs_close,
        .fallocate = smb3_fallocate,
        .enum_snapshots = smb3_enum_snapshots,
+       .notify = smb3_notify,
        .init_transform_rq = smb3_init_transform_rq,
        .is_transform_hdr = smb3_is_transform_hdr,
        .receive_transform = smb3_receive_transform,
index 1a732ff..47cce0b 100644 (file)
@@ -3363,6 +3363,7 @@ SMB2_notify_init(const unsigned int xid, struct smb_rqst *rqst,
 
        req->PersistentFileId = persistent_fid;
        req->VolatileFileId = volatile_fid;
+       /* See note 354 of MS-SMB2, 64K max */
        req->OutputBufferLength =
                cpu_to_le32(SMB2_MAX_BUFFER_SIZE - MAX_SMB2_HDR_SIZE);
        req->CompletionFilter = cpu_to_le32(completion_filter);