Bug 52791

Summary: cf_nlink always 0 for directories on Windows 7/2008 Server mounts
Product: File System Reporter: Dominik Schramm (dominik.schramm)
Component: CIFSAssignee: fs_cifs (fs_cifs)
Status: RESOLVED WILL_NOT_FIX    
Severity: normal CC: admin, alan, bugs, fengxiaoli0714, ihryll, jlayton, ntlgnt1, sitsofe, soubok+kernel, staskorz
Priority: P1    
Hardware: All   
OS: Linux   
Kernel Version: 3.7.1 Subsystem:
Regression: No Bisected commit-id:
Attachments: attachment-24633-0.html

Description Dominik Schramm 2013-01-18 11:39:26 UTC
Overview
----------------------

When mounting a CIFS share from a Microsoft Windows 7 or Windows Server 2008 (R1 or R2) system, the number of hard links of both directories and (under certain circumstances) ordinary files is wrong (always 0), no matter what the mount options are. Mounts from Windows 2003 Server and older are set correctly.

Steps to Reproduce
----------------------

1. mount a share from a Windows 7/2008 system to /mnt
   (mount option [no]unix has no effect)
2. ls -la /mnt   or    stat /mnt

Actual Results
----------------------

Directories on the share have the type "directory" (as reported by ls and stat) but a hard link count of 0.

Expected Results
----------------------

The hard link count for any directory should always be at least 2.

Build Date & Platform
----------------------

kernel version 3.7.1, built on January 10th, on an Ubuntu kernel 3.6.5 (custom compiled), x86_64, with gcc version 4.6.3-1ubuntu5.

Additional Information
----------------------

Local processes, especially the Bash, don't bother about the link count. Accessing files and listing directories, changing into them etc. are no problem. 

Samba, however is probably just one example of software relying on the number of hard links for its own purposes. For example, it treats directories with a 0 hard link count as ordinary files with size 0 (Samba clients cannot "click-change" into them, but they can access them directly), and files with 0 link count are presented to the client but inaccessible. See Demonstration below.

Workaround:

Any combination of mount options ([no]unix, [no]serverino, ...) yields the same results concerning the hard link count.

Patching the function cifs_fattr_to_inode works, although it doesn't seem to do anything different than the several other places in inode.c (e. g. cifs_all_info_to_fattr) that set cf_nlink.

*** linux-3.X.X-ORIGINAL/fs/cifs/inode.c 2012-12-06 14:58:18.248221824 +0100
--- linux-3.X.X/fs/cifs/inode.c 2013-01-10 14:02:38.367733296 +0100
***************
*** 132,138 ****
        inode->i_mtime = fattr->cf_mtime;
        inode->i_ctime = fattr->cf_ctime;
        inode->i_rdev = fattr->cf_rdev;
!       set_nlink(inode, fattr->cf_nlink);
        inode->i_uid = fattr->cf_uid;
        inode->i_gid = fattr->cf_gid;
--- 132,141 ----
        inode->i_mtime = fattr->cf_mtime;
        inode->i_ctime = fattr->cf_ctime;
        inode->i_rdev = fattr->cf_rdev;
!       if (fattr->cf_cifsattrs & ATTR_DIRECTORY)
!               set_nlink(inode, 2);
!       else
!               set_nlink(inode, 1);
        inode->i_uid = fattr->cf_uid;
        inode->i_gid = fattr->cf_gid;

In other words, the following piece of code in the current kernel does not work, as the link count cf_nlink seems to be reset to 0 somewhere else:

static void 
cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
                       struct cifs_sb_info *cifs_sb, bool adjust_tz)
{
...
        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;

Demonstration:

The following is a demonstration with kernel version 3.6.5, however the problem is still present in the current version 3.7.1.

root@log002:~# uname -r -m
3.6.5 x86_64
root@log002:~# mount.cifs -V
mount.cifs version: 5.7
root@log002:~# mount.cifs //sas003/testshare /mnt/testshare -o
serverino,user=administrator
Password for administrator@//sas003/testshare:
root@log002:~# ls -lai /mnt/testshare/
total 5
792352059440498915 drwxr-xr-x 2 root root    0 Nov  1 13:28 .
            917508 drwxr-xr-x 8 root root 4096 Nov  1 13:29 ..
                 3 drwxr-xr-x 0 root root    0 Nov  1 13:27 dir1
                 4 drwxr-xr-x 0 root root    0 Nov  1 13:28 dir2
                 5 -rwxr-xr-x 1 root root   15 Nov  1 13:27 file1.txt
                 6 -rwxr-xr-x 1 root root   22 Nov  1 13:28 file2.txt
root@log002:~# ls -lai /mnt/testshare/dir1
total 1
                 3 drwxr-xr-x 2 root root  0 Nov  1 13:27 .
792352059440498915 drwxr-xr-x 2 root root  0 Nov  1 13:28 ..
                 7 -rwxr-xr-x 1 root root 15 Nov  1 13:27 file_in_dir1.txt
root@log002:~# stat /mnt/testshare/dir1
  File: `/mnt/testshare/dir1'
  Size: 0               Blocks: 0          IO Block: 16384  directory
Device: 13h/19d Inode: 3           Links: 2
Access: (0755/drwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2012-11-04 16:50:36.564250000 +0100
Modify: 2012-11-01 13:27:59.035250000 +0100
Change: 2012-11-01 13:27:59.035250000 +0100
 Birth: -
root@log002:~#

This is what the Samba server (any version really) delivers to the client:

root@log002:~# smbclient //log002/testthis
Enter root's password: (empty)
Domain=[INTERNAL] OS=[Unix] Server=[Samba 3.6.3]
Server not using user level security and no password supplied.
smb: \> dir
  .                                   D        0  Thu Nov  1 13:28:24 2012
  ..                                  D        0  Thu Nov  1 13:29:23 2012
  dir1                                         0  Thu Nov  1 13:27:59 2012
  dir2                                         0  Thu Nov  1 13:28:04 2012
  file1.txt                           A       15  Thu Nov  1 13:27:43 2012
  file2.txt                           A       22  Thu Nov  1 13:28:32 2012

                64636 blocks of size 131072. 24954 blocks available
smb: \> cd dir1
smb: \dir1\> dir
  .                                   D        0  Thu Nov  1 13:27:59 2012
  ..                                  D        0  Thu Nov  1 13:28:24 2012
  file_in_dir1.txt                    A       15  Thu Nov  1 13:27:43 2012

                64636 blocks of size 131072. 24954 blocks available
smb: \dir1\>
root@log002:~#
Comment 1 Malte Starostik 2013-07-05 12:54:23 UTC
Me, too™
To be more precise: I can confirm the issue with 3.8.13 on the client and Windows Server 2003 R2 as well as samba on the server side, so this doesn't seem related to the Windows version really.
Workaround works for me, however the real fix should likely rather care about this "somewhere else":

(In reply to Dominik Schramm from comment #0)
> In other words, the following piece of code in the current kernel does not
> work, as the link count cf_nlink seems to be reset to 0 somewhere else:
Comment 2 ntlgnt1 2013-07-11 20:02:53 UTC
I can confirm this is a problem as well and needs to be resolved asap.

I am running:
kernel: 2.6.32-358.11.1.el6.x86_64
Samba: 3.6.9-151.el6.x86_64

When mounting CIFS share from EMC NAS and sharing via Samba, directories appear as regular files.

I attempted the 'workaround' above by modifying inode.c in cifs.ko but the function set_nlink did not exist so wrote my own fix to force inode to 2 on directories:

inode->i_rdev = fattr->cf_rdev;
    if (fattr->cf_cifsattrs & ATTR_DIRECTORY)
       inode->i_nlink = 2;
    else
       inode->i_nlink = fattr->cf_nlink;
inode->i_uid = fattr->cf_uid;
inode->i_gid = fattr->cf_gid;

This code fixed it for me. I imagine lots of people are having this issue.
Comment 3 Alan 2013-11-19 13:58:56 UTC
This appears to be a samba bug. It should be testing with S_ISDIR(). Setting up i_nlink might be nice but you would have to set it correctly to account for all the subdirectories or it'll confuse other software much of which treats "0" as "undefined" and avoids optimisations.


Also 2.6.32 is a completely obsolete kernel!

Closing as will not fix, but if you do cook up a full patch that gets i_nlink right for the subdirectory count then go for it.
Comment 4 Jeff Layton 2013-11-20 10:51:28 UTC
Not a samba bug actually, since the server in this case is Windows or in the case of comment #2, an EMC NAS. The CIFS protocol doesn't state that the server must return a value of at least 2 for the NumberOfLinks value on a directory like we expect with POSIX-y servers, so it's quite common for them to return a NumberOfLinks value of 0 on a directory.

We have some code in more modern kernels to make the client fake up an i_nlink value when this happens. If you're using RHEL6 above, then you're welcome to open a RH support case and ask us to backport those patches to RHEL6 kernel.
Comment 6 Sitsofe Wheeler 2014-02-25 09:09:07 UTC
The commit Jeff is refering to appears to be http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/fs/cifs?id=74d290da476f672ad756634d12aa707375d3564d . I can't see any part of it in the 3.2 stable kernel so I think there is a good case for backporting it...
Comment 7 bunkobugsy 2018-03-12 13:47:01 UTC
Opened backport request https://bugzilla.redhat.com/show_bug.cgi?id=1554342
Comment 8 Xiaoli Feng 2018-05-07 07:21:57 UTC
Test on 4.17.0-rc1. The cf_nlink is correct for the direcotry of mounted point. But when I create the son directory in the father directory. The cf_nlink of the father directory doesn't be changed.

-bash-4.2# stat cifs
  File: ‘cifs’
  Size: 20090880  	Blocks: 39240      IO Block: 16384  directory
Device: 2eh/46d	Inode: 281474976710691  Links: 2
Access: (0755/drwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Context: system_u:object_r:cifs_t:s0
Access: 2018-05-07 03:07:52.364814200 -0400
Modify: 2018-05-07 03:07:52.364814200 -0400
Change: 2018-05-07 03:07:52.364814200 -0400
 Birth: -
-bash-4.2# stat cifs/sub-dir
  File: ‘cifs/sub-dir’
  Size: 0         	Blocks: 0          IO Block: 16384  directory
Device: 2eh/46d	Inode: 19421773393972992  Links: 2
Access: (0755/drwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Context: system_u:object_r:cifs_t:s0
Access: 2018-05-07 03:07:52.364814200 -0400
Modify: 2018-05-07 03:07:52.364814200 -0400
Change: 2018-05-07 03:07:52.364814200 -0400
 Birth: -
-bash-4.2# stat cifs
  File: ‘cifs’
  Size: 20090880  	Blocks: 39240      IO Block: 16384  directory
Device: 2eh/46d	Inode: 281474976710691  Links: 2
Access: (0755/drwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Context: system_u:object_r:cifs_t:s0
Access: 2018-05-07 03:07:52.364814200 -0400
Modify: 2018-05-07 03:07:52.364814200 -0400
Change: 2018-05-07 03:07:52.364814200 -0400