CIFS: Fix symbolic links usage

Now we treat any reparse point as a symbolic link and map it to a Unix
one that is not true in a common case due to many reparse point types
supported by SMB servers.

Distinguish reparse point types into two groups:
1) that can be accessed directly through a reparse point
(junctions, deduplicated files, NFS symlinks);
2) that need to be processed manually (Windows symbolic links, DFS);

and map only Windows symbolic links to Unix ones.

Cc: <stable@vger.kernel.org>
Acked-by: Jeff Layton <jlayton@redhat.com>
Reported-and-tested-by: Joao Correia <joaomiguelcorreia@gmail.com>
Signed-off-by: Pavel Shilovsky <piastry@etersoft.ru>
Signed-off-by: Steve French <smfrench@gmail.com>
This commit is contained in:
Pavel Shilovsky 2013-10-23 17:49:47 +04:00 committed by Steve French
parent 6c86ae2928
commit eb85d94bdd
6 changed files with 55 additions and 49 deletions

View File

@ -261,7 +261,7 @@ struct smb_version_operations {
/* query path data from the server */ /* query path data from the server */
int (*query_path_info)(const unsigned int, struct cifs_tcon *, int (*query_path_info)(const unsigned int, struct cifs_tcon *,
struct cifs_sb_info *, const char *, struct cifs_sb_info *, const char *,
FILE_ALL_INFO *, bool *); FILE_ALL_INFO *, bool *, bool *);
/* query file data from the server */ /* query file data from the server */
int (*query_file_info)(const unsigned int, struct cifs_tcon *, int (*query_file_info)(const unsigned int, struct cifs_tcon *,
struct cifs_fid *, FILE_ALL_INFO *); struct cifs_fid *, FILE_ALL_INFO *);

View File

@ -542,7 +542,8 @@ static int cifs_sfu_mode(struct cifs_fattr *fattr, const unsigned char *path,
/* Fill a cifs_fattr struct with info from FILE_ALL_INFO */ /* Fill a cifs_fattr struct with info from FILE_ALL_INFO */
static void static void
cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info, cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
struct cifs_sb_info *cifs_sb, bool adjust_tz) struct cifs_sb_info *cifs_sb, bool adjust_tz,
bool symlink)
{ {
struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb); struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
@ -569,7 +570,11 @@ cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
fattr->cf_createtime = le64_to_cpu(info->CreationTime); fattr->cf_createtime = le64_to_cpu(info->CreationTime);
fattr->cf_nlink = le32_to_cpu(info->NumberOfLinks); fattr->cf_nlink = le32_to_cpu(info->NumberOfLinks);
if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
if (symlink) {
fattr->cf_mode = S_IFLNK;
fattr->cf_dtype = DT_LNK;
} else if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
fattr->cf_mode = S_IFDIR | cifs_sb->mnt_dir_mode; fattr->cf_mode = S_IFDIR | cifs_sb->mnt_dir_mode;
fattr->cf_dtype = DT_DIR; fattr->cf_dtype = DT_DIR;
/* /*
@ -578,10 +583,6 @@ cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
*/ */
if (!tcon->unix_ext) if (!tcon->unix_ext)
fattr->cf_flags |= CIFS_FATTR_UNKNOWN_NLINK; fattr->cf_flags |= CIFS_FATTR_UNKNOWN_NLINK;
} 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 { } else {
fattr->cf_mode = S_IFREG | cifs_sb->mnt_file_mode; fattr->cf_mode = S_IFREG | cifs_sb->mnt_file_mode;
fattr->cf_dtype = DT_REG; fattr->cf_dtype = DT_REG;
@ -626,7 +627,8 @@ cifs_get_file_info(struct file *filp)
rc = server->ops->query_file_info(xid, tcon, &cfile->fid, &find_data); rc = server->ops->query_file_info(xid, tcon, &cfile->fid, &find_data);
switch (rc) { switch (rc) {
case 0: case 0:
cifs_all_info_to_fattr(&fattr, &find_data, cifs_sb, false); cifs_all_info_to_fattr(&fattr, &find_data, cifs_sb, false,
false);
break; break;
case -EREMOTE: case -EREMOTE:
cifs_create_dfs_fattr(&fattr, inode->i_sb); cifs_create_dfs_fattr(&fattr, inode->i_sb);
@ -673,6 +675,7 @@ cifs_get_inode_info(struct inode **inode, const char *full_path,
bool adjust_tz = false; bool adjust_tz = false;
struct cifs_fattr fattr; struct cifs_fattr fattr;
struct cifs_search_info *srchinf = NULL; struct cifs_search_info *srchinf = NULL;
bool symlink = false;
tlink = cifs_sb_tlink(cifs_sb); tlink = cifs_sb_tlink(cifs_sb);
if (IS_ERR(tlink)) if (IS_ERR(tlink))
@ -702,12 +705,12 @@ cifs_get_inode_info(struct inode **inode, const char *full_path,
} }
data = (FILE_ALL_INFO *)buf; data = (FILE_ALL_INFO *)buf;
rc = server->ops->query_path_info(xid, tcon, cifs_sb, full_path, rc = server->ops->query_path_info(xid, tcon, cifs_sb, full_path,
data, &adjust_tz); data, &adjust_tz, &symlink);
} }
if (!rc) { if (!rc) {
cifs_all_info_to_fattr(&fattr, (FILE_ALL_INFO *)data, cifs_sb, cifs_all_info_to_fattr(&fattr, data, cifs_sb, adjust_tz,
adjust_tz); symlink);
} else if (rc == -EREMOTE) { } else if (rc == -EREMOTE) {
cifs_create_dfs_fattr(&fattr, sb); cifs_create_dfs_fattr(&fattr, sb);
rc = 0; rc = 0;

View File

@ -134,22 +134,6 @@ out:
dput(dentry); dput(dentry);
} }
/*
* Is it possible that this directory might turn out to be a DFS referral
* once we go to try and use it?
*/
static bool
cifs_dfs_is_possible(struct cifs_sb_info *cifs_sb)
{
#ifdef CONFIG_CIFS_DFS_UPCALL
struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
if (tcon->Flags & SMB_SHARE_IS_IN_DFS)
return true;
#endif
return false;
}
static void static void
cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb) cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb)
{ {
@ -159,27 +143,19 @@ cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb)
if (fattr->cf_cifsattrs & ATTR_DIRECTORY) { if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
fattr->cf_mode = S_IFDIR | cifs_sb->mnt_dir_mode; fattr->cf_mode = S_IFDIR | cifs_sb->mnt_dir_mode;
fattr->cf_dtype = DT_DIR; fattr->cf_dtype = DT_DIR;
/*
* Windows CIFS servers generally make DFS referrals look
* like directories in FIND_* responses with the reparse
* attribute flag also set (since DFS junctions are
* reparse points). We must revalidate at least these
* directory inodes before trying to use them (if
* they are DFS we will get PATH_NOT_COVERED back
* when queried directly and can then try to connect
* to the DFS target)
*/
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 { } else {
fattr->cf_mode = S_IFREG | cifs_sb->mnt_file_mode; fattr->cf_mode = S_IFREG | cifs_sb->mnt_file_mode;
fattr->cf_dtype = DT_REG; fattr->cf_dtype = DT_REG;
} }
/*
* We need to revalidate it further to make a decision about whether it
* is a symbolic link, DFS referral or a reparse point with a direct
* access like junctions, deduplicated files, NFS symlinks.
*/
if (fattr->cf_cifsattrs & ATTR_REPARSE)
fattr->cf_flags |= CIFS_FATTR_NEED_REVAL;
/* non-unix readdir doesn't provide nlink */ /* non-unix readdir doesn't provide nlink */
fattr->cf_flags |= CIFS_FATTR_UNKNOWN_NLINK; fattr->cf_flags |= CIFS_FATTR_UNKNOWN_NLINK;

View File

@ -534,10 +534,12 @@ cifs_is_path_accessible(const unsigned int xid, struct cifs_tcon *tcon,
static int static int
cifs_query_path_info(const unsigned int xid, struct cifs_tcon *tcon, cifs_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
struct cifs_sb_info *cifs_sb, const char *full_path, struct cifs_sb_info *cifs_sb, const char *full_path,
FILE_ALL_INFO *data, bool *adjustTZ) FILE_ALL_INFO *data, bool *adjustTZ, bool *symlink)
{ {
int rc; int rc;
*symlink = false;
/* could do find first instead but this returns more info */ /* could do find first instead but this returns more info */
rc = CIFSSMBQPathInfo(xid, tcon, full_path, data, 0 /* not legacy */, rc = CIFSSMBQPathInfo(xid, tcon, full_path, data, 0 /* not legacy */,
cifs_sb->local_nls, cifs_sb->mnt_cifs_flags & cifs_sb->local_nls, cifs_sb->mnt_cifs_flags &
@ -554,6 +556,23 @@ cifs_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
CIFS_MOUNT_MAP_SPECIAL_CHR); CIFS_MOUNT_MAP_SPECIAL_CHR);
*adjustTZ = true; *adjustTZ = true;
} }
if (!rc && (le32_to_cpu(data->Attributes) & ATTR_REPARSE)) {
int tmprc;
int oplock = 0;
__u16 netfid;
/* Need to check if this is a symbolic link or not */
tmprc = CIFSSMBOpen(xid, tcon, full_path, FILE_OPEN,
FILE_READ_ATTRIBUTES, 0, &netfid, &oplock,
NULL, cifs_sb->local_nls,
cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR);
if (tmprc == -EOPNOTSUPP)
*symlink = true;
else
CIFSSMBClose(xid, tcon, netfid);
}
return rc; return rc;
} }

View File

@ -123,12 +123,13 @@ move_smb2_info_to_cifs(FILE_ALL_INFO *dst, struct smb2_file_all_info *src)
int int
smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon, smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
struct cifs_sb_info *cifs_sb, const char *full_path, struct cifs_sb_info *cifs_sb, const char *full_path,
FILE_ALL_INFO *data, bool *adjust_tz) FILE_ALL_INFO *data, bool *adjust_tz, bool *symlink)
{ {
int rc; int rc;
struct smb2_file_all_info *smb2_data; struct smb2_file_all_info *smb2_data;
*adjust_tz = false; *adjust_tz = false;
*symlink = false;
smb2_data = kzalloc(sizeof(struct smb2_file_all_info) + MAX_NAME * 2, smb2_data = kzalloc(sizeof(struct smb2_file_all_info) + MAX_NAME * 2,
GFP_KERNEL); GFP_KERNEL);
@ -136,9 +137,16 @@ smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
return -ENOMEM; return -ENOMEM;
rc = smb2_open_op_close(xid, tcon, cifs_sb, full_path, rc = smb2_open_op_close(xid, tcon, cifs_sb, full_path,
FILE_READ_ATTRIBUTES, FILE_OPEN, FILE_READ_ATTRIBUTES, FILE_OPEN, 0,
OPEN_REPARSE_POINT, smb2_data, smb2_data, SMB2_OP_QUERY_INFO);
SMB2_OP_QUERY_INFO); if (rc == -EOPNOTSUPP) {
*symlink = true;
/* Failed on a symbolic link - query a reparse point info */
rc = smb2_open_op_close(xid, tcon, cifs_sb, full_path,
FILE_READ_ATTRIBUTES, FILE_OPEN,
OPEN_REPARSE_POINT, smb2_data,
SMB2_OP_QUERY_INFO);
}
if (rc) if (rc)
goto out; goto out;

View File

@ -61,7 +61,7 @@ extern void move_smb2_info_to_cifs(FILE_ALL_INFO *dst,
extern int smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon, extern int smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
struct cifs_sb_info *cifs_sb, struct cifs_sb_info *cifs_sb,
const char *full_path, FILE_ALL_INFO *data, const char *full_path, FILE_ALL_INFO *data,
bool *adjust_tz); bool *adjust_tz, bool *symlink);
extern int smb2_set_path_size(const unsigned int xid, struct cifs_tcon *tcon, extern int smb2_set_path_size(const unsigned int xid, struct cifs_tcon *tcon,
const char *full_path, __u64 size, const char *full_path, __u64 size,
struct cifs_sb_info *cifs_sb, bool set_alloc); struct cifs_sb_info *cifs_sb, bool set_alloc);