summaryrefslogtreecommitdiffstats
path: root/fs/fat
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /fs/fat
Linux-2.6.12-rc2
Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
Diffstat (limited to 'fs/fat')
-rw-r--r--fs/fat/Makefile7
-rw-r--r--fs/fat/cache.c324
-rw-r--r--fs/fat/dir.c1271
-rw-r--r--fs/fat/fatent.c612
-rw-r--r--fs/fat/file.c308
-rw-r--r--fs/fat/inode.c1351
-rw-r--r--fs/fat/misc.c225
7 files changed, 4098 insertions, 0 deletions
diff --git a/fs/fat/Makefile b/fs/fat/Makefile
new file mode 100644
index 000000000000..bfb5f06cf2c8
--- /dev/null
+++ b/fs/fat/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for the Linux fat filesystem support.
+#
+
+obj-$(CONFIG_FAT_FS) += fat.o
+
+fat-objs := cache.o dir.o fatent.o file.o inode.o misc.o
diff --git a/fs/fat/cache.c b/fs/fat/cache.c
new file mode 100644
index 000000000000..7c52e465a619
--- /dev/null
+++ b/fs/fat/cache.c
@@ -0,0 +1,324 @@
+/*
+ * linux/fs/fat/cache.c
+ *
+ * Written 1992,1993 by Werner Almesberger
+ *
+ * Mar 1999. AV. Changed cache, so that it uses the starting cluster instead
+ * of inode number.
+ * May 1999. AV. Fixed the bogosity with FAT32 (read "FAT28"). Fscking lusers.
+ */
+
+#include <linux/fs.h>
+#include <linux/msdos_fs.h>
+#include <linux/buffer_head.h>
+
+/* this must be > 0. */
+#define FAT_MAX_CACHE 8
+
+struct fat_cache {
+ struct list_head cache_list;
+ int nr_contig; /* number of contiguous clusters */
+ int fcluster; /* cluster number in the file. */
+ int dcluster; /* cluster number on disk. */
+};
+
+struct fat_cache_id {
+ unsigned int id;
+ int nr_contig;
+ int fcluster;
+ int dcluster;
+};
+
+static inline int fat_max_cache(struct inode *inode)
+{
+ return FAT_MAX_CACHE;
+}
+
+static kmem_cache_t *fat_cache_cachep;
+
+static void init_once(void *foo, kmem_cache_t *cachep, unsigned long flags)
+{
+ struct fat_cache *cache = (struct fat_cache *)foo;
+
+ if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) ==
+ SLAB_CTOR_CONSTRUCTOR)
+ INIT_LIST_HEAD(&cache->cache_list);
+}
+
+int __init fat_cache_init(void)
+{
+ fat_cache_cachep = kmem_cache_create("fat_cache",
+ sizeof(struct fat_cache),
+ 0, SLAB_RECLAIM_ACCOUNT,
+ init_once, NULL);
+ if (fat_cache_cachep == NULL)
+ return -ENOMEM;
+ return 0;
+}
+
+void __exit fat_cache_destroy(void)
+{
+ if (kmem_cache_destroy(fat_cache_cachep))
+ printk(KERN_INFO "fat_cache: not all structures were freed\n");
+}
+
+static inline struct fat_cache *fat_cache_alloc(struct inode *inode)
+{
+ return kmem_cache_alloc(fat_cache_cachep, SLAB_KERNEL);
+}
+
+static inline void fat_cache_free(struct fat_cache *cache)
+{
+ BUG_ON(!list_empty(&cache->cache_list));
+ kmem_cache_free(fat_cache_cachep, cache);
+}
+
+static inline void fat_cache_update_lru(struct inode *inode,
+ struct fat_cache *cache)
+{
+ if (MSDOS_I(inode)->cache_lru.next != &cache->cache_list)
+ list_move(&cache->cache_list, &MSDOS_I(inode)->cache_lru);
+}
+
+static int fat_cache_lookup(struct inode *inode, int fclus,
+ struct fat_cache_id *cid,
+ int *cached_fclus, int *cached_dclus)
+{
+ static struct fat_cache nohit = { .fcluster = 0, };
+
+ struct fat_cache *hit = &nohit, *p;
+ int offset = -1;
+
+ spin_lock(&MSDOS_I(inode)->cache_lru_lock);
+ list_for_each_entry(p, &MSDOS_I(inode)->cache_lru, cache_list) {
+ /* Find the cache of "fclus" or nearest cache. */
+ if (p->fcluster <= fclus && hit->fcluster < p->fcluster) {
+ hit = p;
+ if ((hit->fcluster + hit->nr_contig) < fclus) {
+ offset = hit->nr_contig;
+ } else {
+ offset = fclus - hit->fcluster;
+ break;
+ }
+ }
+ }
+ if (hit != &nohit) {
+ fat_cache_update_lru(inode, hit);
+
+ cid->id = MSDOS_I(inode)->cache_valid_id;
+ cid->nr_contig = hit->nr_contig;
+ cid->fcluster = hit->fcluster;
+ cid->dcluster = hit->dcluster;
+ *cached_fclus = cid->fcluster + offset;
+ *cached_dclus = cid->dcluster + offset;
+ }
+ spin_unlock(&MSDOS_I(inode)->cache_lru_lock);
+
+ return offset;
+}
+
+static struct fat_cache *fat_cache_merge(struct inode *inode,
+ struct fat_cache_id *new)
+{
+ struct fat_cache *p;
+
+ list_for_each_entry(p, &MSDOS_I(inode)->cache_lru, cache_list) {
+ /* Find the same part as "new" in cluster-chain. */
+ if (p->fcluster == new->fcluster) {
+ BUG_ON(p->dcluster != new->dcluster);
+ if (new->nr_contig > p->nr_contig)
+ p->nr_contig = new->nr_contig;
+ return p;
+ }
+ }
+ return NULL;
+}
+
+static void fat_cache_add(struct inode *inode, struct fat_cache_id *new)
+{
+ struct fat_cache *cache, *tmp;
+
+ if (new->fcluster == -1) /* dummy cache */
+ return;
+
+ spin_lock(&MSDOS_I(inode)->cache_lru_lock);
+ if (new->id != FAT_CACHE_VALID &&
+ new->id != MSDOS_I(inode)->cache_valid_id)
+ goto out; /* this cache was invalidated */
+
+ cache = fat_cache_merge(inode, new);
+ if (cache == NULL) {
+ if (MSDOS_I(inode)->nr_caches < fat_max_cache(inode)) {
+ MSDOS_I(inode)->nr_caches++;
+ spin_unlock(&MSDOS_I(inode)->cache_lru_lock);
+
+ tmp = fat_cache_alloc(inode);
+ spin_lock(&MSDOS_I(inode)->cache_lru_lock);
+ cache = fat_cache_merge(inode, new);
+ if (cache != NULL) {
+ MSDOS_I(inode)->nr_caches--;
+ fat_cache_free(tmp);
+ goto out_update_lru;
+ }
+ cache = tmp;
+ } else {
+ struct list_head *p = MSDOS_I(inode)->cache_lru.prev;
+ cache = list_entry(p, struct fat_cache, cache_list);
+ }
+ cache->fcluster = new->fcluster;
+ cache->dcluster = new->dcluster;
+ cache->nr_contig = new->nr_contig;
+ }
+out_update_lru:
+ fat_cache_update_lru(inode, cache);
+out:
+ spin_unlock(&MSDOS_I(inode)->cache_lru_lock);
+}
+
+/*
+ * Cache invalidation occurs rarely, thus the LRU chain is not updated. It
+ * fixes itself after a while.
+ */
+static void __fat_cache_inval_inode(struct inode *inode)
+{
+ struct msdos_inode_info *i = MSDOS_I(inode);
+ struct fat_cache *cache;
+
+ while (!list_empty(&i->cache_lru)) {
+ cache = list_entry(i->cache_lru.next, struct fat_cache, cache_list);
+ list_del_init(&cache->cache_list);
+ i->nr_caches--;
+ fat_cache_free(cache);
+ }
+ /* Update. The copy of caches before this id is discarded. */
+ i->cache_valid_id++;
+ if (i->cache_valid_id == FAT_CACHE_VALID)
+ i->cache_valid_id++;
+}
+
+void fat_cache_inval_inode(struct inode *inode)
+{
+ spin_lock(&MSDOS_I(inode)->cache_lru_lock);
+ __fat_cache_inval_inode(inode);
+ spin_unlock(&MSDOS_I(inode)->cache_lru_lock);
+}
+
+static inline int cache_contiguous(struct fat_cache_id *cid, int dclus)
+{
+ cid->nr_contig++;
+ return ((cid->dcluster + cid->nr_contig) == dclus);
+}
+
+static inline void cache_init(struct fat_cache_id *cid, int fclus, int dclus)
+{
+ cid->id = FAT_CACHE_VALID;
+ cid->fcluster = fclus;
+ cid->dcluster = dclus;
+ cid->nr_contig = 0;
+}
+
+int fat_get_cluster(struct inode *inode, int cluster, int *fclus, int *dclus)
+{
+ struct super_block *sb = inode->i_sb;
+ const int limit = sb->s_maxbytes >> MSDOS_SB(sb)->cluster_bits;
+ struct fat_entry fatent;
+ struct fat_cache_id cid;
+ int nr;
+
+ BUG_ON(MSDOS_I(inode)->i_start == 0);
+
+ *fclus = 0;
+ *dclus = MSDOS_I(inode)->i_start;
+ if (cluster == 0)
+ return 0;
+
+ if (fat_cache_lookup(inode, cluster, &cid, fclus, dclus) < 0) {
+ /*
+ * dummy, always not contiguous
+ * This is reinitialized by cache_init(), later.
+ */
+ cache_init(&cid, -1, -1);
+ }
+
+ fatent_init(&fatent);
+ while (*fclus < cluster) {
+ /* prevent the infinite loop of cluster chain */
+ if (*fclus > limit) {
+ fat_fs_panic(sb, "%s: detected the cluster chain loop"
+ " (i_pos %lld)", __FUNCTION__,
+ MSDOS_I(inode)->i_pos);
+ nr = -EIO;
+ goto out;
+ }
+
+ nr = fat_ent_read(inode, &fatent, *dclus);
+ if (nr < 0)
+ goto out;
+ else if (nr == FAT_ENT_FREE) {
+ fat_fs_panic(sb, "%s: invalid cluster chain"
+ " (i_pos %lld)", __FUNCTION__,
+ MSDOS_I(inode)->i_pos);
+ nr = -EIO;
+ goto out;
+ } else if (nr == FAT_ENT_EOF) {
+ fat_cache_add(inode, &cid);
+ goto out;
+ }
+ (*fclus)++;
+ *dclus = nr;
+ if (!cache_contiguous(&cid, *dclus))
+ cache_init(&cid, *fclus, *dclus);
+ }
+ nr = 0;
+ fat_cache_add(inode, &cid);
+out:
+ fatent_brelse(&fatent);
+ return nr;
+}
+
+static int fat_bmap_cluster(struct inode *inode, int cluster)
+{
+ struct super_block *sb = inode->i_sb;
+ int ret, fclus, dclus;
+
+ if (MSDOS_I(inode)->i_start == 0)
+ return 0;
+
+ ret = fat_get_cluster(inode, cluster, &fclus, &dclus);
+ if (ret < 0)
+ return ret;
+ else if (ret == FAT_ENT_EOF) {
+ fat_fs_panic(sb, "%s: request beyond EOF (i_pos %lld)",
+ __FUNCTION__, MSDOS_I(inode)->i_pos);
+ return -EIO;
+ }
+ return dclus;
+}
+
+int fat_bmap(struct inode *inode, sector_t sector, sector_t *phys)
+{
+ struct super_block *sb = inode->i_sb;
+ struct msdos_sb_info *sbi = MSDOS_SB(sb);
+ sector_t last_block;
+ int cluster, offset;
+
+ *phys = 0;
+ if ((sbi->fat_bits != 32) && (inode->i_ino == MSDOS_ROOT_INO)) {
+ if (sector < (sbi->dir_entries >> sbi->dir_per_block_bits))
+ *phys = sector + sbi->dir_start;
+ return 0;
+ }
+ last_block = (MSDOS_I(inode)->mmu_private + (sb->s_blocksize - 1))
+ >> sb->s_blocksize_bits;
+ if (sector >= last_block)
+ return 0;
+
+ cluster = sector >> (sbi->cluster_bits - sb->s_blocksize_bits);
+ offset = sector & (sbi->sec_per_clus - 1);
+ cluster = fat_bmap_cluster(inode, cluster);
+ if (cluster < 0)
+ return cluster;
+ else if (cluster)
+ *phys = fat_clus_to_blknr(sbi, cluster) + offset;
+ return 0;
+}
diff --git a/fs/fat/dir.c b/fs/fat/dir.c
new file mode 100644
index 000000000000..e5ae1b720dde
--- /dev/null
+++ b/fs/fat/dir.c
@@ -0,0 +1,1271 @@
+/*
+ * linux/fs/fat/dir.c
+ *
+ * directory handling functions for fat-based filesystems
+ *
+ * Written 1992,1993 by Werner Almesberger
+ *
+ * Hidden files 1995 by Albert Cahalan <albert@ccs.neu.edu> <adc@coe.neu.edu>
+ *
+ * VFAT extensions by Gordon Chaffee <chaffee@plateau.cs.berkeley.edu>
+ * Merged with msdos fs by Henrik Storner <storner@osiris.ping.dk>
+ * Rewritten for constant inumbers. Plugged buffer overrun in readdir(). AV
+ * Short name translation 1999, 2001 by Wolfram Pienkoss <wp@bszh.de>
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/msdos_fs.h>
+#include <linux/dirent.h>
+#include <linux/smp_lock.h>
+#include <linux/buffer_head.h>
+#include <asm/uaccess.h>
+
+static inline loff_t fat_make_i_pos(struct super_block *sb,
+ struct buffer_head *bh,
+ struct msdos_dir_entry *de)
+{
+ return ((loff_t)bh->b_blocknr << MSDOS_SB(sb)->dir_per_block_bits)
+ | (de - (struct msdos_dir_entry *)bh->b_data);
+}
+
+/* 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.
+ AV. Most often we do it item-by-item. Makes sense to optimize.
+ AV. OK, there we go: if both bh and de are non-NULL we assume that we just
+ AV. want the next entry (took one explicit de=NULL in vfat/namei.c).
+ AV. It's done in fat_get_entry() (inlined), here the slow case lives.
+ AV. Additionally, when we return -1 (i.e. reached the end of directory)
+ AV. we make bh NULL.
+ */
+static int fat__get_entry(struct inode *dir, loff_t *pos,
+ struct buffer_head **bh, struct msdos_dir_entry **de)
+{
+ struct super_block *sb = dir->i_sb;
+ sector_t phys, iblock;
+ int offset;
+ int err;
+
+next:
+ if (*bh)
+ brelse(*bh);
+
+ *bh = NULL;
+ iblock = *pos >> sb->s_blocksize_bits;
+ err = fat_bmap(dir, iblock, &phys);
+ if (err || !phys)
+ return -1; /* beyond EOF or error */
+
+ *bh = sb_bread(sb, phys);
+ if (*bh == NULL) {
+ printk(KERN_ERR "FAT: Directory bread(block %llu) failed\n",
+ (unsigned long long)phys);
+ /* skip this block */
+ *pos = (iblock + 1) << sb->s_blocksize_bits;
+ goto next;
+ }
+
+ offset = *pos & (sb->s_blocksize - 1);
+ *pos += sizeof(struct msdos_dir_entry);
+ *de = (struct msdos_dir_entry *)((*bh)->b_data + offset);
+
+ return 0;
+}
+
+static inline int fat_get_entry(struct inode *dir, loff_t *pos,
+ struct buffer_head **bh,
+ struct msdos_dir_entry **de)
+{
+ /* Fast stuff first */
+ if (*bh && *de &&
+ (*de - (struct msdos_dir_entry *)(*bh)->b_data) < MSDOS_SB(dir->i_sb)->dir_per_block - 1) {
+ *pos += sizeof(struct msdos_dir_entry);
+ (*de)++;
+ return 0;
+ }
+ return fat__get_entry(dir, pos, bh, de);
+}
+
+/*
+ * Convert Unicode 16 to UTF8, 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
+ * of Unicode value. This lets us do a full dump and restore of Unicode
+ * filenames. We could get into some trouble with long Unicode names,
+ * but ignore that right now.
+ * Ahem... Stack smashing in ring 0 isn't fun. Fixed.
+ */
+static int uni16_to_x8(unsigned char *ascii, wchar_t *uni, int uni_xlate,
+ struct nls_table *nls)
+{
+ wchar_t *ip, ec;
+ unsigned char *op, nc;
+ int charlen;
+ int k;
+
+ ip = uni;
+ op = ascii;
+
+ while (*ip) {
+ ec = *ip++;
+ if ( (charlen = nls->uni2char(ec, op, NLS_MAX_CHARSET_SIZE)) > 0) {
+ op += charlen;
+ } else {
+ if (uni_xlate == 1) {
+ *op = ':';
+ for (k = 4; k > 0; k--) {
+ nc = ec & 0xF;
+ op[k] = nc > 9 ? nc + ('a' - 10)
+ : nc + '0';
+ ec >>= 4;
+ }
+ op += 5;
+ } else {
+ *op++ = '?';
+ }
+ }
+ /* We have some slack there, so it's OK */
+ if (op>ascii+256) {
+ op = ascii + 256;
+ break;
+ }
+ }
+ *op = 0;
+ return (op - ascii);
+}
+
+static inline int
+fat_short2uni(struct nls_table *t, unsigned char *c, int clen, wchar_t *uni)
+{
+ int charlen;
+
+ charlen = t->char2uni(c, clen, uni);
+ if (charlen < 0) {
+ *uni = 0x003f; /* a question mark */
+ charlen = 1;
+ }
+ return charlen;
+}
+
+static inline int
+fat_short2lower_uni(struct nls_table *t, unsigned char *c, int clen, wchar_t *uni)
+{
+ int charlen;
+ wchar_t wc;
+
+ charlen = t->char2uni(c, clen, &wc);
+ if (charlen < 0) {
+ *uni = 0x003f; /* a question mark */
+ charlen = 1;
+ } else if (charlen <= 1) {
+ unsigned char nc = t->charset2lower[*c];
+
+ if (!nc)
+ nc = *c;
+
+ if ( (charlen = t->char2uni(&nc, 1, uni)) < 0) {
+ *uni = 0x003f; /* a question mark */
+ charlen = 1;
+ }
+ } else
+ *uni = wc;
+
+ return charlen;
+}
+
+static inline int
+fat_shortname2uni(struct nls_table *nls, unsigned char *buf, int buf_size,
+ wchar_t *uni_buf, unsigned short opt, int lower)
+{
+ int len = 0;
+
+ if (opt & VFAT_SFN_DISPLAY_LOWER)
+ len = fat_short2lower_uni(nls, buf, buf_size, uni_buf);
+ else if (opt & VFAT_SFN_DISPLAY_WIN95)
+ len = fat_short2uni(nls, buf, buf_size, uni_buf);
+ else if (opt & VFAT_SFN_DISPLAY_WINNT) {
+ if (lower)
+ len = fat_short2lower_uni(nls, buf, buf_size, uni_buf);
+ else
+ len = fat_short2uni(nls, buf, buf_size, uni_buf);
+ } else
+ len = fat_short2uni(nls, buf, buf_size, uni_buf);
+
+ return len;
+}
+
+/*
+ * Return values: negative -> error, 0 -> not found, positive -> found,
+ * value is the total amount of slots, including the shortname entry.
+ */
+int fat_search_long(struct inode *inode, const unsigned char *name,
+ int name_len, struct fat_slot_info *sinfo)
+{
+ struct super_block *sb = inode->i_sb;
+ struct msdos_sb_info *sbi = MSDOS_SB(sb);
+ struct buffer_head *bh = NULL;
+ struct msdos_dir_entry *de;
+ struct nls_table *nls_io = sbi->nls_io;
+ struct nls_table *nls_disk = sbi->nls_disk;
+ wchar_t bufuname[14];
+ unsigned char xlate_len, nr_slots;
+ wchar_t *unicode = NULL;
+ unsigned char work[8], bufname[260]; /* 256 + 4 */
+ int uni_xlate = sbi->options.unicode_xlate;
+ int utf8 = sbi->options.utf8;
+ int anycase = (sbi->options.name_check != 's');
+ unsigned short opt_shortname = sbi->options.shortname;
+ loff_t cpos = 0;
+ int chl, i, j, last_u, err;
+
+ err = -ENOENT;
+ while(1) {
+ if (fat_get_entry(inode, &cpos, &bh, &de) == -1)
+ goto EODir;
+parse_record:
+ nr_slots = 0;
+ if (de->name[0] == DELETED_FLAG)
+ continue;
+ if (de->attr != ATTR_EXT && (de->attr & ATTR_VOLUME))
+ continue;
+ 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) */
+ 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;
+ }
+
+ memcpy(work, de->name, sizeof(de->name));
+ /* see namei.c, msdos_format_name */
+ if (work[0] == 0x05)
+ work[0] = 0xE5;
+ for (i = 0, j = 0, last_u = 0; i < 8;) {
+ if (!work[i]) break;
+ chl = fat_shortname2uni(nls_disk, &work[i], 8 - i,
+ &bufuname[j++], opt_shortname,
+ de->lcase & CASE_LOWER_BASE);
+ if (chl <= 1) {
+ if (work[i] != ' ')
+ last_u = j;
+ } else {
+ last_u = j;
+ }
+ i += chl;
+ }
+ j = last_u;
+ fat_short2uni(nls_disk, ".", 1, &bufuname[j++]);
+ for (i = 0; i < 3;) {
+ if (!de->ext[i]) break;
+ chl = fat_shortname2uni(nls_disk, &de->ext[i], 3 - i,
+ &bufuname[j++], opt_shortname,
+ de->lcase & CASE_LOWER_EXT);
+ if (chl <= 1) {
+ if (de->ext[i] != ' ')
+ last_u = j;
+ } else {
+ last_u = j;
+ }
+ i += chl;
+ }
+ if (!last_u)
+ continue;
+
+ bufuname[last_u] = 0x0000;
+ xlate_len = utf8
+ ?utf8_wcstombs(bufname, bufuname, sizeof(bufname))
+ :uni16_to_x8(bufname, bufuname, uni_xlate, nls_io);
+ if (xlate_len == name_len)
+ if ((!anycase && !memcmp(name, bufname, xlate_len)) ||
+ (anycase && !nls_strnicmp(nls_io, name, bufname,
+ xlate_len)))
+ goto Found;
+
+ if (nr_slots) {
+ xlate_len = utf8
+ ?utf8_wcstombs(bufname, unicode, sizeof(bufname))
+ :uni16_to_x8(bufname, unicode, uni_xlate, nls_io);
+ if (xlate_len != name_len)
+ continue;
+ if ((!anycase && !memcmp(name, bufname, xlate_len)) ||
+ (anycase && !nls_strnicmp(nls_io, name, bufname,
+ xlate_len)))
+ goto Found;
+ }
+ }
+
+Found:
+ nr_slots++; /* include the de */
+ sinfo->slot_off = cpos - nr_slots * sizeof(*de);
+ sinfo->nr_slots = nr_slots;
+ sinfo->de = de;
+ sinfo->bh = bh;
+ sinfo->i_pos = fat_make_i_pos(sb, sinfo->bh, sinfo->de);
+ err = 0;
+EODir:
+ if (unicode)
+ free_page((unsigned long)unicode);
+
+ return err;
+}
+
+EXPORT_SYMBOL(fat_search_long);
+
+struct fat_ioctl_filldir_callback {
+ struct dirent __user *dirent;
+ int result;
+ /* for dir ioctl */
+ const char *longname;
+ int long_len;
+ const char *shortname;
+ int short_len;
+};
+
+static int fat_readdirx(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);
+ struct buffer_head *bh;
+ struct msdos_dir_entry *de;
+ struct nls_table *nls_io = sbi->nls_io;
+ struct nls_table *nls_disk = sbi->nls_disk;
+ unsigned char long_slots;
+ const char *fill_name;
+ int fill_len;
+ wchar_t bufuname[14];
+ wchar_t *unicode = NULL;
+ unsigned char c, work[8], bufname[56], *ptname = bufname;
+ unsigned long lpos, dummy, *furrfu = &lpos;
+ int uni_xlate = sbi->options.unicode_xlate;
+ int isvfat = sbi->options.isvfat;
+ int utf8 = sbi->options.utf8;
+ int nocase = sbi->options.nocase;
+ unsigned short opt_shortname = sbi->options.shortname;
+ unsigned long inum;
+ int chi, chl, i, i2, j, last, last_u, dotoffset = 0;
+ loff_t cpos;
+ int ret = 0;
+
+ lock_kernel();
+
+ cpos = filp->f_pos;
+ /* Fake . and .. for the root directory. */
+ if (inode->i_ino == MSDOS_ROOT_INO) {
+ while (cpos < 2) {
+ if (filldir(dirent, "..", cpos+1, cpos, MSDOS_ROOT_INO, DT_DIR) < 0)
+ goto out;
+ cpos++;
+ filp->f_pos++;
+ }
+ if (cpos == 2) {
+ dummy = 2;
+ furrfu = &dummy;
+ cpos = 0;
+ }
+ }
+ if (cpos & (sizeof(struct msdos_dir_entry)-1)) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ bh = NULL;
+GetNew:
+ long_slots = 0;
+ if (fat_get_entry(inode, &cpos, &bh, &de) == -1)
+ goto EODir;
+ /* Check for long filename entry */
+ if (isvfat) {
+ if (de->name[0] == DELETED_FLAG)
+ goto RecEnd;
+ if (de->attr != ATTR_EXT && (de->attr & ATTR_VOLUME))
+ goto RecEnd;
+ if (de->attr != ATTR_EXT && IS_FREE(de->name))
+ goto RecEnd;
+ } else {
+ if ((de->attr & ATTR_VOLUME) || IS_FREE(de->name))
+ goto RecEnd;
+ }
+
+ 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))
+ 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;
+ }
+
+ if (sbi->options.dotsOK) {
+ ptname = bufname;
+ dotoffset = 0;
+ if (de->attr & ATTR_HIDDEN) {
+ *ptname++ = '.';
+ dotoffset = 1;
+ }
+ }
+
+ memcpy(work, de->name, sizeof(de->name));
+ /* see namei.c, msdos_format_name */
+ if (work[0] == 0x05)
+ work[0] = 0xE5;
+ for (i = 0, j = 0, last = 0, last_u = 0; i < 8;) {
+ if (!(c = work[i])) break;
+ chl = fat_shortname2uni(nls_disk, &work[i], 8 - i,
+ &bufuname[j++], opt_shortname,
+ de->lcase & CASE_LOWER_BASE);
+ if (chl <= 1) {
+ ptname[i++] = (!nocase && c>='A' && c<='Z') ? c+32 : c;
+ if (c != ' ') {
+ last = i;
+ last_u = j;
+ }
+ } else {
+ last_u = j;
+ for (chi = 0; chi < chl && i < 8; chi++) {
+ ptname[i] = work[i];
+ i++; last = i;
+ }
+ }
+ }
+ i = last;
+ j = last_u;
+ fat_short2uni(nls_disk, ".", 1, &bufuname[j++]);
+ ptname[i++] = '.';
+ for (i2 = 0; i2 < 3;) {
+ if (!(c = de->ext[i2])) break;
+ chl = fat_shortname2uni(nls_disk, &de->ext[i2], 3 - i2,
+ &bufuname[j++], opt_shortname,
+ de->lcase & CASE_LOWER_EXT);
+ if (chl <= 1) {
+ i2++;
+ ptname[i++] = (!nocase && c>='A' && c<='Z') ? c+32 : c;
+ if (c != ' ') {
+ last = i;
+ last_u = j;
+ }
+ } else {
+ last_u = j;
+ for (chi = 0; chi < chl && i2 < 3; chi++) {
+ ptname[i++] = de->ext[i2++];
+ last = i;
+ }
+ }
+ }
+ if (!last)
+ goto RecEnd;
+
+ i = last + dotoffset;
+ j = last_u;
+
+ lpos = cpos - (long_slots+1)*sizeof(struct msdos_dir_entry);
+ 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);
+ } else {
+ loff_t i_pos = fat_make_i_pos(sb, bh, de);
+ struct inode *tmp = fat_iget(sb, i_pos);
+ if (tmp) {
+ inum = tmp->i_ino;
+ iput(tmp);
+ } else
+ inum = iunique(sb, MSDOS_ROOT_INO);
+ }
+
+ if (isvfat) {
+ bufuname[j] = 0x0000;
+ i = utf8 ? utf8_wcstombs(bufname, bufuname, sizeof(bufname))
+ : uni16_to_x8(bufname, bufuname, uni_xlate, nls_io);
+ }
+
+ fill_name = bufname;
+ fill_len = i;
+ if (!short_only && long_slots) {
+ /* convert the unicode long name. 261 is maximum size
+ * of unicode buffer. (13 * slots + nul) */
+ void *longname = unicode + 261;
+ int buf_size = PAGE_SIZE - (261 * sizeof(unicode[0]));
+ int long_len = utf8
+ ? utf8_wcstombs(longname, unicode, buf_size)
+ : uni16_to_x8(longname, unicode, uni_xlate, nls_io);
+
+ if (!both) {
+ fill_name = longname;
+ fill_len = long_len;
+ } else {
+ /* hack for fat_ioctl_filldir() */
+ struct fat_ioctl_filldir_callback *p = dirent;
+
+ p->longname = longname;
+ p->long_len = long_len;
+ p->shortname = bufname;
+ p->short_len = i;
+ fill_name = NULL;
+ fill_len = 0;
+ }
+ }
+ if (filldir(dirent, fill_name, fill_len, *furrfu, inum,
+ (de->attr & ATTR_DIR) ? DT_DIR : DT_REG) < 0)
+ goto FillFailed;
+
+RecEnd:
+ furrfu = &lpos;
+ filp->f_pos = cpos;
+ goto GetNew;
+EODir:
+ filp->f_pos = cpos;
+FillFailed:
+ if (bh)
+ brelse(bh);
+ if (unicode)
+ free_page((unsigned long)unicode);
+out:
+ unlock_kernel();
+ return ret;
+}
+
+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);
+}
+
+static int fat_ioctl_filldir(void *__buf, const char *name, int name_len,
+ loff_t offset, ino_t ino, unsigned int d_type)
+{
+ struct fat_ioctl_filldir_callback *buf = __buf;
+ struct dirent __user *d1 = buf->dirent;
+ struct dirent __user *d2 = d1 + 1;
+
+ if (buf->result)
+ return -EINVAL;
+ buf->result++;
+
+ if (name != NULL) {
+ /* dirent has only short name */
+ if (name_len >= sizeof(d1->d_name))
+ name_len = sizeof(d1->d_name) - 1;
+
+ if (put_user(0, d2->d_name) ||
+ put_user(0, &d2->d_reclen) ||
+ copy_to_user(d1->d_name, name, name_len) ||
+ put_user(0, d1->d_name + name_len) ||
+ put_user(name_len, &d1->d_reclen))
+ goto efault;
+ } else {
+ /* dirent has short and long name */
+ const char *longname = buf->longname;
+ int long_len = buf->long_len;
+ const char *shortname = buf->shortname;
+ int short_len = buf->short_len;
+
+ if (long_len >= sizeof(d1->d_name))
+ long_len = sizeof(d1->d_name) - 1;
+ if (short_len >= sizeof(d1->d_name))
+ short_len = sizeof(d1->d_name) - 1;
+
+ if (copy_to_user(d2->d_name, longname, long_len) ||
+ put_user(0, d2->d_name + long_len) ||
+ put_user(long_len, &d2->d_reclen) ||
+ put_user(ino, &d2->d_ino) ||
+ put_user(offset, &d2->d_off) ||
+ copy_to_user(d1->d_name, shortname, short_len) ||
+ put_user(0, d1->d_name + short_len) ||
+ put_user(short_len, &d1->d_reclen))
+ goto efault;
+ }
+ return 0;
+efault:
+ buf->result = -EFAULT;
+ return -EFAULT;
+}
+
+static int fat_dir_ioctl(struct inode * inode, struct file * filp,
+ unsigned int cmd, unsigned long arg)
+{
+ struct fat_ioctl_filldir_callback buf;
+ struct dirent __user *d1;
+ int ret, short_only, both;
+
+ switch (cmd) {
+ case VFAT_IOCTL_READDIR_SHORT:
+ short_only = 1;
+ both = 0;
+ break;
+ case VFAT_IOCTL_READDIR_BOTH:
+ short_only = 0;
+ both = 1;
+ break;
+ default:
+ return fat_generic_ioctl(inode, filp, cmd, arg);
+ }
+
+ d1 = (struct dirent __user *)arg;
+ if (!access_ok(VERIFY_WRITE, d1, sizeof(struct dirent[2])))
+ return -EFAULT;
+ /*
+ * Yes, we don't need this put_user() absolutely. However old
+ * code didn't return the right value. So, app use this value,
+ * in order to check whether it is EOF.
+ */
+ if (put_user(0, &d1->d_reclen))
+ return -EFAULT;
+
+ buf.dirent = d1;
+ buf.result = 0;
+ down(&inode->i_sem);
+ ret = -ENOENT;
+ if (!IS_DEADDIR(inode)) {
+ ret = fat_readdirx(inode, filp, &buf, fat_ioctl_filldir,
+ short_only, both);
+ }
+ up(&inode->i_sem);
+ if (ret >= 0)
+ ret = buf.result;
+ return ret;
+}
+
+struct file_operations fat_dir_operations = {
+ .read = generic_read_dir,
+ .readdir = fat_readdir,
+ .ioctl = fat_dir_ioctl,
+ .fsync = file_fsync,
+};
+
+static int fat_get_short_entry(struct inode *dir, loff_t *pos,
+ struct buffer_head **bh,
+ struct msdos_dir_entry **de)
+{
+ while (fat_get_entry(dir, pos, bh, de) >= 0) {
+ /* free entry or long name entry or volume label */
+ if (!IS_FREE((*de)->name) && !((*de)->attr & ATTR_VOLUME))
+ return 0;
+ }
+ return -ENOENT;
+}
+
+/*
+ * The ".." entry can not provide the "struct fat_slot_info" informations
+ * for inode. So, this function provide the some informations only.
+ */
+int fat_get_dotdot_entry(struct inode *dir, struct buffer_head **bh,
+ struct msdos_dir_entry **de, loff_t *i_pos)
+{
+ loff_t offset;
+
+ offset = 0;
+ *bh = NULL;
+ while (fat_get_short_entry(dir, &offset, bh, de) >= 0) {
+ if (!strncmp((*de)->name, MSDOS_DOTDOT, MSDOS_NAME)) {
+ *i_pos = fat_make_i_pos(dir->i_sb, *bh, *de);
+ return 0;
+ }
+ }