diff options
| -rw-r--r-- | fs/smb/client/cifsglob.h | 1 | ||||
| -rw-r--r-- | fs/smb/client/cifsproto.h | 1 | ||||
| -rw-r--r-- | fs/smb/client/inode.c | 1 | ||||
| -rw-r--r-- | fs/smb/client/reparse.c | 90 | ||||
| -rw-r--r-- | fs/smb/client/reparse.h | 4 | ||||
| -rw-r--r-- | fs/smb/client/smb1ops.c | 3 | ||||
| -rw-r--r-- | fs/smb/client/smb2file.c | 21 | ||||
| -rw-r--r-- | fs/smb/client/smb2inode.c | 6 | ||||
| -rw-r--r-- | fs/smb/client/smb2proto.h | 9 | 
9 files changed, 108 insertions, 28 deletions
diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index fc33dfe7e925..e97e6dfd665c 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -594,6 +594,7 @@ struct smb_version_operations {  	/* Check for STATUS_NETWORK_NAME_DELETED */  	bool (*is_network_name_deleted)(char *buf, struct TCP_Server_Info *srv);  	int (*parse_reparse_point)(struct cifs_sb_info *cifs_sb, +				   const char *full_path,  				   struct kvec *rsp_iov,  				   struct cifs_open_info_data *data);  	int (*create_reparse_symlink)(const unsigned int xid, diff --git a/fs/smb/client/cifsproto.h b/fs/smb/client/cifsproto.h index 075985bfb13a..c5be95f102a4 100644 --- a/fs/smb/client/cifsproto.h +++ b/fs/smb/client/cifsproto.h @@ -661,6 +661,7 @@ char *extract_hostname(const char *unc);  char *extract_sharename(const char *unc);  int parse_reparse_point(struct reparse_data_buffer *buf,  			u32 plen, struct cifs_sb_info *cifs_sb, +			const char *full_path,  			bool unicode, struct cifs_open_info_data *data);  int __cifs_sfu_make_node(unsigned int xid, struct inode *inode,  			 struct dentry *dentry, struct cifs_tcon *tcon, diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index de8063b44072..befe43460dcb 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -1137,6 +1137,7 @@ static int reparse_info_to_fattr(struct cifs_open_info_data *data,  			rc = 0;  		} else if (iov && server->ops->parse_reparse_point) {  			rc = server->ops->parse_reparse_point(cifs_sb, +							      full_path,  							      iov, data);  		}  		break; diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index 90da1e2b6217..f74d0a86f44a 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -535,9 +535,76 @@ static int parse_reparse_posix(struct reparse_posix_data *buf,  	return 0;  } +int smb2_parse_native_symlink(char **target, const char *buf, unsigned int len, +			      bool unicode, bool relative, +			      const char *full_path, +			      struct cifs_sb_info *cifs_sb) +{ +	char sep = CIFS_DIR_SEP(cifs_sb); +	char *linux_target = NULL; +	char *smb_target = NULL; +	int levels; +	int rc; +	int i; + +	smb_target = cifs_strndup_from_utf16(buf, len, unicode, cifs_sb->local_nls); +	if (!smb_target) { +		rc = -ENOMEM; +		goto out; +	} + +	if (smb_target[0] == sep && relative) { +		/* +		 * This is a relative SMB symlink from the top of the share, +		 * which is the top level directory of the Linux mount point. +		 * Linux does not support such relative symlinks, so convert +		 * it to the relative symlink from the current directory. +		 * full_path is the SMB path to the symlink (from which is +		 * extracted current directory) and smb_target is the SMB path +		 * where symlink points, therefore full_path must always be on +		 * the SMB share. +		 */ +		int smb_target_len = strlen(smb_target)+1; +		levels = 0; +		for (i = 1; full_path[i]; i++) { /* i=1 to skip leading sep */ +			if (full_path[i] == sep) +				levels++; +		} +		linux_target = kmalloc(levels*3 + smb_target_len, GFP_KERNEL); +		if (!linux_target) { +			rc = -ENOMEM; +			goto out; +		} +		for (i = 0; i < levels; i++) { +			linux_target[i*3 + 0] = '.'; +			linux_target[i*3 + 1] = '.'; +			linux_target[i*3 + 2] = sep; +		} +		memcpy(linux_target + levels*3, smb_target+1, smb_target_len); /* +1 to skip leading sep */ +	} else { +		linux_target = smb_target; +		smb_target = NULL; +	} + +	if (sep == '\\') +		convert_delimiter(linux_target, '/'); + +	rc = 0; +	*target = linux_target; + +	cifs_dbg(FYI, "%s: symlink target: %s\n", __func__, *target); + +out: +	if (rc != 0) +		kfree(linux_target); +	kfree(smb_target); +	return rc; +} +  static int parse_reparse_symlink(struct reparse_symlink_data_buffer *sym,  				 u32 plen, bool unicode,  				 struct cifs_sb_info *cifs_sb, +				 const char *full_path,  				 struct cifs_open_info_data *data)  {  	unsigned int len; @@ -552,20 +619,18 @@ static int parse_reparse_symlink(struct reparse_symlink_data_buffer *sym,  		return -EIO;  	} -	data->symlink_target = cifs_strndup_from_utf16(sym->PathBuffer + offs, -						       len, unicode, -						       cifs_sb->local_nls); -	if (!data->symlink_target) -		return -ENOMEM; - -	convert_delimiter(data->symlink_target, '/'); -	cifs_dbg(FYI, "%s: target path: %s\n", __func__, data->symlink_target); - -	return 0; +	return smb2_parse_native_symlink(&data->symlink_target, +					 sym->PathBuffer + offs, +					 len, +					 unicode, +					 le32_to_cpu(sym->Flags) & SYMLINK_FLAG_RELATIVE, +					 full_path, +					 cifs_sb);  }  int parse_reparse_point(struct reparse_data_buffer *buf,  			u32 plen, struct cifs_sb_info *cifs_sb, +			const char *full_path,  			bool unicode, struct cifs_open_info_data *data)  {  	struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb); @@ -580,7 +645,7 @@ int parse_reparse_point(struct reparse_data_buffer *buf,  	case IO_REPARSE_TAG_SYMLINK:  		return parse_reparse_symlink(  			(struct reparse_symlink_data_buffer *)buf, -			plen, unicode, cifs_sb, data); +			plen, unicode, cifs_sb, full_path, data);  	case IO_REPARSE_TAG_LX_SYMLINK:  	case IO_REPARSE_TAG_AF_UNIX:  	case IO_REPARSE_TAG_LX_FIFO: @@ -596,6 +661,7 @@ int parse_reparse_point(struct reparse_data_buffer *buf,  }  int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb, +			     const char *full_path,  			     struct kvec *rsp_iov,  			     struct cifs_open_info_data *data)  { @@ -605,7 +671,7 @@ int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb,  	buf = (struct reparse_data_buffer *)((u8 *)io +  					     le32_to_cpu(io->OutputOffset)); -	return parse_reparse_point(buf, plen, cifs_sb, true, data); +	return parse_reparse_point(buf, plen, cifs_sb, full_path, true, data);  }  static void wsl_to_fattr(struct cifs_open_info_data *data, diff --git a/fs/smb/client/reparse.h b/fs/smb/client/reparse.h index 2a9f4f9f79de..ff05b0e75c92 100644 --- a/fs/smb/client/reparse.h +++ b/fs/smb/client/reparse.h @@ -117,7 +117,9 @@ int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode,  int smb2_mknod_reparse(unsigned int xid, struct inode *inode,  		       struct dentry *dentry, struct cifs_tcon *tcon,  		       const char *full_path, umode_t mode, dev_t dev); -int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb, struct kvec *rsp_iov, +int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb, +			     const char *full_path, +			     struct kvec *rsp_iov,  			     struct cifs_open_info_data *data);  #endif /* _CIFS_REPARSE_H */ diff --git a/fs/smb/client/smb1ops.c b/fs/smb/client/smb1ops.c index 9a6ece66c4d3..abca214f923c 100644 --- a/fs/smb/client/smb1ops.c +++ b/fs/smb/client/smb1ops.c @@ -994,6 +994,7 @@ static int cifs_query_symlink(const unsigned int xid,  }  static int cifs_parse_reparse_point(struct cifs_sb_info *cifs_sb, +				    const char *full_path,  				    struct kvec *rsp_iov,  				    struct cifs_open_info_data *data)  { @@ -1004,7 +1005,7 @@ static int cifs_parse_reparse_point(struct cifs_sb_info *cifs_sb,  	buf = (struct reparse_data_buffer *)((__u8 *)&io->hdr.Protocol +  					     le32_to_cpu(io->DataOffset)); -	return parse_reparse_point(buf, plen, cifs_sb, unicode, data); +	return parse_reparse_point(buf, plen, cifs_sb, full_path, unicode, data);  }  static bool diff --git a/fs/smb/client/smb2file.c b/fs/smb/client/smb2file.c index e301349b0078..e836bc2193dd 100644 --- a/fs/smb/client/smb2file.c +++ b/fs/smb/client/smb2file.c @@ -63,12 +63,12 @@ static struct smb2_symlink_err_rsp *symlink_data(const struct kvec *iov)  	return sym;  } -int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, const struct kvec *iov, char **path) +int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, const struct kvec *iov, +				const char *full_path, char **path)  {  	struct smb2_symlink_err_rsp *sym;  	unsigned int sub_offs, sub_len;  	unsigned int print_offs, print_len; -	char *s;  	if (!cifs_sb || !iov || !iov->iov_base || !iov->iov_len || !path)  		return -EINVAL; @@ -86,15 +86,13 @@ int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, const struct kvec  	    iov->iov_len < SMB2_SYMLINK_STRUCT_SIZE + print_offs + print_len)  		return -EINVAL; -	s = cifs_strndup_from_utf16((char *)sym->PathBuffer + sub_offs, sub_len, true, -				    cifs_sb->local_nls); -	if (!s) -		return -ENOMEM; -	convert_delimiter(s, '/'); -	cifs_dbg(FYI, "%s: symlink target: %s\n", __func__, s); - -	*path = s; -	return 0; +	return smb2_parse_native_symlink(path, +					 (char *)sym->PathBuffer + sub_offs, +					 sub_len, +					 true, +					 le32_to_cpu(sym->Flags) & SYMLINK_FLAG_RELATIVE, +					 full_path, +					 cifs_sb);  }  int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms, __u32 *oplock, void *buf) @@ -126,6 +124,7 @@ int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms, __u32  			goto out;  		if (hdr->Status == STATUS_STOPPED_ON_SYMLINK) {  			rc = smb2_parse_symlink_response(oparms->cifs_sb, &err_iov, +							 oparms->path,  							 &data->symlink_target);  			if (!rc) {  				memset(smb2_data, 0, sizeof(*smb2_data)); diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index e49d0c25eb03..a188908914fe 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -828,6 +828,7 @@ finished:  static int parse_create_response(struct cifs_open_info_data *data,  				 struct cifs_sb_info *cifs_sb, +				 const char *full_path,  				 const struct kvec *iov)  {  	struct smb2_create_rsp *rsp = iov->iov_base; @@ -841,6 +842,7 @@ static int parse_create_response(struct cifs_open_info_data *data,  		break;  	case STATUS_STOPPED_ON_SYMLINK:  		rc = smb2_parse_symlink_response(cifs_sb, iov, +						 full_path,  						 &data->symlink_target);  		if (rc)  			return rc; @@ -930,14 +932,14 @@ int smb2_query_path_info(const unsigned int xid,  	switch (rc) {  	case 0: -		rc = parse_create_response(data, cifs_sb, &out_iov[0]); +		rc = parse_create_response(data, cifs_sb, full_path, &out_iov[0]);  		break;  	case -EOPNOTSUPP:  		/*  		 * BB TODO: When support for special files added to Samba  		 * re-verify this path.  		 */ -		rc = parse_create_response(data, cifs_sb, &out_iov[0]); +		rc = parse_create_response(data, cifs_sb, full_path, &out_iov[0]);  		if (rc || !data->reparse_point)  			goto out; diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h index 71504b30909e..09349fa8da03 100644 --- a/fs/smb/client/smb2proto.h +++ b/fs/smb/client/smb2proto.h @@ -111,7 +111,14 @@ extern int smb3_query_mf_symlink(unsigned int xid, struct cifs_tcon *tcon,  			  struct cifs_sb_info *cifs_sb,  			  const unsigned char *path, char *pbuf,  			  unsigned int *pbytes_read); -int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, const struct kvec *iov, char **path); +int smb2_parse_native_symlink(char **target, const char *buf, unsigned int len, +			      bool unicode, bool relative, +			      const char *full_path, +			      struct cifs_sb_info *cifs_sb); +int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, +				const struct kvec *iov, +				const char *full_path, +				char **path);  int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms, __u32 *oplock,  		   void *buf);  extern int smb2_unlock_range(struct cifsFileInfo *cfile,  | 
