bool    watch_tree;
 } __packed;
 
+struct smb3_notify_info {
+       __u32   completion_filter;
+       bool    watch_tree;
+       __u32   data_len; /* size of notify data below */
+       __u8    notify_data[];
+} __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)
 #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)
 #define CIFS_DUMP_FULL_KEY _IOWR(CIFS_IOCTL_MAGIC, 10, struct smb3_full_key_debug_info)
+#define CIFS_IOC_NOTIFY_INFO _IOWR(CIFS_IOCTL_MAGIC, 11, struct smb3_notify_info)
 #define CIFS_IOC_SHUTDOWN _IOR ('X', 125, __u32)
 
 /*
 
        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);
+                            void __user *pbuf, bool return_changes);
        int (*query_mf_symlink)(unsigned int, struct cifs_tcon *,
                                struct cifs_sb_info *, const unsigned char *,
                                char *, unsigned int *);
 
                        tcon = tlink_tcon(tlink);
                        if (tcon && tcon->ses->server->ops->notify) {
                                rc = tcon->ses->server->ops->notify(xid,
-                                               filep, (void __user *)arg);
+                                               filep, (void __user *)arg,
+                                               false /* no ret data */);
                                cifs_dbg(FYI, "ioctl notify rc %d\n", rc);
                        } else
                                rc = -EOPNOTSUPP;
                        cifs_put_tlink(tlink);
                        break;
+               case CIFS_IOC_NOTIFY_INFO:
+                       if (!S_ISDIR(inode->i_mode)) {
+                               /* Notify can only be done on directories */
+                               rc = -EOPNOTSUPP;
+                               break;
+                       }
+                       cifs_sb = CIFS_SB(inode->i_sb);
+                       tlink = cifs_sb_tlink(cifs_sb);
+                       if (IS_ERR(tlink)) {
+                               rc = PTR_ERR(tlink);
+                               break;
+                       }
+                       tcon = tlink_tcon(tlink);
+                       if (tcon && tcon->ses->server->ops->notify) {
+                               rc = tcon->ses->server->ops->notify(xid,
+                                               filep, (void __user *)arg,
+                                               true /* return details */);
+                               cifs_dbg(FYI, "ioctl notify info rc %d\n", rc);
+                       } else
+                               rc = -EOPNOTSUPP;
+                       cifs_put_tlink(tlink);
+                       break;
                case CIFS_IOC_SHUTDOWN:
                        rc = cifs_shutdown(inode->i_sb, arg);
                        break;
 
 
 static int
 smb3_notify(const unsigned int xid, struct file *pfile,
-           void __user *ioc_buf)
+           void __user *ioc_buf, bool return_changes)
 {
-       struct smb3_notify notify;
+       struct smb3_notify_info notify;
+       struct smb3_notify_info __user *pnotify_buf;
        struct dentry *dentry = pfile->f_path.dentry;
        struct inode *inode = file_inode(pfile);
        struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
        struct cifs_fid fid;
        struct cifs_tcon *tcon;
        const unsigned char *path;
+       char *returned_ioctl_info = NULL;
        void *page = alloc_dentry_path();
        __le16 *utf16_path = NULL;
        u8 oplock = SMB2_OPLOCK_LEVEL_NONE;
        int rc = 0;
+       __u32 ret_len = 0;
 
        path = build_path_from_dentry(dentry, page);
        if (IS_ERR(path)) {
                goto notify_exit;
        }
 
-       if (copy_from_user(¬ify, ioc_buf, sizeof(struct smb3_notify))) {
-               rc = -EFAULT;
-               goto notify_exit;
+       if (return_changes) {
+               if (copy_from_user(¬ify, ioc_buf, sizeof(struct smb3_notify_info))) {
+                       rc = -EFAULT;
+                       goto notify_exit;
+               }
+       } else {
+               if (copy_from_user(¬ify, ioc_buf, sizeof(struct smb3_notify))) {
+                       rc = -EFAULT;
+                       goto notify_exit;
+               }
+               notify.data_len = 0;
        }
 
        tcon = cifs_sb_master_tcon(cifs_sb);
                goto notify_exit;
 
        rc = SMB2_change_notify(xid, tcon, fid.persistent_fid, fid.volatile_fid,
-                               notify.watch_tree, notify.completion_filter);
+                               notify.watch_tree, notify.completion_filter,
+                               notify.data_len, &returned_ioctl_info, &ret_len);
 
        SMB2_close(xid, tcon, fid.persistent_fid, fid.volatile_fid);
 
        cifs_dbg(FYI, "change notify for path %s rc %d\n", path, rc);
-
+       if (return_changes && (ret_len > 0) && (notify.data_len > 0)) {
+               if (ret_len > notify.data_len)
+                       ret_len = notify.data_len;
+               pnotify_buf = (struct smb3_notify_info __user *)ioc_buf;
+               if (copy_to_user(pnotify_buf->notify_data, returned_ioctl_info, ret_len))
+                       rc = -EFAULT;
+               else if (copy_to_user(&pnotify_buf->data_len, &ret_len, sizeof(ret_len)))
+                       rc = -EFAULT;
+       }
+       kfree(returned_ioctl_info);
 notify_exit:
        free_dentry_path(page);
        kfree(utf16_path);
 
 int
 SMB2_change_notify(const unsigned int xid, struct cifs_tcon *tcon,
                u64 persistent_fid, u64 volatile_fid, bool watch_tree,
-               u32 completion_filter)
+               u32 completion_filter, u32 max_out_data_len, char **out_data,
+               u32 *plen /* returned data len */)
 {
        struct cifs_ses *ses = tcon->ses;
        struct TCP_Server_Info *server = cifs_pick_channel(ses);
        struct smb_rqst rqst;
+       struct smb2_change_notify_rsp *smb_rsp;
        struct kvec iov[1];
        struct kvec rsp_iov = {NULL, 0};
        int resp_buftype = CIFS_NO_BUFFER;
 
        memset(&rqst, 0, sizeof(struct smb_rqst));
        memset(&iov, 0, sizeof(iov));
+       if (plen)
+               *plen = 0;
+
        rqst.rq_iov = iov;
        rqst.rq_nvec = 1;
 
                cifs_stats_fail_inc(tcon, SMB2_CHANGE_NOTIFY_HE);
                trace_smb3_notify_err(xid, persistent_fid, tcon->tid, ses->Suid,
                                (u8)watch_tree, completion_filter, rc);
-       } else
+       } else {
                trace_smb3_notify_done(xid, persistent_fid, tcon->tid,
-                               ses->Suid, (u8)watch_tree, completion_filter);
+                       ses->Suid, (u8)watch_tree, completion_filter);
+               /* validate that notify information is plausible */
+               if ((rsp_iov.iov_base == NULL) ||
+                   (rsp_iov.iov_len < sizeof(struct smb2_change_notify_rsp)))
+                       goto cnotify_exit;
+
+               smb_rsp = (struct smb2_change_notify_rsp *)rsp_iov.iov_base;
+
+               smb2_validate_iov(le16_to_cpu(smb_rsp->OutputBufferOffset),
+                               le32_to_cpu(smb_rsp->OutputBufferLength), &rsp_iov,
+                               sizeof(struct file_notify_information));
+
+               *out_data = kmemdup((char *)smb_rsp + le16_to_cpu(smb_rsp->OutputBufferOffset),
+                               le32_to_cpu(smb_rsp->OutputBufferLength), GFP_KERNEL);
+               if (*out_data == NULL) {
+                       rc = -ENOMEM;
+                       goto cnotify_exit;
+               } else
+                       *plen = le32_to_cpu(smb_rsp->OutputBufferLength);
+       }
 
  cnotify_exit:
        if (rqst.rq_iov)
 
 extern void SMB2_ioctl_free(struct smb_rqst *rqst);
 extern int SMB2_change_notify(const unsigned int xid, struct cifs_tcon *tcon,
                        u64 persistent_fid, u64 volatile_fid, bool watch_tree,
-                       u32 completion_filter);
+                       u32 completion_filter, u32 max_out_data_len,
+                       char **out_data, u32 *plen /* returned data len */);
 
 extern int __SMB2_close(const unsigned int xid, struct cifs_tcon *tcon,
                        u64 persistent_fid, u64 volatile_fid,