fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / fs / fat / dir.c
index e5ae1b7..c16af24 100644 (file)
@@ -20,6 +20,7 @@
 #include <linux/dirent.h>
 #include <linux/smp_lock.h>
 #include <linux/buffer_head.h>
+#include <linux/compat.h>
 #include <asm/uaccess.h>
 
 static inline loff_t fat_make_i_pos(struct super_block *sb,
@@ -30,6 +31,29 @@ static inline loff_t fat_make_i_pos(struct super_block *sb,
                | (de - (struct msdos_dir_entry *)bh->b_data);
 }
 
+static inline void fat_dir_readahead(struct inode *dir, sector_t iblock,
+                                    sector_t phys)
+{
+       struct super_block *sb = dir->i_sb;
+       struct msdos_sb_info *sbi = MSDOS_SB(sb);
+       struct buffer_head *bh;
+       int sec;
+
+       /* This is not a first sector of cluster, or sec_per_clus == 1 */
+       if ((iblock & (sbi->sec_per_clus - 1)) || sbi->sec_per_clus == 1)
+               return;
+       /* root dir of FAT12/FAT16 */
+       if ((sbi->fat_bits != 32) && (dir->i_ino == MSDOS_ROOT_INO))
+               return;
+
+       bh = sb_find_get_block(sb, phys);
+       if (bh == NULL || !buffer_uptodate(bh)) {
+               for (sec = 0; sec < sbi->sec_per_clus; sec++)
+                       sb_breadahead(sb, phys + sec);
+       }
+       brelse(bh);
+}
+
 /* Returns the inode number of the directory entry at offset pos. If bh is
    non-NULL, it is brelse'd before. Pos is incremented. The buffer header is
    returned in bh.
@@ -45,8 +69,8 @@ static int fat__get_entry(struct inode *dir, loff_t *pos,
 {
        struct super_block *sb = dir->i_sb;
        sector_t phys, iblock;
-       int offset;
-       int err;
+       unsigned long mapped_blocks;
+       int err, offset;
 
 next:
        if (*bh)
@@ -54,10 +78,12 @@ next:
 
        *bh = NULL;
        iblock = *pos >> sb->s_blocksize_bits;
-       err = fat_bmap(dir, iblock, &phys);
+       err = fat_bmap(dir, iblock, &phys, &mapped_blocks);
        if (err || !phys)
                return -1;      /* beyond EOF or error */
 
+       fat_dir_readahead(dir, iblock, phys);
+
        *bh = sb_bread(sb, phys);
        if (*bh == NULL) {
                printk(KERN_ERR "FAT: Directory bread(block %llu) failed\n",
@@ -89,7 +115,7 @@ static inline int fat_get_entry(struct inode *dir, loff_t *pos,
 }
 
 /*
- * Convert Unicode 16 to UTF8, translated Unicode, or ASCII.
+ * Convert Unicode 16 to UTF-8, translated Unicode, or ASCII.
  * If uni_xlate is enabled and we can't get a 1:1 conversion, use a
  * colon as an escape character since it is normally invalid on the vfat
  * filesystem. The following four characters are the hexadecimal digits
@@ -197,6 +223,80 @@ fat_shortname2uni(struct nls_table *nls, unsigned char *buf, int buf_size,
        return len;
 }
 
+enum { PARSE_INVALID = 1, PARSE_NOT_LONGNAME, PARSE_EOF, };
+
+/**
+ * fat_parse_long - Parse extended directory entry.
+ *
+ * This function returns zero on success, negative value on error, or one of
+ * the following:
+ *
+ * %PARSE_INVALID - Directory entry is invalid.
+ * %PARSE_NOT_LONGNAME - Directory entry does not contain longname.
+ * %PARSE_EOF - Directory has no more entries.
+ */
+static int fat_parse_long(struct inode *dir, loff_t *pos,
+                         struct buffer_head **bh, struct msdos_dir_entry **de,
+                         wchar_t **unicode, unsigned char *nr_slots)
+{
+       struct msdos_dir_slot *ds;
+       unsigned char id, slot, slots, alias_checksum;
+
+       if (!*unicode) {
+               *unicode = (wchar_t *)__get_free_page(GFP_KERNEL);
+               if (!*unicode) {
+                       brelse(*bh);
+                       return -ENOMEM;
+               }
+       }
+parse_long:
+       slots = 0;
+       ds = (struct msdos_dir_slot *)*de;
+       id = ds->id;
+       if (!(id & 0x40))
+               return PARSE_INVALID;
+       slots = id & ~0x40;
+       if (slots > 20 || !slots)       /* ceil(256 * 2 / 26) */
+               return PARSE_INVALID;
+       *nr_slots = slots;
+       alias_checksum = ds->alias_checksum;
+
+       slot = slots;
+       while (1) {
+               int offset;
+
+               slot--;
+               offset = slot * 13;
+               fat16_towchar(*unicode + offset, ds->name0_4, 5);
+               fat16_towchar(*unicode + offset + 5, ds->name5_10, 6);
+               fat16_towchar(*unicode + offset + 11, ds->name11_12, 2);
+
+               if (ds->id & 0x40)
+                       (*unicode)[offset + 13] = 0;
+               if (fat_get_entry(dir, pos, bh, de) < 0)
+                       return PARSE_EOF;
+               if (slot == 0)
+                       break;
+               ds = (struct msdos_dir_slot *)*de;
+               if (ds->attr != ATTR_EXT)
+                       return PARSE_NOT_LONGNAME;
+               if ((ds->id & ~0x40) != slot)
+                       goto parse_long;
+               if (ds->alias_checksum != alias_checksum)
+                       goto parse_long;
+       }
+       if ((*de)->name[0] == DELETED_FLAG)
+               return PARSE_INVALID;
+       if ((*de)->attr == ATTR_EXT)
+               goto parse_long;
+       if (IS_FREE((*de)->name) || ((*de)->attr & ATTR_VOLUME))
+               return PARSE_INVALID;
+       if (fat_checksum((*de)->name) != alias_checksum)
+               *nr_slots = 0;
+
+       return 0;
+}
+
 /*
  * Return values: negative -> error, 0 -> not found, positive -> found,
  * value is the total amount of slots, including the shortname entry.
@@ -234,68 +334,16 @@ parse_record:
                if (de->attr != ATTR_EXT && IS_FREE(de->name))
                        continue;
                if (de->attr == ATTR_EXT) {
-                       struct msdos_dir_slot *ds;
-                       unsigned char id;
-                       unsigned char slot;
-                       unsigned char slots;
-                       unsigned char sum;
-                       unsigned char alias_checksum;
-
-                       if (!unicode) {
-                               unicode = (wchar_t *)
-                                       __get_free_page(GFP_KERNEL);
-                               if (!unicode) {
-                                       brelse(bh);
-                                       return -ENOMEM;
-                               }
-                       }
-parse_long:
-                       slots = 0;
-                       ds = (struct msdos_dir_slot *) de;
-                       id = ds->id;
-                       if (!(id & 0x40))
-                               continue;
-                       slots = id & ~0x40;
-                       if (slots > 20 || !slots)       /* ceil(256 * 2 / 26) */
+                       int status = fat_parse_long(inode, &cpos, &bh, &de,
+                                                   &unicode, &nr_slots);
+                       if (status < 0)
+                               return status;
+                       else if (status == PARSE_INVALID)
                                continue;
-                       nr_slots = slots;
-                       alias_checksum = ds->alias_checksum;
-
-                       slot = slots;
-                       while (1) {
-                               int offset;
-
-                               slot--;
-                               offset = slot * 13;
-                               fat16_towchar(unicode + offset, ds->name0_4, 5);
-                               fat16_towchar(unicode + offset + 5, ds->name5_10, 6);
-                               fat16_towchar(unicode + offset + 11, ds->name11_12, 2);
-
-                               if (ds->id & 0x40) {
-                                       unicode[offset + 13] = 0;
-                               }
-                               if (fat_get_entry(inode, &cpos, &bh, &de) < 0)
-                                       goto EODir;
-                               if (slot == 0)
-                                       break;
-                               ds = (struct msdos_dir_slot *) de;
-                               if (ds->attr !=  ATTR_EXT)
-                                       goto parse_record;
-                               if ((ds->id & ~0x40) != slot)
-                                       goto parse_long;
-                               if (ds->alias_checksum != alias_checksum)
-                                       goto parse_long;
-                       }
-                       if (de->name[0] == DELETED_FLAG)
-                               continue;
-                       if (de->attr ==  ATTR_EXT)
-                               goto parse_long;
-                       if (IS_FREE(de->name) || (de->attr & ATTR_VOLUME))
-                               continue;
-                       for (sum = 0, i = 0; i < 11; i++)
-                               sum = (((sum&1)<<7)|((sum&0xfe)>>1)) + de->name[i];
-                       if (sum != alias_checksum)
-                               nr_slots = 0;
+                       else if (status == PARSE_NOT_LONGNAME)
+                               goto parse_record;
+                       else if (status == PARSE_EOF)
+                               goto EODir;
                }
 
                memcpy(work, de->name, sizeof(de->name));
@@ -371,7 +419,7 @@ EODir:
        return err;
 }
 
-EXPORT_SYMBOL(fat_search_long);
+EXPORT_SYMBOL_GPL(fat_search_long);
 
 struct fat_ioctl_filldir_callback {
        struct dirent __user *dirent;
@@ -383,8 +431,8 @@ struct fat_ioctl_filldir_callback {
        int short_len;
 };
 
-static int fat_readdirx(struct inode *inode, struct file *filp, void *dirent,
-                       filldir_t filldir, int short_only, int both)
+static int __fat_readdir(struct inode *inode, struct file *filp, void *dirent,
+                        filldir_t filldir, int short_only, int both)
 {
        struct super_block *sb = inode->i_sb;
        struct msdos_sb_info *sbi = MSDOS_SB(sb);
@@ -433,9 +481,10 @@ static int fat_readdirx(struct inode *inode, struct file *filp, void *dirent,
 
        bh = NULL;
 GetNew:
-       long_slots = 0;
        if (fat_get_entry(inode, &cpos, &bh, &de) == -1)
                goto EODir;
+parse_record:
+       long_slots = 0;
        /* Check for long filename entry */
        if (isvfat) {
                if (de->name[0] == DELETED_FLAG)
@@ -450,69 +499,18 @@ GetNew:
        }
 
        if (isvfat && de->attr == ATTR_EXT) {
-               struct msdos_dir_slot *ds;
-               unsigned char id;
-               unsigned char slot;
-               unsigned char slots;
-               unsigned char sum;
-               unsigned char alias_checksum;
-
-               if (!unicode) {
-                       unicode = (wchar_t *)__get_free_page(GFP_KERNEL);
-                       if (!unicode) {
-                               filp->f_pos = cpos;
-                               brelse(bh);
-                               ret = -ENOMEM;
-                               goto out;
-                       }
-               }
-ParseLong:
-               slots = 0;
-               ds = (struct msdos_dir_slot *) de;
-               id = ds->id;
-               if (!(id & 0x40))
+               int status = fat_parse_long(inode, &cpos, &bh, &de,
+                                           &unicode, &long_slots);
+               if (status < 0) {
+                       filp->f_pos = cpos;
+                       ret = status;
+                       goto out;
+               } else if (status == PARSE_INVALID)
                        goto RecEnd;
-               slots = id & ~0x40;
-               if (slots > 20 || !slots)       /* ceil(256 * 2 / 26) */
-                       goto RecEnd;
-               long_slots = slots;
-               alias_checksum = ds->alias_checksum;
-
-               slot = slots;
-               while (1) {
-                       int offset;
-
-                       slot--;
-                       offset = slot * 13;
-                       fat16_towchar(unicode + offset, ds->name0_4, 5);
-                       fat16_towchar(unicode + offset + 5, ds->name5_10, 6);
-                       fat16_towchar(unicode + offset + 11, ds->name11_12, 2);
-
-                       if (ds->id & 0x40) {
-                               unicode[offset + 13] = 0;
-                       }
-                       if (fat_get_entry(inode, &cpos, &bh, &de) == -1)
-                               goto EODir;
-                       if (slot == 0)
-                               break;
-                       ds = (struct msdos_dir_slot *) de;
-                       if (ds->attr !=  ATTR_EXT)
-                               goto RecEnd;    /* XXX */
-                       if ((ds->id & ~0x40) != slot)
-                               goto ParseLong;
-                       if (ds->alias_checksum != alias_checksum)
-                               goto ParseLong;
-               }
-               if (de->name[0] == DELETED_FLAG)
-                       goto RecEnd;
-               if (de->attr ==  ATTR_EXT)
-                       goto ParseLong;
-               if (IS_FREE(de->name) || (de->attr & ATTR_VOLUME))
-                       goto RecEnd;
-               for (sum = 0, i = 0; i < 11; i++)
-                       sum = (((sum&1)<<7)|((sum&0xfe)>>1)) + de->name[i];
-               if (sum != alias_checksum)
-                       long_slots = 0;
+               else if (status == PARSE_NOT_LONGNAME)
+                       goto parse_record;
+               else if (status == PARSE_EOF)
+                       goto EODir;
        }
 
        if (sbi->options.dotsOK) {
@@ -581,7 +579,7 @@ ParseLong:
        if (!memcmp(de->name, MSDOS_DOT, MSDOS_NAME))
                inum = inode->i_ino;
        else if (!memcmp(de->name, MSDOS_DOTDOT, MSDOS_NAME)) {
-               inum = parent_ino(filp->f_dentry);
+               inum = parent_ino(filp->f_path.dentry);
        } else {
                loff_t i_pos = fat_make_i_pos(sb, bh, de);
                struct inode *tmp = fat_iget(sb, i_pos);
@@ -635,8 +633,7 @@ RecEnd:
 EODir:
        filp->f_pos = cpos;
 FillFailed:
-       if (bh)
-               brelse(bh);
+       brelse(bh);
        if (unicode)
                free_page((unsigned long)unicode);
 out:
@@ -646,12 +643,12 @@ out:
 
 static int fat_readdir(struct file *filp, void *dirent, filldir_t filldir)
 {
-       struct inode *inode = filp->f_dentry->d_inode;
-       return fat_readdirx(inode, filp, dirent, filldir, 0, 0);
+       struct inode *inode = filp->f_path.dentry->d_inode;
+       return __fat_readdir(inode, filp, dirent, filldir, 0, 0);
 }
 
 static int fat_ioctl_filldir(void *__buf, const char *name, int name_len,
-                            loff_t offset, ino_t ino, unsigned int d_type)
+                            loff_t offset, u64 ino, unsigned int d_type)
 {
        struct fat_ioctl_filldir_callback *buf = __buf;
        struct dirent __user *d1 = buf->dirent;
@@ -733,22 +730,77 @@ static int fat_dir_ioctl(struct inode * inode, struct file * filp,
 
        buf.dirent = d1;
        buf.result = 0;
-       down(&inode->i_sem);
+       mutex_lock(&inode->i_mutex);
        ret = -ENOENT;
        if (!IS_DEADDIR(inode)) {
-               ret = fat_readdirx(inode, filp, &buf, fat_ioctl_filldir,
-                                  short_only, both);
+               ret = __fat_readdir(inode, filp, &buf, fat_ioctl_filldir,
+                                   short_only, both);
        }
-       up(&inode->i_sem);
+       mutex_unlock(&inode->i_mutex);
        if (ret >= 0)
                ret = buf.result;
        return ret;
 }
 
-struct file_operations fat_dir_operations = {
+#ifdef CONFIG_COMPAT
+#define        VFAT_IOCTL_READDIR_BOTH32       _IOR('r', 1, struct compat_dirent[2])
+#define        VFAT_IOCTL_READDIR_SHORT32      _IOR('r', 2, struct compat_dirent[2])
+
+static long fat_compat_put_dirent32(struct dirent *d,
+                                   struct compat_dirent __user *d32)
+{
+        if (!access_ok(VERIFY_WRITE, d32, sizeof(struct compat_dirent)))
+                return -EFAULT;
+
+        __put_user(d->d_ino, &d32->d_ino);
+        __put_user(d->d_off, &d32->d_off);
+        __put_user(d->d_reclen, &d32->d_reclen);
+        if (__copy_to_user(d32->d_name, d->d_name, d->d_reclen))
+               return -EFAULT;
+
+        return 0;
+}
+
+static long fat_compat_dir_ioctl(struct file *file, unsigned cmd,
+                                unsigned long arg)
+{
+       struct compat_dirent __user *p = compat_ptr(arg);
+       int ret;
+       mm_segment_t oldfs = get_fs();
+       struct dirent d[2];
+
+       switch (cmd) {
+       case VFAT_IOCTL_READDIR_BOTH32:
+               cmd = VFAT_IOCTL_READDIR_BOTH;
+               break;
+       case VFAT_IOCTL_READDIR_SHORT32:
+               cmd = VFAT_IOCTL_READDIR_SHORT;
+               break;
+       default:
+               return -ENOIOCTLCMD;
+       }
+
+       set_fs(KERNEL_DS);
+       lock_kernel();
+       ret = fat_dir_ioctl(file->f_path.dentry->d_inode, file,
+                           cmd, (unsigned long) &d);
+       unlock_kernel();
+       set_fs(oldfs);
+       if (ret >= 0) {
+               ret |= fat_compat_put_dirent32(&d[0], p);
+               ret |= fat_compat_put_dirent32(&d[1], p + 1);
+       }
+       return ret;
+}
+#endif /* CONFIG_COMPAT */
+
+const struct file_operations fat_dir_operations = {
        .read           = generic_read_dir,
        .readdir        = fat_readdir,
        .ioctl          = fat_dir_ioctl,
+#ifdef CONFIG_COMPAT
+       .compat_ioctl   = fat_compat_dir_ioctl,
+#endif
        .fsync          = file_fsync,
 };
 
@@ -784,7 +836,7 @@ int fat_get_dotdot_entry(struct inode *dir, struct buffer_head **bh,
        return -ENOENT;
 }
 
-EXPORT_SYMBOL(fat_get_dotdot_entry);
+EXPORT_SYMBOL_GPL(fat_get_dotdot_entry);
 
 /* See if directory is empty */
 int fat_dir_empty(struct inode *dir)
@@ -807,7 +859,7 @@ int fat_dir_empty(struct inode *dir)
        return result;
 }
 
-EXPORT_SYMBOL(fat_dir_empty);
+EXPORT_SYMBOL_GPL(fat_dir_empty);
 
 /*
  * fat_subdirs counts the number of sub-directories of dir. It can be run
@@ -853,7 +905,7 @@ int fat_scan(struct inode *dir, const unsigned char *name,
        return -ENOENT;
 }
 
-EXPORT_SYMBOL(fat_scan);
+EXPORT_SYMBOL_GPL(fat_scan);
 
 static int __fat_remove_entries(struct inode *dir, loff_t pos, int nr_slots)
 {
@@ -940,7 +992,7 @@ int fat_remove_entries(struct inode *dir, struct fat_slot_info *sinfo)
        return 0;
 }
 
-EXPORT_SYMBOL(fat_remove_entries);
+EXPORT_SYMBOL_GPL(fat_remove_entries);
 
 static int fat_zeroed_cluster(struct inode *dir, sector_t blknr, int nr_used,
                              struct buffer_head **bhs, int nr_bhs)
@@ -1052,7 +1104,7 @@ error:
        return err;
 }
 
-EXPORT_SYMBOL(fat_alloc_new_dir);
+EXPORT_SYMBOL_GPL(fat_alloc_new_dir);
 
 static int fat_add_new_entries(struct inode *dir, void *slots, int nr_slots,
                               int *nr_cluster, struct msdos_dir_entry **de,
@@ -1268,4 +1320,4 @@ error_remove:
        return err;
 }
 
-EXPORT_SYMBOL(fat_add_entries);
+EXPORT_SYMBOL_GPL(fat_add_entries);