• Aurelien Aptel's avatar
    smb2: fix missing files in root share directory listing · dcdb2262
    Aurelien Aptel authored
    commit 0595751f upstream.
    
    When mounting a Windows share that is the root of a drive (eg. C$)
    the server does not return . and .. directory entries. This results in
    the smb2 code path erroneously skipping the 2 first entries.
    
    Pseudo-code of the readdir() code path:
    
    cifs_readdir(struct file, struct dir_context)
        initiate_cifs_search            <-- if no reponse cached yet
            server->ops->query_dir_first
    
        dir_emit_dots
            dir_emit                    <-- adds "." and ".." if we're at pos=0
    
        find_cifs_entry
            initiate_cifs_search        <-- if pos < start of current response
                                             (restart search)
            server->ops->query_dir_next <-- if pos > end of current response
                                             (fetch next search res)
    
        for(...)                        <-- loops over cur response entries
                                              starting at pos
            cifs_filldir                <-- skip . and .., emit entry
                cifs_fill_dirent
                dir_emit
    	pos++
    
    A) dir_emit_dots() always adds . & ..
       and sets the current dir pos to 2 (0 and 1 are done).
    
    Therefore we always want the index_to_find to be 2 regardless of if
    the response has . and ..
    
    B) smb1 code initializes index_of_last_entry with a +2 offset
    
      in cifssmb.c CIFSFindFirst():
    		psrch_inf->index_of_last_entry = 2 /* skip . and .. */ +
    			psrch_inf->entries_in_buffer;
    
    Later in find_cifs_entry() we want to find the next dir entry at pos=2
    as a result of (A)
    
    	first_entry_in_buffer = cfile->srch_inf.index_of_last_entry -
    					cfile->srch_inf.entries_in_buffer;
    
    This var is the dir pos that the first entry in the buffer will
    have therefore it must be 2 in the first call.
    
    If we don't offset index_of_last_entry by 2 (like in (B)),
    first_entry_in_buffer=0 but we were instructed to get pos=2 so this
    code in find_cifs_entry() skips the 2 first which is ok for non-root
    shares, as it skips . and .. from the response but is not ok for root
    shares where the 2 first are actual files
    
    		pos_in_buf = index_to_find - first_entry_in_buffer;
                    // pos_in_buf=2
    		// we skip 2 first response entries :(
    		for (i = 0; (i < (pos_in_buf)) && (cur_ent != NULL); i++) {
    			/* go entry by entry figuring out which is first */
    			cur_ent = nxt_dir_entry(cur_ent, end_of_smb,
    						cfile->srch_inf.info_level);
    		}
    
    C) cifs_filldir() skips . and .. so we can safely ignore them for now.
    
    Sample program:
    
    int main(int argc, char **argv)
    {
    	const char *path = argc >= 2 ? argv[1] : ".";
    	DIR *dh;
    	struct dirent *de;
    
    	printf("listing path <%s>\n", path);
    	dh = opendir(path);
    	if (!dh) {
    		printf("opendir error %d\n", errno);
    		return 1;
    	}
    
    	while (1) {
    		de = readdir(dh);
    		if (!de) {
    			if (errno) {
    				printf("readdir error %d\n", errno);
    				return 1;
    			}
    			printf("end of listing\n");
    			break;
    		}
    		printf("off=%lu <%s>\n", de->d_off, de->d_name);
    	}
    
    	return 0;
    }
    
    Before the fix with SMB1 on root shares:
    
    <.>            off=1
    <..>           off=2
    <$Recycle.Bin> off=3
    <bootmgr>      off=4
    
    and on non-root shares:
    
    <.>    off=1
    <..>   off=4  <-- after adding .., the offsets jumps to +2 because
    <2536> off=5       we skipped . and .. from response buffer (C)
    <411>  off=6       but still incremented pos
    <file> off=7
    <fsx>  off=8
    
    Therefore the fix for smb2 is to mimic smb1 behaviour and offset the
    index_of_last_entry by 2.
    
    Test results comparing smb1 and smb2 before/after the fix on root
    share, non-root shares and on large directories (ie. multi-response
    dir listing):
    
    PRE FIX
    =======
    pre-1-root VS pre-2-root:
            ERR pre-2-root is missing [bootmgr, $Recycle.Bin]
    pre-1-nonroot VS pre-2-nonroot:
            OK~ same files, same order, different offsets
    pre-1-nonroot-large VS pre-2-nonroot-large:
            OK~ same files, same order, different offsets
    
    POST FIX
    ========
    post-1-root VS post-2-root:
            OK same files, same order, same offsets
    post-1-nonroot VS post-2-nonroot:
            OK same files, same order, same offsets
    post-1-nonroot-large VS post-2-nonroot-large:
            OK same files, same order, same offsets
    
    REGRESSION?
    ===========
    pre-1-root VS post-1-root:
            OK same files, same order, same offsets
    pre-1-nonroot VS post-1-nonroot:
            OK same files, same order, same offsets
    
    BugLink: https://bugzilla.samba.org/show_bug.cgi?id=13107Signed-off-by: 's avatarAurelien Aptel <aaptel@suse.com>
    Signed-off-by: 's avatarPaulo Alcantara <palcantara@suse.deR>
    Reviewed-by: 's avatarRonnie Sahlberg <lsahlber@redhat.com>
    Signed-off-by: 's avatarSteve French <stfrench@microsoft.com>
    CC: Stable <stable@vger.kernel.org>
    Signed-off-by: 's avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
    dcdb2262
Name
Last commit
Last update
..
Kconfig Loading commit data...
Makefile Loading commit data...
asn1.c Loading commit data...
cache.c Loading commit data...
cifs_debug.c Loading commit data...
cifs_debug.h Loading commit data...
cifs_dfs_ref.c Loading commit data...
cifs_fs_sb.h Loading commit data...
cifs_ioctl.h Loading commit data...
cifs_spnego.c Loading commit data...
cifs_spnego.h Loading commit data...
cifs_unicode.c Loading commit data...
cifs_unicode.h Loading commit data...
cifs_uniupr.h Loading commit data...
cifsacl.c Loading commit data...
cifsacl.h Loading commit data...
cifsencrypt.c Loading commit data...
cifsfs.c Loading commit data...
cifsfs.h Loading commit data...
cifsglob.h Loading commit data...
cifspdu.h Loading commit data...
cifsproto.h Loading commit data...
cifssmb.c Loading commit data...
connect.c Loading commit data...
dir.c Loading commit data...
dns_resolve.c Loading commit data...
dns_resolve.h Loading commit data...
export.c Loading commit data...
file.c Loading commit data...
fscache.c Loading commit data...
fscache.h Loading commit data...
inode.c Loading commit data...
ioctl.c Loading commit data...
link.c Loading commit data...
misc.c Loading commit data...
netmisc.c Loading commit data...
nterr.c Loading commit data...
nterr.h Loading commit data...
ntlmssp.h Loading commit data...
readdir.c Loading commit data...
rfc1002pdu.h Loading commit data...
sess.c Loading commit data...
smb1ops.c Loading commit data...
smb2file.c Loading commit data...
smb2glob.h Loading commit data...
smb2inode.c Loading commit data...
smb2maperror.c Loading commit data...
smb2misc.c Loading commit data...
smb2ops.c Loading commit data...
smb2pdu.c Loading commit data...
smb2pdu.h Loading commit data...
smb2proto.h Loading commit data...
smb2status.h Loading commit data...
smb2transport.c Loading commit data...
smbencrypt.c Loading commit data...
smberr.h Loading commit data...
smbfsctl.h Loading commit data...
transport.c Loading commit data...
winucase.c Loading commit data...
xattr.c Loading commit data...