/* 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,
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,
rc = 0;
} else if (iov && server->ops->parse_reparse_point) {
rc = server->ops->parse_reparse_point(cifs_sb,
+ full_path,
iov, data);
}
break;
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;
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);
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:
}
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)
{
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,
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 */
}
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)
{
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
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;
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)
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));
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;
break;
case STATUS_STOPPED_ON_SYMLINK:
rc = smb2_parse_symlink_response(cifs_sb, iov,
+ full_path,
&data->symlink_target);
if (rc)
return rc;
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;
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,