Skip to content

Commit

Permalink
[CIFS] Provide sane values for nlink
Browse files Browse the repository at this point in the history
Since we don't get info about the number of links from the readdir
linfo levels, stat() will return 0 for st_nlink, and in particular,
samba re-exported shares will show directories as files (as samba is
keying off st_nlink before evaluating how to set the dos modebits)
when doing a dir or ls.

Copy nlink to the inode, unless it wasn't provided.  Provide
sane values if we don't have an existing one and none was provided.

Signed-off-by: Jim McDonough <jmcd@samba.org>
Reviewed-by: Jeff Layton <jlayton@redhat.com>
Reviewed-by: David Disseldorp <ddiss@samba.org>
Signed-off-by: Steve French <smfrench@gmail.com>
  • Loading branch information
Jim McDonough authored and Steve French committed Sep 21, 2013
1 parent 9ae6cf6 commit 74d290d
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 6 deletions.
1 change: 1 addition & 0 deletions fs/cifs/cifsglob.h
Original file line number Diff line number Diff line change
Expand Up @@ -1268,6 +1268,7 @@ struct dfs_info3_param {
#define CIFS_FATTR_DELETE_PENDING 0x2
#define CIFS_FATTR_NEED_REVAL 0x4
#define CIFS_FATTR_INO_COLLISION 0x8
#define CIFS_FATTR_UNKNOWN_NLINK 0x10

struct cifs_fattr {
u32 cf_flags;
Expand Down
45 changes: 39 additions & 6 deletions fs/cifs/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,33 @@ cifs_revalidate_cache(struct inode *inode, struct cifs_fattr *fattr)
cifs_i->invalid_mapping = true;
}

/*
* copy nlink to the inode, unless it wasn't provided. Provide
* sane values if we don't have an existing one and none was provided
*/
static void
cifs_nlink_fattr_to_inode(struct inode *inode, struct cifs_fattr *fattr)
{
/*
* if we're in a situation where we can't trust what we
* got from the server (readdir, some non-unix cases)
* fake reasonable values
*/
if (fattr->cf_flags & CIFS_FATTR_UNKNOWN_NLINK) {
/* only provide fake values on a new inode */
if (inode->i_state & I_NEW) {
if (fattr->cf_cifsattrs & ATTR_DIRECTORY)
set_nlink(inode, 2);
else
set_nlink(inode, 1);
}
return;
}

/* we trust the server, so update it */
set_nlink(inode, fattr->cf_nlink);
}

/* populate an inode with info from a cifs_fattr struct */
void
cifs_fattr_to_inode(struct inode *inode, struct cifs_fattr *fattr)
Expand All @@ -134,7 +161,7 @@ cifs_fattr_to_inode(struct inode *inode, struct cifs_fattr *fattr)
inode->i_mtime = fattr->cf_mtime;
inode->i_ctime = fattr->cf_ctime;
inode->i_rdev = fattr->cf_rdev;
set_nlink(inode, fattr->cf_nlink);
cifs_nlink_fattr_to_inode(inode, fattr);
inode->i_uid = fattr->cf_uid;
inode->i_gid = fattr->cf_gid;

Expand Down Expand Up @@ -541,14 +568,16 @@ cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
fattr->cf_bytes = le64_to_cpu(info->AllocationSize);
fattr->cf_createtime = le64_to_cpu(info->CreationTime);

fattr->cf_nlink = le32_to_cpu(info->NumberOfLinks);
if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
fattr->cf_mode = S_IFDIR | cifs_sb->mnt_dir_mode;
fattr->cf_dtype = DT_DIR;
/*
* Server can return wrong NumberOfLinks value for directories
* when Unix extensions are disabled - fake it.
*/
fattr->cf_nlink = 2;
if (!tcon->unix_ext)
fattr->cf_flags |= CIFS_FATTR_UNKNOWN_NLINK;
} else if (fattr->cf_cifsattrs & ATTR_REPARSE) {
fattr->cf_mode = S_IFLNK;
fattr->cf_dtype = DT_LNK;
Expand All @@ -561,11 +590,15 @@ cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
if (fattr->cf_cifsattrs & ATTR_READONLY)
fattr->cf_mode &= ~(S_IWUGO);

fattr->cf_nlink = le32_to_cpu(info->NumberOfLinks);
if (fattr->cf_nlink < 1) {
cifs_dbg(1, "replacing bogus file nlink value %u\n",
/*
* Don't accept zero nlink from non-unix servers unless
* delete is pending. Instead mark it as unknown.
*/
if ((fattr->cf_nlink < 1) && !tcon->unix_ext &&
!info->DeletePending) {
cifs_dbg(1, "bogus file nlink value %u\n",
fattr->cf_nlink);
fattr->cf_nlink = 1;
fattr->cf_flags |= CIFS_FATTR_UNKNOWN_NLINK;
}
}

Expand Down
3 changes: 3 additions & 0 deletions fs/cifs/readdir.c
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,9 @@ cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb)
fattr->cf_dtype = DT_REG;
}

/* non-unix readdir doesn't provide nlink */
fattr->cf_flags |= CIFS_FATTR_UNKNOWN_NLINK;

if (fattr->cf_cifsattrs & ATTR_READONLY)
fattr->cf_mode &= ~S_IWUGO;

Expand Down

0 comments on commit 74d290d

Please sign in to comment.