2013/8/12 Jeff Layton <jlayton@xxxxxxxxxx>: > 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? Yes, it fails now. We can try to handle this error by resolving an actual path and resend the atomic open request but it is work for other patches, I think. > >> 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> -- Best regards, Pavel Shilovsky. -- 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