Commit d26c2ddd authored by Steve French's avatar Steve French
Browse files

cifs: add SMB3 change notification support



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: default avatarSteve French <stfrench@microsoft.com>
Reviewed-by: default avatarAurelien Aptel <aaptel@suse.com>
Acked-by: default avatarPaulo Alcantara (SUSE) <pc@cjr.nz>
parent 343a1b77
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -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)
+2 −0
Original line number Diff line number Diff line
@@ -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 *);
+16 −0
Original line number Diff line number Diff line
@@ -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;
+62 −0
Original line number Diff line number Diff line
@@ -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,
+1 −0
Original line number Diff line number Diff line
@@ -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);