Re: [PATCH 1/2] CIFS: Implement follow_link for SMB2

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Fri,  9 Aug 2013 16:57:21 +0400
Pavel Shilovsky <pshilovsky@xxxxxxxxx> wrote:

> that allows to access files through symlink created on a server.
> 
> Signed-off-by: Pavel Shilovsky <pshilovsky@xxxxxxxxx>
> ---
>  fs/cifs/cifsglob.h  |    3 +++
>  fs/cifs/inode.c     |    4 ++++
>  fs/cifs/link.c      |   24 +++++----------------
>  fs/cifs/readdir.c   |    3 +++
>  fs/cifs/smb2file.c  |    2 +-
>  fs/cifs/smb2inode.c |    9 ++++----
>  fs/cifs/smb2misc.c  |    4 ++++
>  fs/cifs/smb2ops.c   |   60 ++++++++++++++++++++++++++++++++++++++++++++++++---
>  fs/cifs/smb2pdu.c   |    9 +++++++-
>  fs/cifs/smb2pdu.h   |   14 ++++++++++++
>  fs/cifs/smb2proto.h |    3 ++-
>  11 files changed, 106 insertions(+), 29 deletions(-)
> 
> diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
> index 1b1b144..e96ba59 100644
> --- a/fs/cifs/cifsglob.h
> +++ b/fs/cifs/cifsglob.h
> @@ -305,6 +305,9 @@ struct smb_version_operations {
>  	int (*create_hardlink)(const unsigned int, struct cifs_tcon *,
>  			       const char *, const char *,
>  			       struct cifs_sb_info *);
> +	/* query symlink target */
> +	int (*query_symlink)(const unsigned int, struct cifs_tcon *,
> +			     const char *, char **, struct cifs_sb_info *);
>  	/* open a file for non-posix mounts */
>  	int (*open)(const unsigned int, struct cifs_open_parms *,
>  		    __u32 *, FILE_ALL_INFO *);
> diff --git a/fs/cifs/inode.c b/fs/cifs/inode.c
> index 20efd81..f776fbf 100644
> --- a/fs/cifs/inode.c
> +++ b/fs/cifs/inode.c
> @@ -549,6 +549,10 @@ cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
>  		 * when Unix extensions are disabled - fake it.
>  		 */
>  		fattr->cf_nlink = 2;
> +	} else if (fattr->cf_cifsattrs & ATTR_REPARSE) {
> +		fattr->cf_mode = S_IFLNK;
> +		fattr->cf_dtype = DT_LNK;
> +		fattr->cf_nlink = le32_to_cpu(info->NumberOfLinks);
>  	} else {
>  		fattr->cf_mode = S_IFREG | cifs_sb->mnt_file_mode;
>  		fattr->cf_dtype = DT_REG;
> diff --git a/fs/cifs/link.c b/fs/cifs/link.c
> index b83c3f5..fd089f7 100644
> --- a/fs/cifs/link.c
> +++ b/fs/cifs/link.c
> @@ -487,6 +487,7 @@ cifs_follow_link(struct dentry *direntry, struct nameidata *nd)
>  	struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
>  	struct tcon_link *tlink = NULL;
>  	struct cifs_tcon *tcon;
> +	struct TCP_Server_Info *server;
>  
>  	xid = get_xid();
>  
> @@ -497,25 +498,7 @@ cifs_follow_link(struct dentry *direntry, struct nameidata *nd)
>  		goto out;
>  	}
>  	tcon = tlink_tcon(tlink);
> -
> -	/*
> -	 * For now, we just handle symlinks with unix extensions enabled.
> -	 * Eventually we should handle NTFS reparse points, and MacOS
> -	 * symlink support. For instance...
> -	 *
> -	 * rc = CIFSSMBQueryReparseLinkInfo(...)
> -	 *
> -	 * For now, just return -EACCES when the server doesn't support posix
> -	 * extensions. Note that we still allow querying symlinks when posix
> -	 * extensions are manually disabled. We could disable these as well
> -	 * but there doesn't seem to be any harm in allowing the client to
> -	 * read them.
> -	 */
> -	if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MF_SYMLINKS) &&
> -	    !cap_unix(tcon->ses)) {
> -		rc = -EACCES;
> -		goto out;
> -	}
> +	server = tcon->ses->server;
>  
>  	full_path = build_path_from_dentry(direntry);
>  	if (!full_path)
> @@ -537,6 +520,9 @@ cifs_follow_link(struct dentry *direntry, struct nameidata *nd)
>  	if ((rc != 0) && cap_unix(tcon->ses))
>  		rc = CIFSSMBUnixQuerySymLink(xid, tcon, full_path, &target_path,
>  					     cifs_sb->local_nls);
> +	else if (rc != 0 && server->ops->query_symlink)
> +		rc = server->ops->query_symlink(xid, tcon, full_path,
> +						&target_path, cifs_sb);
>  
>  	kfree(full_path);
>  out:
> diff --git a/fs/cifs/readdir.c b/fs/cifs/readdir.c
> index 94d6201..38ceb51 100644
> --- a/fs/cifs/readdir.c
> +++ b/fs/cifs/readdir.c
> @@ -164,6 +164,9 @@ cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb)
>  		if (cifs_dfs_is_possible(cifs_sb) &&
>  		    (fattr->cf_cifsattrs & ATTR_REPARSE))
>  			fattr->cf_flags |= CIFS_FATTR_NEED_REVAL;
> +	} else if (fattr->cf_cifsattrs & ATTR_REPARSE) {
> +		fattr->cf_mode = S_IFLNK;
> +		fattr->cf_dtype = DT_LNK;
>  	} else {
>  		fattr->cf_mode = S_IFREG | cifs_sb->mnt_file_mode;
>  		fattr->cf_dtype = DT_REG;
> diff --git a/fs/cifs/smb2file.c b/fs/cifs/smb2file.c
> index 04a81a4..020245d 100644
> --- a/fs/cifs/smb2file.c
> +++ b/fs/cifs/smb2file.c
> @@ -86,7 +86,7 @@ smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms,
>  	if (oparms->tcon->ses->server->capabilities & SMB2_GLOBAL_CAP_LEASING)
>  		memcpy(smb2_oplock + 1, fid->lease_key, SMB2_LEASE_KEY_SIZE);
>  
> -	rc = SMB2_open(xid, oparms, smb2_path, smb2_oplock, smb2_data);
> +	rc = SMB2_open(xid, oparms, smb2_path, smb2_oplock, smb2_data, NULL);

So what happens if I run across one of these symlinks as the terminal
dentry of an atomic_open call? Does that just fail currently?

>  	if (rc)
>  		goto out;
>  
> diff --git a/fs/cifs/smb2inode.c b/fs/cifs/smb2inode.c
> index c6ec163..78ff88c 100644
> --- a/fs/cifs/smb2inode.c
> +++ b/fs/cifs/smb2inode.c
> @@ -60,7 +60,7 @@ smb2_open_op_close(const unsigned int xid, struct cifs_tcon *tcon,
>  	oparms.fid = &fid;
>  	oparms.reconnect = false;
>  
> -	rc = SMB2_open(xid, &oparms, utf16_path, &oplock, NULL);
> +	rc = SMB2_open(xid, &oparms, utf16_path, &oplock, NULL, NULL);
>  	if (rc) {
>  		kfree(utf16_path);
>  		return rc;
> @@ -136,7 +136,8 @@ smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
>  		return -ENOMEM;
>  
>  	rc = smb2_open_op_close(xid, tcon, cifs_sb, full_path,
> -				FILE_READ_ATTRIBUTES, FILE_OPEN, 0, smb2_data,
> +				FILE_READ_ATTRIBUTES, FILE_OPEN,
> +				OPEN_REPARSE_POINT, smb2_data,
>  				SMB2_OP_QUERY_INFO);
>  	if (rc)
>  		goto out;
> @@ -191,8 +192,8 @@ smb2_unlink(const unsigned int xid, struct cifs_tcon *tcon, const char *name,
>  	    struct cifs_sb_info *cifs_sb)
>  {
>  	return smb2_open_op_close(xid, tcon, cifs_sb, name, DELETE, FILE_OPEN,
> -				  CREATE_DELETE_ON_CLOSE, NULL,
> -				  SMB2_OP_DELETE);
> +				  CREATE_DELETE_ON_CLOSE | OPEN_REPARSE_POINT,
> +				  NULL, SMB2_OP_DELETE);
>  }
>  
>  static int
> diff --git a/fs/cifs/smb2misc.c b/fs/cifs/smb2misc.c
> index b0c4334..6103359 100644
> --- a/fs/cifs/smb2misc.c
> +++ b/fs/cifs/smb2misc.c
> @@ -171,6 +171,10 @@ smb2_check_message(char *buf, unsigned int length)
>  	if (4 + len != clc_len) {
>  		cifs_dbg(FYI, "Calculated size %u length %u mismatch mid %llu\n",
>  			 clc_len, 4 + len, mid);
> +		/* create failed on symlink */
> +		if (command == SMB2_CREATE_HE &&
> +		    hdr->Status == STATUS_STOPPED_ON_SYMLINK)
> +			return 0;
>  		/* Windows 7 server returns 24 bytes more */
>  		if (clc_len + 20 == len && command == SMB2_OPLOCK_BREAK_HE)
>  			return 0;
> diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c
> index 300ff85..66acbb3 100644
> --- a/fs/cifs/smb2ops.c
> +++ b/fs/cifs/smb2ops.c
> @@ -24,6 +24,7 @@
>  #include "smb2proto.h"
>  #include "cifsproto.h"
>  #include "cifs_debug.h"
> +#include "cifs_unicode.h"
>  #include "smb2status.h"
>  #include "smb2glob.h"
>  
> @@ -229,7 +230,7 @@ smb2_is_path_accessible(const unsigned int xid, struct cifs_tcon *tcon,
>  	oparms.fid = &fid;
>  	oparms.reconnect = false;
>  
> -	rc = SMB2_open(xid, &oparms, utf16_path, &oplock, NULL);
> +	rc = SMB2_open(xid, &oparms, utf16_path, &oplock, NULL, NULL);
>  	if (rc) {
>  		kfree(utf16_path);
>  		return rc;
> @@ -463,7 +464,7 @@ smb2_query_dir_first(const unsigned int xid, struct cifs_tcon *tcon,
>  	oparms.fid = fid;
>  	oparms.reconnect = false;
>  
> -	rc = SMB2_open(xid, &oparms, utf16_path, &oplock, NULL);
> +	rc = SMB2_open(xid, &oparms, utf16_path, &oplock, NULL, NULL);
>  	kfree(utf16_path);
>  	if (rc) {
>  		cifs_dbg(VFS, "open dir failed\n");
> @@ -550,7 +551,7 @@ smb2_queryfs(const unsigned int xid, struct cifs_tcon *tcon,
>  	oparms.fid = &fid;
>  	oparms.reconnect = false;
>  
> -	rc = SMB2_open(xid, &oparms, &srch_path, &oplock, NULL);
> +	rc = SMB2_open(xid, &oparms, &srch_path, &oplock, NULL, NULL);
>  	if (rc)
>  		return rc;
>  	buf->f_type = SMB2_MAGIC_NUMBER;
> @@ -596,6 +597,57 @@ smb2_new_lease_key(struct cifs_fid *fid)
>  	get_random_bytes(fid->lease_key, SMB2_LEASE_KEY_SIZE);
>  }
>  
> +int
> +smb2_query_symlink(const unsigned int xid, struct cifs_tcon *tcon,
> +		   const char *full_path, char **target_path,
> +		   struct cifs_sb_info *cifs_sb)
> +{
> +	int rc;
> +	__le16 *utf16_path;
> +	__u8 oplock = SMB2_OPLOCK_LEVEL_NONE;
> +	struct cifs_open_parms oparms;
> +	struct cifs_fid fid;
> +	struct smb2_err_rsp *err_buf = NULL;
> +	struct smb2_symlink_err_rsp *symlink;
> +	unsigned int sub_len, sub_offset;
> +
> +	cifs_dbg(FYI, "%s: path: %s\n", __func__, full_path);
> +
> +	utf16_path = cifs_convert_path_to_utf16(full_path, cifs_sb);
> +	if (!utf16_path)
> +		return -ENOMEM;
> +
> +	oparms.tcon = tcon;
> +	oparms.desired_access = FILE_READ_ATTRIBUTES;
> +	oparms.disposition = FILE_OPEN;
> +	oparms.create_options = 0;
> +	oparms.fid = &fid;
> +	oparms.reconnect = false;
> +
> +	rc = SMB2_open(xid, &oparms, utf16_path, &oplock, NULL, &err_buf);
> +
> +	if (!rc || !err_buf) {
> +		kfree(utf16_path);
> +		return -ENOENT;
> +	}
> +	/* open must fail on symlink - reset rc */
> +	rc = 0;
> +	symlink = (struct smb2_symlink_err_rsp *)err_buf->ErrorData;
> +	sub_len = le16_to_cpu(symlink->SubstituteNameLength);
> +	sub_offset = le16_to_cpu(symlink->SubstituteNameOffset);
> +	*target_path = cifs_strndup_from_utf16(
> +				(char *)symlink->PathBuffer + sub_offset,
> +				sub_len, true, cifs_sb->local_nls);
> +	if (!(*target_path)) {
> +		kfree(utf16_path);
> +		return -ENOMEM;
> +	}
> +	convert_delimiter(*target_path, '/');
> +	cifs_dbg(FYI, "%s: target path: %s\n", __func__, *target_path);
> +	kfree(utf16_path);
> +	return rc;
> +}
> +
>  struct smb_version_operations smb21_operations = {
>  	.compare_fids = smb2_compare_fids,
>  	.setup_request = smb2_setup_request,
> @@ -638,6 +690,7 @@ struct smb_version_operations smb21_operations = {
>  	.unlink = smb2_unlink,
>  	.rename = smb2_rename_path,
>  	.create_hardlink = smb2_create_hardlink,
> +	.query_symlink = smb2_query_symlink,
>  	.open = smb2_open_file,
>  	.set_fid = smb2_set_fid,
>  	.close = smb2_close_file,
> @@ -706,6 +759,7 @@ struct smb_version_operations smb30_operations = {
>  	.unlink = smb2_unlink,
>  	.rename = smb2_rename_path,
>  	.create_hardlink = smb2_create_hardlink,
> +	.query_symlink = smb2_query_symlink,
>  	.open = smb2_open_file,
>  	.set_fid = smb2_set_fid,
>  	.close = smb2_close_file,
> diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c
> index c7ad06f..e4a6e2d 100644
> --- a/fs/cifs/smb2pdu.c
> +++ b/fs/cifs/smb2pdu.c
> @@ -977,7 +977,8 @@ add_durable_context(struct kvec *iov, unsigned int *num_iovec,
>  
>  int
>  SMB2_open(const unsigned int xid, struct cifs_open_parms *oparms, __le16 *path,
> -	  __u8 *oplock, struct smb2_file_all_info *buf)
> +	  __u8 *oplock, struct smb2_file_all_info *buf,
> +	  struct smb2_err_rsp **err_buf)
>  {
>  	struct smb2_create_req *req;
>  	struct smb2_create_rsp *rsp;
> @@ -1081,6 +1082,12 @@ SMB2_open(const unsigned int xid, struct cifs_open_parms *oparms, __le16 *path,
>  
>  	if (rc != 0) {
>  		cifs_stats_fail_inc(tcon, SMB2_CREATE_HE);
> +		if (err_buf) {
> +			unsigned int buf_size = get_rfc1002_length(rsp) + 4;
> +			*err_buf = kmalloc(buf_size, GFP_KERNEL);
> +			if (*err_buf)
> +				memcpy(*err_buf, rsp, buf_size);
> +		}

This should be replaced with kmemdup().

>  		goto creat_exit;
>  	}
>  
> diff --git a/fs/cifs/smb2pdu.h b/fs/cifs/smb2pdu.h
> index 36b0d37..40baeae 100644
> --- a/fs/cifs/smb2pdu.h
> +++ b/fs/cifs/smb2pdu.h
> @@ -150,6 +150,20 @@ struct smb2_err_rsp {
>  	__u8   ErrorData[1];  /* variable length */
>  } __packed;
>  
> +struct smb2_symlink_err_rsp {
> +	__le32 SymLinkLength;
> +	__le32 SymLinkErrorTag;
> +	__le32 ReparseTag;
> +	__le16 ReparseDataLength;
> +	__le16 UnparsedPathLength;
> +	__le16 SubstituteNameOffset;
> +	__le16 SubstituteNameLength;
> +	__le16 PrintNameOffset;
> +	__le16 PrintNameLength;
> +	__le32 Flags;
> +	__u8  PathBuffer[0];
> +} __packed;
> +
>  #define SMB2_CLIENT_GUID_SIZE 16
>  
>  extern __u8 cifs_client_guid[SMB2_CLIENT_GUID_SIZE];
> diff --git a/fs/cifs/smb2proto.h b/fs/cifs/smb2proto.h
> index 1a5ecbe..1db89fd 100644
> --- a/fs/cifs/smb2proto.h
> +++ b/fs/cifs/smb2proto.h
> @@ -106,7 +106,8 @@ extern int SMB2_tcon(const unsigned int xid, struct cifs_ses *ses,
>  extern int SMB2_tdis(const unsigned int xid, struct cifs_tcon *tcon);
>  extern int SMB2_open(const unsigned int xid, struct cifs_open_parms *oparms,
>  		     __le16 *path, __u8 *oplock,
> -		     struct smb2_file_all_info *buf);
> +		     struct smb2_file_all_info *buf,
> +		     struct smb2_err_rsp **err_buf);
>  extern int SMB2_ioctl(const unsigned int xid, struct cifs_tcon *tcon,
>  		     u64 persistent_fid, u64 volatile_fid, u32 opcode,
>  		     bool is_fsctl, char *in_data, u32 indatalen,

Looks fairly reasonable otherwise...

Acked-by: Jeff Layton <jlayton@xxxxxxxxxx>
--
To unsubscribe from this list: send the line "unsubscribe linux-cifs" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux