Merge git://git.kernel.org/pub/scm/linux/kernel/git/sfrench/cifs-2.6
[sfrench/cifs-2.6.git] / fs / cifs / readdir.c
index b689c5035124d137d1b019393cbad2d0b1123bf9..b5b0a2a41befe85734ffc30dc47f68f76bf05790 100644 (file)
@@ -21,6 +21,7 @@
  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  */
 #include <linux/fs.h>
+#include <linux/pagemap.h>
 #include <linux/stat.h>
 #include <linux/smp_lock.h>
 #include "cifspdu.h"
@@ -31,8 +32,8 @@
 #include "cifs_fs_sb.h"
 #include "cifsfs.h"
 
-/* BB fixme - add debug wrappers around this function to disable it fixme BB */
-/* static void dump_cifs_file_struct(struct file *file, char *label)
+#ifdef CONFIG_CIFS_DEBUG2
+static void dump_cifs_file_struct(struct file *file, char *label)
 {
        struct cifsFileInfo * cf;
 
@@ -53,7 +54,8 @@
                }
                
        }
-} */
+}
+#endif /* DEBUG2 */
 
 /* Returns one if new inode created (which therefore needs to be hashed) */
 /* Might check in the future if inode number changed so we can rehash inode */
@@ -80,7 +82,6 @@ static int construct_dentry(struct qstr *qstring, struct file *file,
                        if(*ptmp_inode == NULL)
                                return rc;
                        rc = 1;
-                       d_instantiate(tmp_dentry, *ptmp_inode);
                }
        } else {
                tmp_dentry = d_alloc(file->f_dentry, qstring);
@@ -97,9 +98,7 @@ static int construct_dentry(struct qstr *qstring, struct file *file,
                        tmp_dentry->d_op = &cifs_dentry_ops;
                if(*ptmp_inode == NULL)
                        return rc;
-               rc = 1;
-               d_instantiate(tmp_dentry, *ptmp_inode);
-               d_rehash(tmp_dentry);
+               rc = 2;
        }
 
        tmp_dentry->d_time = jiffies;
@@ -107,32 +106,70 @@ 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 AdjustForTZ(struct cifsTconInfo * tcon, struct inode * inode)
+{
+       if((tcon) && (tcon->ses) && (tcon->ses->server)) {
+               inode->i_ctime.tv_sec += tcon->ses->server->timeAdj;
+               inode->i_mtime.tv_sec += tcon->ses->server->timeAdj;
+               inode->i_atime.tv_sec += tcon->ses->server->timeAdj;
+       }
+       return;
+}
+
+
+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 */
+/*             struct timespec ts;*/
+               FIND_FILE_STANDARD_INFO * pfindData = 
+                       (FIND_FILE_STANDARD_INFO *)buf;
+
+               tmp_inode->i_mtime = cnvrtDosUnixTm(
+                               le16_to_cpu(pfindData->LastWriteDate),
+                               le16_to_cpu(pfindData->LastWriteTime));
+               tmp_inode->i_atime = cnvrtDosUnixTm(
+                               le16_to_cpu(pfindData->LastAccessDate),
+                               le16_to_cpu(pfindData->LastAccessTime));
+                tmp_inode->i_ctime = cnvrtDosUnixTm(
+                                le16_to_cpu(pfindData->LastWriteDate),
+                                le16_to_cpu(pfindData->LastWriteTime));
+               AdjustForTZ(cifs_sb->tcon, tmp_inode);
+               attr = le16_to_cpu(pfindData->Attributes);
+               allocation_size = le32_to_cpu(pfindData->AllocationSize);
+               end_of_file = le32_to_cpu(pfindData->DataSize);
+       }
+
        /* 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? 
@@ -197,10 +234,9 @@ static void fill_in_inode(struct inode *tmp_inode,
 
        if (allocation_size < end_of_file)
                cFYI(1, ("May be sparse file, allocation less than file size"));
-       cFYI(1, ("File Size %ld and blocks %llu and blocksize %ld",
+       cFYI(1, ("File Size %ld and blocks %llu",
                (unsigned long)tmp_inode->i_size,
-               (unsigned long long)tmp_inode->i_blocks,
-               tmp_inode->i_blksize));
+               (unsigned long long)tmp_inode->i_blocks));
        if (S_ISREG(tmp_inode->i_mode)) {
                cFYI(1, ("File inode"));
                tmp_inode->i_op = &cifs_file_inode_ops;
@@ -215,11 +251,13 @@ static void fill_in_inode(struct inode *tmp_inode,
                else
                        tmp_inode->i_fop = &cifs_file_ops;
 
-               tmp_inode->i_data.a_ops = &cifs_addr_ops;
                if((cifs_sb->tcon) && (cifs_sb->tcon->ses) &&
                   (cifs_sb->tcon->ses->server->maxBuf <
-                       4096 + MAX_CIFS_HDR_SIZE))
-                       tmp_inode->i_data.a_ops->readpages = NULL;
+                       PAGE_CACHE_SIZE + MAX_CIFS_HDR_SIZE))
+                       tmp_inode->i_data.a_ops = &cifs_addr_ops_smallbuf;
+               else
+                       tmp_inode->i_data.a_ops = &cifs_addr_ops;
+
                if(isNewInode)
                        return; /* No sense invalidating pages for new inode
                                   since have not started caching readahead file
@@ -338,11 +376,12 @@ static void unix_fill_in_inode(struct inode *tmp_inode,
                else
                        tmp_inode->i_fop = &cifs_file_ops;
 
-               tmp_inode->i_data.a_ops = &cifs_addr_ops;
                if((cifs_sb->tcon) && (cifs_sb->tcon->ses) &&
                   (cifs_sb->tcon->ses->server->maxBuf < 
-                       4096 + MAX_CIFS_HDR_SIZE))
-                       tmp_inode->i_data.a_ops->readpages = NULL;
+                       PAGE_CACHE_SIZE + MAX_CIFS_HDR_SIZE))
+                       tmp_inode->i_data.a_ops = &cifs_addr_ops_smallbuf;
+               else
+                       tmp_inode->i_data.a_ops = &cifs_addr_ops;
 
                if(isNewInode)
                        return; /* No sense invalidating pages for new inode since we
@@ -415,7 +454,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? */ {
@@ -451,12 +493,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) {
@@ -464,7 +513,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;
@@ -482,7 +534,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) {
@@ -491,26 +543,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 = pFindData->FileNameLength;
        } else {
                cFYI(1,("Unknown findfirst level %d",cfile->srch_inf.info_level));
        }
@@ -597,7 +657,9 @@ static int find_cifs_entry(const int xid, struct cifsTconInfo *pTcon,
        . and .. for the root of a drive and for those we need
        to start two entries earlier */
 
-/*     dump_cifs_file_struct(file, "In fce ");*/
+#ifdef CONFIG_CIFS_DEBUG2
+       dump_cifs_file_struct(file, "In fce ");
+#endif
        if(((index_to_find < cifsFile->srch_inf.index_of_last_entry) && 
             is_dir_changed(file)) || 
           (index_to_find < first_entry_in_buffer)) {
@@ -644,10 +706,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 */
@@ -674,7 +738,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;
@@ -718,10 +782,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 */
@@ -741,7 +817,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;
@@ -777,6 +853,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)
@@ -798,14 +875,23 @@ 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);
+
+       if(rc) /* new inode - needs to be tied to dentry */ {
+               d_instantiate(tmp_dentry, tmp_inode);
+               if(rc == 2)
+                       d_rehash(tmp_dentry);
        }
        
+       
        rc = filldir(direntry,qstring.name,qstring.len,file->f_pos,
                     tmp_inode->i_ino,obj_type);
        if(rc) {
@@ -864,6 +950,13 @@ 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;
+               cifsFile->srch_inf.resume_key = pFindData->ResumeKey;
        } else {
                cFYI(1,("Unknown findfirst level %d",level));
                return -EINVAL;
@@ -884,6 +977,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();
 
@@ -909,7 +1003,7 @@ int cifs_readdir(struct file *file, void *direntry, filldir_t filldir)
        case 1:
                if (filldir(direntry, "..", 2, file->f_pos,
                     file->f_dentry->d_parent->d_inode->i_ino, DT_DIR) < 0) {
-                       cERROR(1, ("Filldir for parent dir failed "));
+                       cERROR(1, ("Filldir for parent dir failed"));
                        rc = -ENOMEM;
                        break;
                }
@@ -959,10 +1053,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,
@@ -977,17 +1072,19 @@ 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) {
+                       if(file->f_pos == 
+                               cifsFile->srch_inf.index_of_last_entry) {
                                cFYI(1,("last entry in buf at pos %lld %s",
-                                       file->f_pos,tmp_buf)); /* BB removeme BB */
+                                       file->f_pos,tmp_buf));
                                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;