Commit e3e94634 authored by Steve French's avatar Steve French

smb3: improve SMB3 change notification support

Change notification is a commonly supported feature by most servers,
but the current ioctl to request notification when a directory is
changed does not return the information about what changed
(even though it is returned by the server in the SMB3 change
notify response), it simply returns when there is a change.

This ioctl improves upon CIFS_IOC_NOTIFY by returning the notify
information structure which includes the name of the file(s) that
changed and why. See MS-SMB2 2.2.35 for details on the individual
filter flags and the file_notify_information structure returned.

To use this simply pass in the following (with enough space
to fit at least one file_notify_information structure)

struct __attribute__((__packed__)) smb3_notify {
       uint32_t completion_filter;
       bool     watch_tree;
       uint32_t data_len;
       uint8_t  data[];
} __packed;

using CIFS_IOC_NOTIFY_INFO 0xc009cf0b
 or equivalently _IOWR(CIFS_IOCTL_MAGIC, 11, struct smb3_notify_info)

The ioctl will block until the server detects a change to that
directory or its subdirectories (if watch_tree is set).
Acked-by: default avatarPaulo Alcantara (SUSE) <pc@cjr.nz>
Acked-by: default avatarRonnie Sahlberg <lsahlber@redhat.com>
Signed-off-by: default avatarSteve French <stfrench@microsoft.com>
parent 2bff0659
...@@ -91,6 +91,13 @@ struct smb3_notify { ...@@ -91,6 +91,13 @@ struct smb3_notify {
bool watch_tree; bool watch_tree;
} __packed; } __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_IOCTL_MAGIC 0xCF
#define CIFS_IOC_COPYCHUNK_FILE _IOW(CIFS_IOCTL_MAGIC, 3, int) #define CIFS_IOC_COPYCHUNK_FILE _IOW(CIFS_IOCTL_MAGIC, 3, int)
#define CIFS_IOC_SET_INTEGRITY _IO(CIFS_IOCTL_MAGIC, 4) #define CIFS_IOC_SET_INTEGRITY _IO(CIFS_IOCTL_MAGIC, 4)
...@@ -100,6 +107,7 @@ struct smb3_notify { ...@@ -100,6 +107,7 @@ struct smb3_notify {
#define CIFS_DUMP_KEY _IOWR(CIFS_IOCTL_MAGIC, 8, struct smb3_key_debug_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) #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_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) #define CIFS_IOC_SHUTDOWN _IOR ('X', 125, __u32)
/* /*
......
...@@ -454,7 +454,7 @@ struct smb_version_operations { ...@@ -454,7 +454,7 @@ struct smb_version_operations {
int (*enum_snapshots)(const unsigned int xid, struct cifs_tcon *tcon, int (*enum_snapshots)(const unsigned int xid, struct cifs_tcon *tcon,
struct cifsFileInfo *src_file, void __user *); struct cifsFileInfo *src_file, void __user *);
int (*notify)(const unsigned int xid, struct file *pfile, 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 *, int (*query_mf_symlink)(unsigned int, struct cifs_tcon *,
struct cifs_sb_info *, const unsigned char *, struct cifs_sb_info *, const unsigned char *,
char *, unsigned int *); char *, unsigned int *);
......
...@@ -484,12 +484,35 @@ long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg) ...@@ -484,12 +484,35 @@ long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg)
tcon = tlink_tcon(tlink); tcon = tlink_tcon(tlink);
if (tcon && tcon->ses->server->ops->notify) { if (tcon && tcon->ses->server->ops->notify) {
rc = tcon->ses->server->ops->notify(xid, 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); cifs_dbg(FYI, "ioctl notify rc %d\n", rc);
} else } else
rc = -EOPNOTSUPP; rc = -EOPNOTSUPP;
cifs_put_tlink(tlink); cifs_put_tlink(tlink);
break; 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: case CIFS_IOC_SHUTDOWN:
rc = cifs_shutdown(inode->i_sb, arg); rc = cifs_shutdown(inode->i_sb, arg);
break; break;
......
...@@ -2018,9 +2018,10 @@ smb3_enum_snapshots(const unsigned int xid, struct cifs_tcon *tcon, ...@@ -2018,9 +2018,10 @@ smb3_enum_snapshots(const unsigned int xid, struct cifs_tcon *tcon,
static int static int
smb3_notify(const unsigned int xid, struct file *pfile, 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 dentry *dentry = pfile->f_path.dentry;
struct inode *inode = file_inode(pfile); struct inode *inode = file_inode(pfile);
struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb); struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
...@@ -2028,10 +2029,12 @@ smb3_notify(const unsigned int xid, struct file *pfile, ...@@ -2028,10 +2029,12 @@ smb3_notify(const unsigned int xid, struct file *pfile,
struct cifs_fid fid; struct cifs_fid fid;
struct cifs_tcon *tcon; struct cifs_tcon *tcon;
const unsigned char *path; const unsigned char *path;
char *returned_ioctl_info = NULL;
void *page = alloc_dentry_path(); void *page = alloc_dentry_path();
__le16 *utf16_path = NULL; __le16 *utf16_path = NULL;
u8 oplock = SMB2_OPLOCK_LEVEL_NONE; u8 oplock = SMB2_OPLOCK_LEVEL_NONE;
int rc = 0; int rc = 0;
__u32 ret_len = 0;
path = build_path_from_dentry(dentry, page); path = build_path_from_dentry(dentry, page);
if (IS_ERR(path)) { if (IS_ERR(path)) {
...@@ -2045,10 +2048,18 @@ smb3_notify(const unsigned int xid, struct file *pfile, ...@@ -2045,10 +2048,18 @@ smb3_notify(const unsigned int xid, struct file *pfile,
goto notify_exit; goto notify_exit;
} }
if (return_changes) {
if (copy_from_user(&notify, ioc_buf, sizeof(struct smb3_notify_info))) {
rc = -EFAULT;
goto notify_exit;
}
} else {
if (copy_from_user(&notify, ioc_buf, sizeof(struct smb3_notify))) { if (copy_from_user(&notify, ioc_buf, sizeof(struct smb3_notify))) {
rc = -EFAULT; rc = -EFAULT;
goto notify_exit; goto notify_exit;
} }
notify.data_len = 0;
}
tcon = cifs_sb_master_tcon(cifs_sb); tcon = cifs_sb_master_tcon(cifs_sb);
oparms.tcon = tcon; oparms.tcon = tcon;
...@@ -2064,12 +2075,22 @@ smb3_notify(const unsigned int xid, struct file *pfile, ...@@ -2064,12 +2075,22 @@ smb3_notify(const unsigned int xid, struct file *pfile,
goto notify_exit; goto notify_exit;
rc = SMB2_change_notify(xid, tcon, fid.persistent_fid, fid.volatile_fid, 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); SMB2_close(xid, tcon, fid.persistent_fid, fid.volatile_fid);
cifs_dbg(FYI, "change notify for path %s rc %d\n", path, rc); 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: notify_exit:
free_dentry_path(page); free_dentry_path(page);
kfree(utf16_path); kfree(utf16_path);
......
...@@ -3710,11 +3710,13 @@ SMB2_notify_init(const unsigned int xid, struct smb_rqst *rqst, ...@@ -3710,11 +3710,13 @@ SMB2_notify_init(const unsigned int xid, struct smb_rqst *rqst,
int int
SMB2_change_notify(const unsigned int xid, struct cifs_tcon *tcon, SMB2_change_notify(const unsigned int xid, struct cifs_tcon *tcon,
u64 persistent_fid, u64 volatile_fid, bool watch_tree, 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 cifs_ses *ses = tcon->ses;
struct TCP_Server_Info *server = cifs_pick_channel(ses); struct TCP_Server_Info *server = cifs_pick_channel(ses);
struct smb_rqst rqst; struct smb_rqst rqst;
struct smb2_change_notify_rsp *smb_rsp;
struct kvec iov[1]; struct kvec iov[1];
struct kvec rsp_iov = {NULL, 0}; struct kvec rsp_iov = {NULL, 0};
int resp_buftype = CIFS_NO_BUFFER; int resp_buftype = CIFS_NO_BUFFER;
...@@ -3730,6 +3732,9 @@ SMB2_change_notify(const unsigned int xid, struct cifs_tcon *tcon, ...@@ -3730,6 +3732,9 @@ SMB2_change_notify(const unsigned int xid, struct cifs_tcon *tcon,
memset(&rqst, 0, sizeof(struct smb_rqst)); memset(&rqst, 0, sizeof(struct smb_rqst));
memset(&iov, 0, sizeof(iov)); memset(&iov, 0, sizeof(iov));
if (plen)
*plen = 0;
rqst.rq_iov = iov; rqst.rq_iov = iov;
rqst.rq_nvec = 1; rqst.rq_nvec = 1;
...@@ -3748,9 +3753,28 @@ SMB2_change_notify(const unsigned int xid, struct cifs_tcon *tcon, ...@@ -3748,9 +3753,28 @@ SMB2_change_notify(const unsigned int xid, struct cifs_tcon *tcon,
cifs_stats_fail_inc(tcon, SMB2_CHANGE_NOTIFY_HE); cifs_stats_fail_inc(tcon, SMB2_CHANGE_NOTIFY_HE);
trace_smb3_notify_err(xid, persistent_fid, tcon->tid, ses->Suid, trace_smb3_notify_err(xid, persistent_fid, tcon->tid, ses->Suid,
(u8)watch_tree, completion_filter, rc); (u8)watch_tree, completion_filter, rc);
} else } else {
trace_smb3_notify_done(xid, persistent_fid, tcon->tid, 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: cnotify_exit:
if (rqst.rq_iov) if (rqst.rq_iov)
......
...@@ -144,7 +144,8 @@ extern int SMB2_ioctl_init(struct cifs_tcon *tcon, ...@@ -144,7 +144,8 @@ extern int SMB2_ioctl_init(struct cifs_tcon *tcon,
extern void SMB2_ioctl_free(struct smb_rqst *rqst); extern void SMB2_ioctl_free(struct smb_rqst *rqst);
extern int SMB2_change_notify(const unsigned int xid, struct cifs_tcon *tcon, extern int SMB2_change_notify(const unsigned int xid, struct cifs_tcon *tcon,
u64 persistent_fid, u64 volatile_fid, bool watch_tree, 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, extern int __SMB2_close(const unsigned int xid, struct cifs_tcon *tcon,
u64 persistent_fid, u64 volatile_fid, u64 persistent_fid, u64 volatile_fid,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment