[CIFS] Add support for readdir to legacy servers
authorSteve French <sfrench@us.ibm.com>
Wed, 7 Jun 2006 00:18:43 +0000 (00:18 +0000)
committerSteve French <sfrench@us.ibm.com>
Wed, 7 Jun 2006 00:18:43 +0000 (00:18 +0000)
Fixes oops to OS/2 on ls and removes redundant NTCreateX calls to servers
which do not support NT SMBs.  Key operations to OS/2 work.

Signed-off-by: Steve French <sfrench@us.ibm.com>
fs/cifs/CHANGES
fs/cifs/cifspdu.h
fs/cifs/dir.c
fs/cifs/file.c
fs/cifs/readdir.c

index b878dfc..7e0058b 100644 (file)
@@ -2,6 +2,10 @@ Version 1.44
 ------------
 Rewritten sessionsetup support, including support for legacy SMB
 session setup needed for OS/2 and older servers such as Windows 95 and 98.
+Fix oops on ls to OS/2 servers.  Add support for level 1 FindFirst
+so we can do search (ls etc.) to OS/2.  Do not send NTCreateX
+or recent levels of FindFirst unless server says it supports NT SMBs
+(instead use legacy equivalents from LANMAN dialect).
 
 Version 1.43
 ------------
index 5250b93..8623902 100644 (file)
@@ -1374,6 +1374,9 @@ struct smb_t2_rsp {
 #define SMB_FILE_MAXIMUM_INFO           0x40d
 
 /* Find File infolevels */
+#define SMB_FIND_FILE_INFO_STANDARD       0x001
+#define SMB_FIND_FILE_QUERY_EA_SIZE       0x002
+#define SMB_FIND_FILE_QUERY_EAS_FROM_LIST 0x003
 #define SMB_FIND_FILE_DIRECTORY_INFO      0x101
 #define SMB_FIND_FILE_FULL_DIRECTORY_INFO 0x102
 #define SMB_FIND_FILE_NAMES_INFO          0x103
@@ -1998,7 +2001,8 @@ typedef struct {
 
 struct file_allocation_info {
        __le64 AllocationSize; /* Note old Samba srvr rounds this up too much */
-} __attribute__((packed));     /* size used on disk, level 0x103 for set, 0x105 for query */
+} __attribute__((packed));     /* size used on disk, for level 0x103 for set,
+                                  0x105 for query */
 
 struct file_end_of_file_info {
        __le64 FileSize;                /* offset to end of file */
@@ -2105,7 +2109,7 @@ typedef struct {
        __le32 ExtFileAttributes;
        __le32 FileNameLength;
        char FileName[1];
-} __attribute__((packed)) FILE_DIRECTORY_INFO;   /* level 0x101 FF response data area */
+} __attribute__((packed)) FILE_DIRECTORY_INFO;   /* level 0x101 FF resp data */
 
 typedef struct {
        __le32 NextEntryOffset;
@@ -2120,7 +2124,7 @@ typedef struct {
        __le32 FileNameLength;
        __le32 EaSize; /* length of the xattrs */
        char FileName[1];
-} __attribute__((packed)) FILE_FULL_DIRECTORY_INFO;   /* level 0x102 FF response data area */
+} __attribute__((packed)) FILE_FULL_DIRECTORY_INFO; /* level 0x102 rsp data */
 
 typedef struct {
        __le32 NextEntryOffset;
@@ -2137,7 +2141,7 @@ typedef struct {
        __le32 Reserved;
        __u64 UniqueId; /* inode num - le since Samba puts ino in low 32 bit*/
        char FileName[1];
-} __attribute__((packed)) SEARCH_ID_FULL_DIR_INFO;   /* level 0x105 FF response data area */
+} __attribute__((packed)) SEARCH_ID_FULL_DIR_INFO; /* level 0x105 FF rsp data */
 
 typedef struct {
        __le32 NextEntryOffset;
@@ -2155,7 +2159,22 @@ typedef struct {
        __u8   Reserved;
        __u8   ShortName[12];
        char FileName[1];
-} __attribute__((packed)) FILE_BOTH_DIRECTORY_INFO;   /* level 0x104 FF response data area */
+} __attribute__((packed)) FILE_BOTH_DIRECTORY_INFO; /* level 0x104 FFrsp data */
+
+typedef struct {
+       __u32  ResumeKey;
+       __le16 CreationDate; /* SMB Date */
+       __le16 CreationTime; /* SMB Time */
+       __le16 LastAccessDate;
+       __le16 LastAccessTime;
+       __le16 LastWriteDate;
+       __le16 LastWriteTime;
+       __le32 DataSize; /* File Size (EOF) */
+       __le32 AllocationSize;
+       __le16 Attributes; /* verify not u32 */
+       __u8   FileNameLength;
+       char FileName[1];
+} __attribute__((packed)) FIND_FILE_STANDARD_INFO; /* level 0x1 FF resp data */
 
 
 struct win_dev {
index e6ed64e..ba4cbe9 100644 (file)
@@ -178,11 +178,14 @@ cifs_create(struct inode *inode, struct dentry *direntry, int mode,
                FreeXid(xid);
                return -ENOMEM;
        }
-
-       rc = CIFSSMBOpen(xid, pTcon, full_path, disposition,
+       if (cifs_sb->tcon->ses->capabilities & CAP_NT_SMBS) 
+               rc = CIFSSMBOpen(xid, pTcon, full_path, disposition,
                         desiredAccess, CREATE_NOT_DIR,
                         &fileHandle, &oplock, buf, cifs_sb->local_nls,
                         cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR);
+       else
+               rc = -EIO; /* no NT SMB support fall into legacy open below */
+
        if(rc == -EIO) {
                /* old server, retry the open legacy style */
                rc = SMBLegacyOpen(xid, pTcon, full_path, disposition,
@@ -369,6 +372,10 @@ int cifs_mknod(struct inode *inode, struct dentry *direntry, int mode,
                                         cifs_sb->mnt_cifs_flags & 
                                            CIFS_MOUNT_MAP_SPECIAL_CHR);
 
+                       /* BB FIXME - add handling for backlevel servers
+                          which need legacy open and check for all
+                          calls to SMBOpen for fallback to 
+                          SMBLeagcyOpen */
                        if(!rc) {
                                /* BB Do not bother to decode buf since no
                                   local inode yet to put timestamps in,
index d62e29f..fafdcdf 100644 (file)
@@ -260,10 +260,15 @@ int cifs_open(struct inode *inode, struct file *file)
                rc = -ENOMEM;
                goto out;
        }
-       rc = CIFSSMBOpen(xid, pTcon, full_path, disposition, desiredAccess,
-                        CREATE_NOT_DIR, &netfid, &oplock, buf,
+
+       if (cifs_sb->tcon->ses->capabilities & CAP_NT_SMBS)
+               rc = CIFSSMBOpen(xid, pTcon, full_path, disposition, 
+                        desiredAccess, CREATE_NOT_DIR, &netfid, &oplock, buf,
                         cifs_sb->local_nls, cifs_sb->mnt_cifs_flags
                                 & CIFS_MOUNT_MAP_SPECIAL_CHR);
+       else
+               rc = -EIO; /* no NT SMB support fall into legacy open below */
+
        if (rc == -EIO) {
                /* Old server, try legacy style OpenX */
                rc = SMBLegacyOpen(xid, pTcon, full_path, disposition,
index e3e762d..03bbcb3 100644 (file)
@@ -109,32 +109,52 @@ static int construct_dentry(struct qstr *qstring, struct file *file,
        return rc;
 }
 
-static void fill_in_inode(struct inode *tmp_inode,
-       FILE_DIRECTORY_INFO *pfindData, int *pobject_type, int isNewInode)
+static void fill_in_inode(struct inode *tmp_inode, int new_buf_type,
+               char * buf, int *pobject_type, int isNewInode)
 {
        loff_t local_size;
        struct timespec local_mtime;
 
        struct cifsInodeInfo *cifsInfo = CIFS_I(tmp_inode);
        struct cifs_sb_info *cifs_sb = CIFS_SB(tmp_inode->i_sb);
-       __u32 attr = le32_to_cpu(pfindData->ExtFileAttributes);
-       __u64 allocation_size = le64_to_cpu(pfindData->AllocationSize);
-       __u64 end_of_file = le64_to_cpu(pfindData->EndOfFile);
-
-       cifsInfo->cifsAttrs = attr;
-       cifsInfo->time = jiffies;
+       __u32 attr;
+       __u64 allocation_size;
+       __u64 end_of_file;
 
        /* save mtime and size */
        local_mtime = tmp_inode->i_mtime;
        local_size  = tmp_inode->i_size;
 
+       if(new_buf_type) {
+               FILE_DIRECTORY_INFO *pfindData = (FILE_DIRECTORY_INFO *)buf;
+
+               attr = le32_to_cpu(pfindData->ExtFileAttributes);
+               allocation_size = le64_to_cpu(pfindData->AllocationSize);
+               end_of_file = le64_to_cpu(pfindData->EndOfFile);
+               tmp_inode->i_atime =
+                     cifs_NTtimeToUnix(le64_to_cpu(pfindData->LastAccessTime));
+               tmp_inode->i_mtime =
+                     cifs_NTtimeToUnix(le64_to_cpu(pfindData->LastWriteTime));
+               tmp_inode->i_ctime =
+                     cifs_NTtimeToUnix(le64_to_cpu(pfindData->ChangeTime));
+       } else { /* legacy, OS2 and DOS style */
+               FIND_FILE_STANDARD_INFO * pfindData = 
+                       (FIND_FILE_STANDARD_INFO *)buf;
+
+               attr = le16_to_cpu(pfindData->Attributes);
+               allocation_size = le32_to_cpu(pfindData->AllocationSize);
+               end_of_file = le32_to_cpu(pfindData->DataSize);
+               tmp_inode->i_atime = CURRENT_TIME;
+               /* tmp_inode->i_mtime =  BB FIXME - add dos time handling
+               tmp_inode->i_ctime = 0;   BB FIXME */
+
+       }
+
        /* Linux can not store file creation time unfortunately so ignore it */
-       tmp_inode->i_atime =
-           cifs_NTtimeToUnix(le64_to_cpu(pfindData->LastAccessTime));
-       tmp_inode->i_mtime =
-           cifs_NTtimeToUnix(le64_to_cpu(pfindData->LastWriteTime));
-       tmp_inode->i_ctime =
-           cifs_NTtimeToUnix(le64_to_cpu(pfindData->ChangeTime));
+
+       cifsInfo->cifsAttrs = attr;
+       cifsInfo->time = jiffies;
+
        /* treat dos attribute of read-only as read-only mode bit e.g. 555? */
        /* 2767 perms - indicate mandatory locking */
                /* BB fill in uid and gid here? with help from winbind? 
@@ -420,7 +440,10 @@ static int initiate_cifs_search(const int xid, struct file *file)
 ffirst_retry:
        /* test for Unix extensions */
        if (pTcon->ses->capabilities & CAP_UNIX) {
-               cifsFile->srch_inf.info_level = SMB_FIND_FILE_UNIX; 
+               cifsFile->srch_inf.info_level = SMB_FIND_FILE_UNIX;
+       } else if ((pTcon->ses->capabilities & 
+                       (CAP_NT_SMBS | CAP_NT_FIND)) == 0) {
+               cifsFile->srch_inf.info_level = SMB_FIND_FILE_INFO_STANDARD;
        } else if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SERVER_INUM) {
                cifsFile->srch_inf.info_level = SMB_FIND_FILE_ID_FULL_DIR_INFO;
        } else /* not srvinos - BB fixme add check for backlevel? */ {
@@ -456,12 +479,19 @@ static int cifs_unicode_bytelen(char *str)
        return len << 1;
 }
 
-static char *nxt_dir_entry(char *old_entry, char *end_of_smb)
+static char *nxt_dir_entry(char *old_entry, char *end_of_smb, int level)
 {
        char * new_entry;
        FILE_DIRECTORY_INFO * pDirInfo = (FILE_DIRECTORY_INFO *)old_entry;
 
-       new_entry = old_entry + le32_to_cpu(pDirInfo->NextEntryOffset);
+       if(level == SMB_FIND_FILE_INFO_STANDARD) {
+               FIND_FILE_STANDARD_INFO * pfData;
+               pfData = (FIND_FILE_STANDARD_INFO *)pDirInfo;
+
+               new_entry = old_entry + sizeof(FIND_FILE_STANDARD_INFO) +
+                               pfData->FileNameLength;
+       } else
+               new_entry = old_entry + le32_to_cpu(pDirInfo->NextEntryOffset);
        cFYI(1,("new entry %p old entry %p",new_entry,old_entry));
        /* validate that new_entry is not past end of SMB */
        if(new_entry >= end_of_smb) {
@@ -469,7 +499,10 @@ static char *nxt_dir_entry(char *old_entry, char *end_of_smb)
                      ("search entry %p began after end of SMB %p old entry %p",
                        new_entry, end_of_smb, old_entry)); 
                return NULL;
-       } else if (new_entry + sizeof(FILE_DIRECTORY_INFO) > end_of_smb) {
+       } else if(((level == SMB_FIND_FILE_INFO_STANDARD) &&
+                  (new_entry + sizeof(FIND_FILE_STANDARD_INFO) > end_of_smb)) ||
+                 ((level != SMB_FIND_FILE_INFO_STANDARD) &&
+                  (new_entry + sizeof(FILE_DIRECTORY_INFO) > end_of_smb)))  {
                cERROR(1,("search entry %p extends after end of SMB %p",
                        new_entry, end_of_smb));
                return NULL;
@@ -487,7 +520,7 @@ static int cifs_entry_is_dot(char *current_entry, struct cifsFileInfo *cfile)
        char * filename = NULL;
        int len = 0; 
 
-       if(cfile->srch_inf.info_level == 0x202) {
+       if(cfile->srch_inf.info_level == SMB_FIND_FILE_UNIX) {
                FILE_UNIX_INFO * pFindData = (FILE_UNIX_INFO *)current_entry;
                filename = &pFindData->FileName[0];
                if(cfile->srch_inf.unicode) {
@@ -496,26 +529,34 @@ static int cifs_entry_is_dot(char *current_entry, struct cifsFileInfo *cfile)
                        /* BB should we make this strnlen of PATH_MAX? */
                        len = strnlen(filename, 5);
                }
-       } else if(cfile->srch_inf.info_level == 0x101) {
+       } else if(cfile->srch_inf.info_level == SMB_FIND_FILE_DIRECTORY_INFO) {
                FILE_DIRECTORY_INFO * pFindData = 
                        (FILE_DIRECTORY_INFO *)current_entry;
                filename = &pFindData->FileName[0];
                len = le32_to_cpu(pFindData->FileNameLength);
-       } else if(cfile->srch_inf.info_level == 0x102) {
+       } else if(cfile->srch_inf.info_level == 
+                       SMB_FIND_FILE_FULL_DIRECTORY_INFO) {
                FILE_FULL_DIRECTORY_INFO * pFindData = 
                        (FILE_FULL_DIRECTORY_INFO *)current_entry;
                filename = &pFindData->FileName[0];
                len = le32_to_cpu(pFindData->FileNameLength);
-       } else if(cfile->srch_inf.info_level == 0x105) {
+       } else if(cfile->srch_inf.info_level ==
+                       SMB_FIND_FILE_ID_FULL_DIR_INFO) {
                SEARCH_ID_FULL_DIR_INFO * pFindData = 
                        (SEARCH_ID_FULL_DIR_INFO *)current_entry;
                filename = &pFindData->FileName[0];
                len = le32_to_cpu(pFindData->FileNameLength);
-       } else if(cfile->srch_inf.info_level == 0x104) {
+       } else if(cfile->srch_inf.info_level == 
+                       SMB_FIND_FILE_BOTH_DIRECTORY_INFO) {
                FILE_BOTH_DIRECTORY_INFO * pFindData = 
                        (FILE_BOTH_DIRECTORY_INFO *)current_entry;
                filename = &pFindData->FileName[0];
                len = le32_to_cpu(pFindData->FileNameLength);
+       } else if(cfile->srch_inf.info_level == SMB_FIND_FILE_INFO_STANDARD) {
+               FIND_FILE_STANDARD_INFO * pFindData =
+                       (FIND_FILE_STANDARD_INFO *)current_entry;
+               filename = &pFindData->FileName[0];
+               len = le32_to_cpu(pFindData->FileNameLength);
        } else {
                cFYI(1,("Unknown findfirst level %d",cfile->srch_inf.info_level));
        }
@@ -651,10 +692,12 @@ static int find_cifs_entry(const int xid, struct cifsTconInfo *pTcon,
                first_entry_in_buffer = cifsFile->srch_inf.index_of_last_entry
                                        - cifsFile->srch_inf.entries_in_buffer;
                pos_in_buf = index_to_find - first_entry_in_buffer;
-               cFYI(1,("found entry - pos_in_buf %d",pos_in_buf)); 
+               cFYI(1,("found entry - pos_in_buf %d",pos_in_buf));
+
                for(i=0;(i<(pos_in_buf)) && (current_entry != NULL);i++) {
                        /* go entry by entry figuring out which is first */
-                       current_entry = nxt_dir_entry(current_entry,end_of_smb);
+                       current_entry = nxt_dir_entry(current_entry,end_of_smb,
+                                               cifsFile->srch_inf.info_level);
                }
                if((current_entry == NULL) && (i < pos_in_buf)) {
                        /* BB fixme - check if we should flag this error */
@@ -681,7 +724,7 @@ static int find_cifs_entry(const int xid, struct cifsTconInfo *pTcon,
 /* inode num, inode type and filename returned */
 static int cifs_get_name_from_search_buf(struct qstr *pqst,
        char *current_entry, __u16 level, unsigned int unicode,
-       struct cifs_sb_info * cifs_sb, ino_t *pinum)
+       struct cifs_sb_info * cifs_sb, int max_len, ino_t *pinum)
 {
        int rc = 0;
        unsigned int len = 0;
@@ -725,10 +768,22 @@ static int cifs_get_name_from_search_buf(struct qstr *pqst,
                        (FILE_BOTH_DIRECTORY_INFO *)current_entry;
                filename = &pFindData->FileName[0];
                len = le32_to_cpu(pFindData->FileNameLength);
+       } else if(level == SMB_FIND_FILE_INFO_STANDARD) {
+               FIND_FILE_STANDARD_INFO * pFindData =
+                       (FIND_FILE_STANDARD_INFO *)current_entry;
+               filename = &pFindData->FileName[0];
+               /* one byte length, no name conversion */
+               len = (unsigned int)pFindData->FileNameLength;
        } else {
                cFYI(1,("Unknown findfirst level %d",level));
                return -EINVAL;
        }
+
+       if(len > max_len) {
+               cERROR(1,("bad search response length %d past smb end", len));
+               return -EINVAL;
+       }
+
        if(unicode) {
                /* BB fixme - test with long names */
                /* Note converted filename can be longer than in unicode */
@@ -748,7 +803,7 @@ static int cifs_get_name_from_search_buf(struct qstr *pqst,
 }
 
 static int cifs_filldir(char *pfindEntry, struct file *file,
-       filldir_t filldir, void *direntry, char *scratch_buf)
+       filldir_t filldir, void *direntry, char *scratch_buf, int max_len)
 {
        int rc = 0;
        struct qstr qstring;
@@ -784,6 +839,7 @@ static int cifs_filldir(char *pfindEntry, struct file *file,
        rc = cifs_get_name_from_search_buf(&qstring,pfindEntry,
                        pCifsF->srch_inf.info_level,
                        pCifsF->srch_inf.unicode,cifs_sb,
+                       max_len,
                        &inum /* returned */);
 
        if(rc)
@@ -805,13 +861,16 @@ static int cifs_filldir(char *pfindEntry, struct file *file,
        /* we pass in rc below, indicating whether it is a new inode,
           so we can figure out whether to invalidate the inode cached
           data if the file has changed */
-       if(pCifsF->srch_inf.info_level == SMB_FIND_FILE_UNIX) {
+       if(pCifsF->srch_inf.info_level == SMB_FIND_FILE_UNIX)
                unix_fill_in_inode(tmp_inode,
-                                  (FILE_UNIX_INFO *)pfindEntry,&obj_type, rc);
-       } else {
-               fill_in_inode(tmp_inode,
-                             (FILE_DIRECTORY_INFO *)pfindEntry,&obj_type, rc);
-       }
+                                  (FILE_UNIX_INFO *)pfindEntry,
+                                  &obj_type, rc);
+       else if(pCifsF->srch_inf.info_level == SMB_FIND_FILE_INFO_STANDARD)
+               fill_in_inode(tmp_inode, 0 /* old level 1 buffer type */,
+                               pfindEntry, &obj_type, rc);
+       else
+               fill_in_inode(tmp_inode, 1 /* NT */, pfindEntry, &obj_type, rc);
+       
        
        rc = filldir(direntry,qstring.name,qstring.len,file->f_pos,
                     tmp_inode->i_ino,obj_type);
@@ -871,6 +930,12 @@ static int cifs_save_resume_key(const char *current_entry,
                filename = &pFindData->FileName[0];
                len = le32_to_cpu(pFindData->FileNameLength);
                cifsFile->srch_inf.resume_key = pFindData->FileIndex;
+       } else if(level == SMB_FIND_FILE_INFO_STANDARD) {
+               FIND_FILE_STANDARD_INFO * pFindData =
+                       (FIND_FILE_STANDARD_INFO *)current_entry;
+               filename = &pFindData->FileName[0];
+               /* one byte length, no name conversion */
+               len = (unsigned int)pFindData->FileNameLength;
        } else {
                cFYI(1,("Unknown findfirst level %d",level));
                return -EINVAL;
@@ -891,6 +956,7 @@ int cifs_readdir(struct file *file, void *direntry, filldir_t filldir)
        int num_to_fill = 0;
        char * tmp_buf = NULL;
        char * end_of_smb;
+       int max_len;
 
        xid = GetXid();
 
@@ -966,10 +1032,11 @@ int cifs_readdir(struct file *file, void *direntry, filldir_t filldir)
                        goto rddir2_exit;
                }
                cFYI(1,("loop through %d times filling dir for net buf %p",
-                       num_to_fill,cifsFile->srch_inf.ntwrk_buf_start)); 
-               end_of_smb = cifsFile->srch_inf.ntwrk_buf_start +
-                       smbCalcSize((struct smb_hdr *)
-                                   cifsFile->srch_inf.ntwrk_buf_start);
+                       num_to_fill,cifsFile->srch_inf.ntwrk_buf_start));
+               max_len = smbCalcSize((struct smb_hdr *)
+                               cifsFile->srch_inf.ntwrk_buf_start);
+               end_of_smb = cifsFile->srch_inf.ntwrk_buf_start + max_len;
+
                /* To be safe - for UCS to UTF-8 with strings loaded
                with the rare long characters alloc more to account for
                such multibyte target UTF-8 characters. cifs_unicode.c,
@@ -984,8 +1051,8 @@ int cifs_readdir(struct file *file, void *direntry, filldir_t filldir)
                        }
                        /* if buggy server returns . and .. late do
                        we want to check for that here? */
-                       rc = cifs_filldir(current_entry, file, 
-                                       filldir, direntry,tmp_buf);
+                       rc = cifs_filldir(current_entry, file,
+                                       filldir, direntry, tmp_buf, max_len);
                        file->f_pos++;
                        if(file->f_pos == 
                                cifsFile->srch_inf.index_of_last_entry) {
@@ -994,8 +1061,9 @@ int cifs_readdir(struct file *file, void *direntry, filldir_t filldir)
                                cifs_save_resume_key(current_entry,cifsFile);
                                break;
                        } else 
-                               current_entry = nxt_dir_entry(current_entry,
-                                                             end_of_smb);
+                               current_entry = 
+                                       nxt_dir_entry(current_entry, end_of_smb,
+                                               cifsFile->srch_inf.info_level);
                }
                kfree(tmp_buf);
                break;