fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / fs / msdos / namei.c
index 817f57a..4524619 100644 (file)
 #include <linux/msdos_fs.h>
 #include <linux/smp_lock.h>
 
-
-/* MS-DOS "device special files" */
-static const unsigned char *reserved_names[] = {
-       "CON     ","PRN     ","NUL     ","AUX     ",
-       "LPT1    ","LPT2    ","LPT3    ","LPT4    ",
-       "COM1    ","COM2    ","COM3    ","COM4    ",
-       NULL
-};
-
 /* Characters that are undesirable in an MS-DOS file name */
 static unsigned char bad_chars[] = "*?<>|\"";
 static unsigned char bad_if_strict_pc[] = "+=,; ";
-static unsigned char bad_if_strict_atari[] = " "; /* GEMDOS is less restrictive */
-#define        bad_if_strict(opts) ((opts)->atari ? bad_if_strict_atari : bad_if_strict_pc)
+/* GEMDOS is less restrictive */
+static unsigned char bad_if_strict_atari[] = " ";
+
+#define bad_if_strict(opts) \
+       ((opts)->atari ? bad_if_strict_atari : bad_if_strict_pc)
 
 /***** Formats an MS-DOS file name. Rejects invalid names. */
 static int msdos_format_name(const unsigned char *name, int len,
-       unsigned char *res, struct fat_mount_options *opts)
-       /* name is the proposed name, len is its length, res is
+                            unsigned char *res, struct fat_mount_options *opts)
+       /*
+        * name is the proposed name, len is its length, res is
         * the resulting name, opts->name_check is either (r)elaxed,
         * (n)ormal or (s)trict, opts->dotsOK allows dots at the
         * beginning of name (for hidden files)
         */
 {
        unsigned char *walk;
-       const unsigned char **reserved;
        unsigned char c;
        int space;
 
-       if (name[0] == '.') {  /* dotfile because . and .. already done */
+       if (name[0] == '.') {   /* dotfile because . and .. already done */
                if (opts->dotsOK) {
                        /* Get rid of dot - test for it elsewhere */
-                       name++; len--;
-               }
-               else if (!opts->atari) return -EINVAL;
+                       name++;
+                       len--;
+               } else if (!opts->atari)
+                       return -EINVAL;
        }
-       /* disallow names that _really_ start with a dot for MS-DOS, GEMDOS does
-        * not care */
+       /*
+        * disallow names that _really_ start with a dot for MS-DOS,
+        * GEMDOS does not care
+        */
        space = !opts->atari;
        c = 0;
-       for (walk = res; len && walk-res < 8; walk++) {
-               c = *name++;
+       for (walk = res; len && walk - res < 8; walk++) {
+               c = *name++;
                len--;
-               if (opts->name_check != 'r' && strchr(bad_chars,c))
+               if (opts->name_check != 'r' && strchr(bad_chars, c))
+                       return -EINVAL;
+               if (opts->name_check == 's' && strchr(bad_if_strict(opts), c))
                        return -EINVAL;
-               if (opts->name_check == 's' && strchr(bad_if_strict(opts),c))
+               if (c >= 'A' && c <= 'Z' && opts->name_check == 's')
                        return -EINVAL;
-               if (c >= 'A' && c <= 'Z' && opts->name_check == 's')
+               if (c < ' ' || c == ':' || c == '\\')
                        return -EINVAL;
-               if (c < ' ' || c == ':' || c == '\\') return -EINVAL;
-/*  0xE5 is legal as a first character, but we must substitute 0x05     */
-/*  because 0xE5 marks deleted files.  Yes, DOS really does this.       */
-/*  It seems that Microsoft hacked DOS to support non-US characters     */
-/*  after the 0xE5 character was already in use to mark deleted files.  */
-               if((res==walk) && (c==0xE5)) c=0x05;
-               if (c == '.') break;
+       /*
+        * 0xE5 is legal as a first character, but we must substitute
+        * 0x05 because 0xE5 marks deleted files.  Yes, DOS really
+        * does this.
+        * It seems that Microsoft hacked DOS to support non-US
+        * characters after the 0xE5 character was already in use to
+        * mark deleted files.
+        */
+               if ((res == walk) && (c == 0xE5))
+                       c = 0x05;
+               if (c == '.')
+                       break;
                space = (c == ' ');
-               *walk = (!opts->nocase && c >= 'a' && c <= 'z') ? c-32 : c;
+               *walk = (!opts->nocase && c >= 'a' && c <= 'z') ? c - 32 : c;
        }
-       if (space) return -EINVAL;
+       if (space)
+               return -EINVAL;
        if (opts->name_check == 's' && len && c != '.') {
                c = *name++;
                len--;
-               if (c != '.') return -EINVAL;
+               if (c != '.')
+                       return -EINVAL;
        }
-       while (c != '.' && len--) c = *name++;
+       while (c != '.' && len--)
+               c = *name++;
        if (c == '.') {
-               while (walk-res < 8) *walk++ = ' ';
-               while (len > 0 && walk-res < MSDOS_NAME) {
+               while (walk - res < 8)
+                       *walk++ = ' ';
+               while (len > 0 && walk - res < MSDOS_NAME) {
                        c = *name++;
                        len--;
-                       if (opts->name_check != 'r' && strchr(bad_chars,c))
+                       if (opts->name_check != 'r' && strchr(bad_chars, c))
                                return -EINVAL;
                        if (opts->name_check == 's' &&
-                           strchr(bad_if_strict(opts),c))
+                           strchr(bad_if_strict(opts), c))
                                return -EINVAL;
                        if (c < ' ' || c == ':' || c == '\\')
                                return -EINVAL;
@@ -98,43 +106,47 @@ static int msdos_format_name(const unsigned char *name, int len,
                        if (c >= 'A' && c <= 'Z' && opts->name_check == 's')
                                return -EINVAL;
                        space = c == ' ';
-                       *walk++ = (!opts->nocase && c >= 'a' && c <= 'z') ? c-32 : c;
+                       if (!opts->nocase && c >= 'a' && c <= 'z')
+                               *walk++ = c - 32;
+                       else
+                               *walk++ = c;
                }
-               if (space) return -EINVAL;
-               if (opts->name_check == 's' && len) return -EINVAL;
+               if (space)
+                       return -EINVAL;
+               if (opts->name_check == 's' && len)
+                       return -EINVAL;
        }
-       while (walk-res < MSDOS_NAME) *walk++ = ' ';
-       if (!opts->atari)
-               /* GEMDOS is less stupid and has no reserved names */
-               for (reserved = reserved_names; *reserved; reserved++)
-                       if (!strncmp(res,*reserved,8)) return -EINVAL;
+       while (walk - res < MSDOS_NAME)
+               *walk++ = ' ';
+
        return 0;
 }
 
 /***** Locates a directory entry.  Uses unformatted name. */
 static int msdos_find(struct inode *dir, const unsigned char *name, int len,
-                     struct buffer_head **bh, struct msdos_dir_entry **de,
-                     loff_t *i_pos)
+                     struct fat_slot_info *sinfo)
 {
+       struct msdos_sb_info *sbi = MSDOS_SB(dir->i_sb);
        unsigned char msdos_name[MSDOS_NAME];
-       char dotsOK;
-       int res;
+       int err;
 
-       dotsOK = MSDOS_SB(dir->i_sb)->options.dotsOK;
-       res = msdos_format_name(name,len, msdos_name,&MSDOS_SB(dir->i_sb)->options);
-       if (res < 0)
+       err = msdos_format_name(name, len, msdos_name, &sbi->options);
+       if (err)
                return -ENOENT;
-       res = fat_scan(dir, msdos_name, bh, de, i_pos);
-       if (!res && dotsOK) {
-               if (name[0]=='.') {
-                       if (!((*de)->attr & ATTR_HIDDEN))
-                               res = -ENOENT;
+
+       err = fat_scan(dir, msdos_name, sinfo);
+       if (!err && sbi->options.dotsOK) {
+               if (name[0] == '.') {
+                       if (!(sinfo->de->attr & ATTR_HIDDEN))
+                               err = -ENOENT;
                } else {
-                       if ((*de)->attr & ATTR_HIDDEN)
-                               res = -ENOENT;
+                       if (sinfo->de->attr & ATTR_HIDDEN)
+                               err = -ENOENT;
                }
+               if (err)
+                       brelse(sinfo->bh);
        }
-       return res;
+       return err;
 }
 
 /*
@@ -145,10 +157,10 @@ static int msdos_find(struct inode *dir, const unsigned char *name, int len,
  */
 static int msdos_hash(struct dentry *dentry, struct qstr *qstr)
 {
-       struct fat_mount_options *options = & (MSDOS_SB(dentry->d_sb)->options);
+       struct fat_mount_options *options = &MSDOS_SB(dentry->d_sb)->options;
        unsigned char msdos_name[MSDOS_NAME];
        int error;
-       
+
        error = msdos_format_name(qstr->name, qstr->len, msdos_name, options);
        if (!error)
                qstr->hash = full_name_hash(msdos_name, MSDOS_NAME);
@@ -161,7 +173,7 @@ static int msdos_hash(struct dentry *dentry, struct qstr *qstr)
  */
 static int msdos_cmp(struct dentry *dentry, struct qstr *a, struct qstr *b)
 {
-       struct fat_mount_options *options = & (MSDOS_SB(dentry->d_sb)->options);
+       struct fat_mount_options *options = &MSDOS_SB(dentry->d_sb)->options;
        unsigned char a_msdos_name[MSDOS_NAME], b_msdos_name[MSDOS_NAME];
        int error;
 
@@ -182,7 +194,6 @@ old_compare:
        goto out;
 }
 
-
 static struct dentry_operations msdos_dentry_operations = {
        .d_hash         = msdos_hash,
        .d_compare      = msdos_cmp,
@@ -194,34 +205,33 @@ static struct dentry_operations msdos_dentry_operations = {
 
 /***** Get inode using directory and name */
 static struct dentry *msdos_lookup(struct inode *dir, struct dentry *dentry,
-               struct nameidata *nd)
+                                  struct nameidata *nd)
 {
        struct super_block *sb = dir->i_sb;
+       struct fat_slot_info sinfo;
        struct inode *inode = NULL;
-       struct msdos_dir_entry *de;
-       struct buffer_head *bh = NULL;
-       loff_t i_pos;
        int res;
-       
+
        dentry->d_op = &msdos_dentry_operations;
 
        lock_kernel();
-       res = msdos_find(dir, dentry->d_name.name, dentry->d_name.len, &bh,
-                        &de, &i_pos);
+       res = msdos_find(dir, dentry->d_name.name, dentry->d_name.len, &sinfo);
        if (res == -ENOENT)
                goto add;
        if (res < 0)
                goto out;
-       inode = fat_build_inode(sb, de, i_pos, &res);
-       if (res)
+       inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos);
+       brelse(sinfo.bh);
+       if (IS_ERR(inode)) {
+               res = PTR_ERR(inode);
                goto out;
+       }
 add:
        res = 0;
        dentry = d_splice_alias(inode, dentry);
        if (dentry)
                dentry->d_op = &msdos_dentry_operations;
 out:
-       brelse(bh);
        unlock_kernel();
        if (!res)
                return dentry;
@@ -230,347 +240,410 @@ out:
 
 /***** Creates a directory entry (name is already formatted). */
 static int msdos_add_entry(struct inode *dir, const unsigned char *name,
-                          struct buffer_head **bh,
-                          struct msdos_dir_entry **de,
-                          loff_t *i_pos, int is_dir, int is_hid)
+                          int is_dir, int is_hid, int cluster,
+                          struct timespec *ts, struct fat_slot_info *sinfo)
 {
-       int res;
-
-       res = fat_add_entries(dir, 1, bh, de, i_pos);
-       if (res < 0)
-               return res;
+       struct msdos_dir_entry de;
+       __le16 time, date;
+       int err;
 
-       /*
-        * XXX all times should be set by caller upon successful completion.
-        */
-       dir->i_ctime = dir->i_mtime = CURRENT_TIME;
-       mark_inode_dirty(dir);
-
-       memcpy((*de)->name, name, MSDOS_NAME);
-       (*de)->attr = is_dir ? ATTR_DIR : ATTR_ARCH;
+       memcpy(de.name, name, MSDOS_NAME);
+       de.attr = is_dir ? ATTR_DIR : ATTR_ARCH;
        if (is_hid)
-               (*de)->attr |= ATTR_HIDDEN;
-       (*de)->start = 0;
-       (*de)->starthi = 0;
-       fat_date_unix2dos(dir->i_mtime.tv_sec, &(*de)->time, &(*de)->date);
-       (*de)->size = 0;
-       mark_buffer_dirty(*bh);
+               de.attr |= ATTR_HIDDEN;
+       de.lcase = 0;
+       fat_date_unix2dos(ts->tv_sec, &time, &date);
+       de.cdate = de.adate = 0;
+       de.ctime = 0;
+       de.ctime_cs = 0;
+       de.time = time;
+       de.date = date;
+       de.start = cpu_to_le16(cluster);
+       de.starthi = cpu_to_le16(cluster >> 16);
+       de.size = 0;
+
+       err = fat_add_entries(dir, &de, 1, sinfo);
+       if (err)
+               return err;
+
+       dir->i_ctime = dir->i_mtime = *ts;
+       if (IS_DIRSYNC(dir))
+               (void)fat_sync_inode(dir);
+       else
+               mark_inode_dirty(dir);
+
        return 0;
 }
 
 /***** Create a file */
 static int msdos_create(struct inode *dir, struct dentry *dentry, int mode,
-               struct nameidata *nd)
+                       struct nameidata *nd)
 {
        struct super_block *sb = dir->i_sb;
-       struct buffer_head *bh;
-       struct msdos_dir_entry *de;
-       struct inode *inode;
-       loff_t i_pos;
-       int res, is_hid;
+       struct inode *inode = NULL;
+       struct fat_slot_info sinfo;
+       struct timespec ts;
        unsigned char msdos_name[MSDOS_NAME];
+       int err, is_hid;
 
        lock_kernel();
-       res = msdos_format_name(dentry->d_name.name,dentry->d_name.len,
+
+       err = msdos_format_name(dentry->d_name.name, dentry->d_name.len,
                                msdos_name, &MSDOS_SB(sb)->options);
-       if (res < 0) {
-               unlock_kernel();
-               return res;
-       }
-       is_hid = (dentry->d_name.name[0]=='.') && (msdos_name[0]!='.');
+       if (err)
+               goto out;
+       is_hid = (dentry->d_name.name[0] == '.') && (msdos_name[0] != '.');
        /* Have to do it due to foo vs. .foo conflicts */
-       if (fat_scan(dir, msdos_name, &bh, &de, &i_pos) >= 0) {
-               brelse(bh);
-               unlock_kernel();
-               return -EINVAL;
-       }
-       inode = NULL;
-       res = msdos_add_entry(dir, msdos_name, &bh, &de, &i_pos, 0, is_hid);
-       if (res) {
-               unlock_kernel();
-               return res;
+       if (!fat_scan(dir, msdos_name, &sinfo)) {
+               brelse(sinfo.bh);
+               err = -EINVAL;
+               goto out;
        }
-       inode = fat_build_inode(dir->i_sb, de, i_pos, &res);
-       brelse(bh);
-       if (!inode) {
-               unlock_kernel();
-               return res;
+
+       ts = CURRENT_TIME_SEC;
+       err = msdos_add_entry(dir, msdos_name, 0, is_hid, 0, &ts, &sinfo);
+       if (err)
+               goto out;
+       inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos);
+       brelse(sinfo.bh);
+       if (IS_ERR(inode)) {
+               err = PTR_ERR(inode);
+               goto out;
        }
-       inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME;
-       mark_inode_dirty(inode);
+       inode->i_mtime = inode->i_atime = inode->i_ctime = ts;
+       /* timestamp is already written, so mark_inode_dirty() is unneeded. */
+
        d_instantiate(dentry, inode);
+out:
        unlock_kernel();
-       return 0;
+       if (!err)
+               err = fat_flush_inodes(sb, dir, inode);
+       return err;
 }
 
 /***** Remove a directory */
 static int msdos_rmdir(struct inode *dir, struct dentry *dentry)
 {
        struct inode *inode = dentry->d_inode;
-       loff_t i_pos;
-       int res;
-       struct buffer_head *bh;
-       struct msdos_dir_entry *de;
+       struct fat_slot_info sinfo;
+       int err;
 
-       bh = NULL;
        lock_kernel();
-       res = msdos_find(dir, dentry->d_name.name, dentry->d_name.len,
-                        &bh, &de, &i_pos);
-       if (res < 0)
-               goto rmdir_done;
        /*
         * Check whether the directory is not in use, then check
         * whether it is empty.
         */
-       res = fat_dir_empty(inode);
-       if (res)
-               goto rmdir_done;
+       err = fat_dir_empty(inode);
+       if (err)
+               goto out;
+       err = msdos_find(dir, dentry->d_name.name, dentry->d_name.len, &sinfo);
+       if (err)
+               goto out;
 
-       de->name[0] = DELETED_FLAG;
-       mark_buffer_dirty(bh);
-       fat_detach(inode);
-       inode->i_nlink = 0;
-       inode->i_ctime = dir->i_ctime = dir->i_mtime = CURRENT_TIME;
-       dir->i_nlink--;
-       mark_inode_dirty(inode);
-       mark_inode_dirty(dir);
-       res = 0;
+       err = fat_remove_entries(dir, &sinfo);  /* and releases bh */
+       if (err)
+               goto out;
+       drop_nlink(dir);
 
-rmdir_done:
-       brelse(bh);
+       clear_nlink(inode);
+       inode->i_ctime = CURRENT_TIME_SEC;
+       fat_detach(inode);
+out:
        unlock_kernel();
-       return res;
+       if (!err)
+               err = fat_flush_inodes(inode->i_sb, dir, inode);
+
+       return err;
 }
 
 /***** Make a directory */
 static int msdos_mkdir(struct inode *dir, struct dentry *dentry, int mode)
 {
        struct super_block *sb = dir->i_sb;
-       struct buffer_head *bh;
-       struct msdos_dir_entry *de;
+       struct fat_slot_info sinfo;
        struct inode *inode;
-       int res,is_hid;
        unsigned char msdos_name[MSDOS_NAME];
-       loff_t i_pos;
+       struct timespec ts;
+       int err, is_hid, cluster;
 
        lock_kernel();
-       res = msdos_format_name(dentry->d_name.name,dentry->d_name.len,
+
+       err = msdos_format_name(dentry->d_name.name, dentry->d_name.len,
                                msdos_name, &MSDOS_SB(sb)->options);
-       if (res < 0) {
-               unlock_kernel();
-               return res;
-       }
-       is_hid = (dentry->d_name.name[0]=='.') && (msdos_name[0]!='.');
+       if (err)
+               goto out;
+       is_hid = (dentry->d_name.name[0] == '.') && (msdos_name[0] != '.');
        /* foo vs .foo situation */
-       if (fat_scan(dir, msdos_name, &bh, &de, &i_pos) >= 0)
-               goto out_exist;
-
-       res = msdos_add_entry(dir, msdos_name, &bh, &de, &i_pos, 1, is_hid);
-       if (res)
-               goto out_unlock;
-       inode = fat_build_inode(dir->i_sb, de, i_pos, &res);
-       if (!inode) {
-               brelse(bh);
-               goto out_unlock;
+       if (!fat_scan(dir, msdos_name, &sinfo)) {
+               brelse(sinfo.bh);
+               err = -EINVAL;
+               goto out;
        }
-       res = 0;
 
-       dir->i_nlink++;
-       inode->i_nlink = 2; /* no need to mark them dirty */
-
-       res = fat_new_dir(inode, dir, 0);
-       if (res)
-               goto mkdir_error;
+       ts = CURRENT_TIME_SEC;
+       cluster = fat_alloc_new_dir(dir, &ts);
+       if (cluster < 0) {
+               err = cluster;
+               goto out;
+       }
+       err = msdos_add_entry(dir, msdos_name, 1, is_hid, cluster, &ts, &sinfo);
+       if (err)
+               goto out_free;
+       inc_nlink(dir);
+
+       inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos);
+       brelse(sinfo.bh);
+       if (IS_ERR(inode)) {
+               err = PTR_ERR(inode);
+               /* the directory was completed, just return a error */
+               goto out;
+       }
+       inode->i_nlink = 2;
+       inode->i_mtime = inode->i_atime = inode->i_ctime = ts;
+       /* timestamp is already written, so mark_inode_dirty() is unneeded. */
 
-       brelse(bh);
        d_instantiate(dentry, inode);
-       res = 0;
 
-out_unlock:
        unlock_kernel();
-       return res;
-
-mkdir_error:
-       inode->i_nlink = 0;
-       inode->i_ctime = dir->i_ctime = dir->i_mtime = CURRENT_TIME;
-       dir->i_nlink--;
-       mark_inode_dirty(inode);
-       mark_inode_dirty(dir);
-       de->name[0] = DELETED_FLAG;
-       mark_buffer_dirty(bh);
-       brelse(bh);
-       fat_detach(inode);
-       iput(inode);
-       goto out_unlock;
+       fat_flush_inodes(sb, dir, inode);
+       return 0;
 
-out_exist:
-       brelse(bh);
-       res = -EINVAL;
-       goto out_unlock;
+out_free:
+       fat_free_clusters(dir, cluster);
+out:
+       unlock_kernel();
+       return err;
 }
 
 /***** Unlink a file */
 static int msdos_unlink(struct inode *dir, struct dentry *dentry)
 {
        struct inode *inode = dentry->d_inode;
-       loff_t i_pos;
-       int res;
-       struct buffer_head *bh;
-       struct msdos_dir_entry *de;
+       struct fat_slot_info sinfo;
+       int err;
 
-       bh = NULL;
        lock_kernel();
-       res = msdos_find(dir, dentry->d_name.name, dentry->d_name.len,
-                        &bh, &de, &i_pos);
-       if (res < 0)
-               goto unlink_done;
+       err = msdos_find(dir, dentry->d_name.name, dentry->d_name.len, &sinfo);
+       if (err)
+               goto out;
 
-       de->name[0] = DELETED_FLAG;
-       mark_buffer_dirty(bh);
+       err = fat_remove_entries(dir, &sinfo);  /* and releases bh */
+       if (err)
+               goto out;
+       clear_nlink(inode);
+       inode->i_ctime = CURRENT_TIME_SEC;
        fat_detach(inode);
-       brelse(bh);
-       inode->i_nlink = 0;
-       inode->i_ctime = dir->i_ctime = dir->i_mtime = CURRENT_TIME;
-       mark_inode_dirty(inode);
-       mark_inode_dirty(dir);
-       res = 0;
-unlink_done:
+out:
        unlock_kernel();
-       return res;
+       if (!err)
+               err = fat_flush_inodes(inode->i_sb, dir, inode);
+
+       return err;
 }
 
 static int do_msdos_rename(struct inode *old_dir, unsigned char *old_name,
-    struct dentry *old_dentry,
-    struct inode *new_dir, unsigned char *new_name, struct dentry *new_dentry,
-    struct buffer_head *old_bh,
-    struct msdos_dir_entry *old_de, loff_t old_i_pos, int is_hid)
+                          struct dentry *old_dentry,
+                          struct inode *new_dir, unsigned char *new_name,
+                          struct dentry *new_dentry, int is_hid)
 {
-       struct buffer_head *new_bh=NULL,*dotdot_bh=NULL;
-       struct msdos_dir_entry *new_de,*dotdot_de;
-       struct inode *old_inode,*new_inode;
-       loff_t new_i_pos, dotdot_i_pos;
-       int error;
-       int is_dir;
-
+       struct buffer_head *dotdot_bh;
+       struct msdos_dir_entry *dotdot_de;
+       struct inode *old_inode, *new_inode;
+       struct fat_slot_info old_sinfo, sinfo;
+       struct timespec ts;
+       loff_t dotdot_i_pos, new_i_pos;
+       int err, old_attrs, is_dir, update_dotdot, corrupt = 0;
+
+       old_sinfo.bh = sinfo.bh = dotdot_bh = NULL;
        old_inode = old_dentry->d_inode;
        new_inode = new_dentry->d_inode;
+
+       err = fat_scan(old_dir, old_name, &old_sinfo);
+       if (err) {
+               err = -EIO;
+               goto out;
+       }
+
        is_dir = S_ISDIR(old_inode->i_mode);
+       update_dotdot = (is_dir && old_dir != new_dir);
+       if (update_dotdot) {
+               if (fat_get_dotdot_entry(old_inode, &dotdot_bh, &dotdot_de,
+                                        &dotdot_i_pos) < 0) {
+                       err = -EIO;
+                       goto out;
+               }
+       }
 
-       if (fat_scan(new_dir, new_name, &new_bh, &new_de, &new_i_pos) >= 0
-           && !new_inode)
-               goto degenerate_case;
-       if (is_dir) {
-               if (new_inode) {
-                       error = fat_dir_empty(new_inode);
-                       if (error)
+       old_attrs = MSDOS_I(old_inode)->i_attrs;
+       err = fat_scan(new_dir, new_name, &sinfo);
+       if (!err) {
+               if (!new_inode) {
+                       /* "foo" -> ".foo" case. just change the ATTR_HIDDEN */
+                       if (sinfo.de != old_sinfo.de) {
+                               err = -EINVAL;
                                goto out;
-               }
-               if (fat_scan(old_inode, MSDOS_DOTDOT, &dotdot_bh,
-                            &dotdot_de, &dotdot_i_pos) < 0) {
-                       error = -EIO;
+                       }
+                       if (is_hid)
+                               MSDOS_I(old_inode)->i_attrs |= ATTR_HIDDEN;
+                       else
+                               MSDOS_I(old_inode)->i_attrs &= ~ATTR_HIDDEN;
+                       if (IS_DIRSYNC(old_dir)) {
+                               err = fat_sync_inode(old_inode);
+                               if (err) {
+                                       MSDOS_I(old_inode)->i_attrs = old_attrs;
+                                       goto out;
+                               }
+                       } else
+                               mark_inode_dirty(old_inode);
+
+                       old_dir->i_version++;
+                       old_dir->i_ctime = old_dir->i_mtime = CURRENT_TIME_SEC;
+                       if (IS_DIRSYNC(old_dir))
+                               (void)fat_sync_inode(old_dir);
+                       else
+                               mark_inode_dirty(old_dir);
                        goto out;
                }
        }
-       if (!new_bh) {
-               error = msdos_add_entry(new_dir, new_name, &new_bh, &new_de,
-                                       &new_i_pos, is_dir, is_hid);
-               if (error)
+
+       ts = CURRENT_TIME_SEC;
+       if (new_inode) {
+               if (err)
                        goto out;
+               if (is_dir) {
+                       err = fat_dir_empty(new_inode);
+                       if (err)
+                               goto out;
+               }
+               new_i_pos = MSDOS_I(new_inode)->i_pos;
+               fat_detach(new_inode);
+       } else {
+               err = msdos_add_entry(new_dir, new_name, is_dir, is_hid, 0,
+                                     &ts, &sinfo);
+               if (err)
+                       goto out;
+               new_i_pos = sinfo.i_pos;
        }
        new_dir->i_version++;
 
-       /* There we go */
-
-       if (new_inode)
-               fat_detach(new_inode);
-       old_de->name[0] = DELETED_FLAG;
-       mark_buffer_dirty(old_bh);
        fat_detach(old_inode);
        fat_attach(old_inode, new_i_pos);
        if (is_hid)
                MSDOS_I(old_inode)->i_attrs |= ATTR_HIDDEN;
        else
                MSDOS_I(old_inode)->i_attrs &= ~ATTR_HIDDEN;
-       mark_inode_dirty(old_inode);
-       old_dir->i_version++;
-       old_dir->i_ctime = old_dir->i_mtime = CURRENT_TIME;
-       mark_inode_dirty(old_dir);
-       if (new_inode) {
-               new_inode->i_nlink--;
-               new_inode->i_ctime = CURRENT_TIME;
-               mark_inode_dirty(new_inode);
-       }
-       if (dotdot_bh) {
-               dotdot_de->start = CT_LE_W(MSDOS_I(new_dir)->i_logstart);
-               dotdot_de->starthi = CT_LE_W((MSDOS_I(new_dir)->i_logstart) >> 16);
+       if (IS_DIRSYNC(new_dir)) {
+               err = fat_sync_inode(old_inode);
+               if (err)
+                       goto error_inode;
+       } else
+               mark_inode_dirty(old_inode);
+
+       if (update_dotdot) {
+               int start = MSDOS_I(new_dir)->i_logstart;
+               dotdot_de->start = cpu_to_le16(start);
+               dotdot_de->starthi = cpu_to_le16(start >> 16);
                mark_buffer_dirty(dotdot_bh);
-               old_dir->i_nlink--;
-               mark_inode_dirty(old_dir);
-               if (new_inode) {
-                       new_inode->i_nlink--;
-                       mark_inode_dirty(new_inode);
-               } else {
-                       new_dir->i_nlink++;
-                       mark_inode_dirty(new_dir);
+               if (IS_DIRSYNC(new_dir)) {
+                       err = sync_dirty_buffer(dotdot_bh);
+                       if (err)
+                               goto error_dotdot;
                }
+               drop_nlink(old_dir);
+               if (!new_inode)
+                       inc_nlink(new_dir);
+       }
+
+       err = fat_remove_entries(old_dir, &old_sinfo);  /* and releases bh */
+       old_sinfo.bh = NULL;
+       if (err)
+               goto error_dotdot;
+       old_dir->i_version++;
+       old_dir->i_ctime = old_dir->i_mtime = ts;
+       if (IS_DIRSYNC(old_dir))
+               (void)fat_sync_inode(old_dir);
+       else
+               mark_inode_dirty(old_dir);
+
+       if (new_inode) {
+               drop_nlink(new_inode);
+               if (is_dir)
+                       drop_nlink(new_inode);
+               new_inode->i_ctime = ts;
        }
-       error = 0;
 out:
-       brelse(new_bh);
+       brelse(sinfo.bh);
        brelse(dotdot_bh);
-       return error;
+       brelse(old_sinfo.bh);
+       return err;
 
-degenerate_case:
-       error = -EINVAL;
-       if (new_de!=old_de)
-               goto out;
-       if (is_hid)
-               MSDOS_I(old_inode)->i_attrs |= ATTR_HIDDEN;
-       else
-               MSDOS_I(old_inode)->i_attrs &= ~ATTR_HIDDEN;
-       mark_inode_dirty(old_inode);
-       old_dir->i_version++;
-       old_dir->i_ctime = old_dir->i_mtime = CURRENT_TIME;
-       mark_inode_dirty(old_dir);
-       return 0;
+error_dotdot:
+       /* data cluster is shared, serious corruption */
+       corrupt = 1;
+
+       if (update_dotdot) {
+               int start = MSDOS_I(old_dir)->i_logstart;
+               dotdot_de->start = cpu_to_le16(start);
+               dotdot_de->starthi = cpu_to_le16(start >> 16);
+               mark_buffer_dirty(dotdot_bh);
+               corrupt |= sync_dirty_buffer(dotdot_bh);
+       }
+error_inode:
+       fat_detach(old_inode);
+       fat_attach(old_inode, old_sinfo.i_pos);
+       MSDOS_I(old_inode)->i_attrs = old_attrs;
+       if (new_inode) {
+               fat_attach(new_inode, new_i_pos);
+               if (corrupt)
+                       corrupt |= fat_sync_inode(new_inode);
+       } else {
+               /*
+                * If new entry was not sharing the data cluster, it
+                * shouldn't be serious corruption.
+                */
+               int err2 = fat_remove_entries(new_dir, &sinfo);
+               if (corrupt)
+                       corrupt |= err2;
+               sinfo.bh = NULL;
+       }
+       if (corrupt < 0) {
+               fat_fs_panic(new_dir->i_sb,
+                            "%s: Filesystem corrupted (i_pos %lld)",
+                            __FUNCTION__, sinfo.i_pos);
+       }
+       goto out;
 }
 
 /***** Rename, a wrapper for rename_same_dir & rename_diff_dir */
 static int msdos_rename(struct inode *old_dir, struct dentry *old_dentry,
-               struct inode *new_dir, struct dentry *new_dentry)
+                       struct inode *new_dir, struct dentry *new_dentry)
 {
-       struct buffer_head *old_bh;
-       struct msdos_dir_entry *old_de;
-       loff_t old_i_pos;
-       int error, is_hid, old_hid; /* if new file and old file are hidden */
        unsigned char old_msdos_name[MSDOS_NAME], new_msdos_name[MSDOS_NAME];
+       int err, is_hid;
 
        lock_kernel();
-       error = msdos_format_name(old_dentry->d_name.name,
-                                 old_dentry->d_name.len,old_msdos_name,
-                                 &MSDOS_SB(old_dir->i_sb)->options);
-       if (error < 0)
-               goto rename_done;
-       error = msdos_format_name(new_dentry->d_name.name,
-                                 new_dentry->d_name.len,new_msdos_name,
-                                 &MSDOS_SB(new_dir->i_sb)->options);
-       if (error < 0)
-               goto rename_done;
-
-       is_hid  = (new_dentry->d_name.name[0]=='.') && (new_msdos_name[0]!='.');
-       old_hid = (old_dentry->d_name.name[0]=='.') && (old_msdos_name[0]!='.');
-       error = fat_scan(old_dir, old_msdos_name, &old_bh, &old_de, &old_i_pos);
-       if (error < 0)
-               goto rename_done;
-
-       error = do_msdos_rename(old_dir, old_msdos_name, old_dentry,
-                               new_dir, new_msdos_name, new_dentry,
-                               old_bh, old_de, old_i_pos, is_hid);
-       brelse(old_bh);
-
-rename_done:
+
+       err = msdos_format_name(old_dentry->d_name.name,
+                               old_dentry->d_name.len, old_msdos_name,
+                               &MSDOS_SB(old_dir->i_sb)->options);
+       if (err)
+               goto out;
+       err = msdos_format_name(new_dentry->d_name.name,
+                               new_dentry->d_name.len, new_msdos_name,
+                               &MSDOS_SB(new_dir->i_sb)->options);
+       if (err)
+               goto out;
+
+       is_hid =
+            (new_dentry->d_name.name[0] == '.') && (new_msdos_name[0] != '.');
+
+       err = do_msdos_rename(old_dir, old_msdos_name, old_dentry,
+                             new_dir, new_msdos_name, new_dentry, is_hid);
+out:
        unlock_kernel();
-       return error;
+       if (!err)
+               err = fat_flush_inodes(old_dir->i_sb, old_dir, new_dir);
+       return err;
 }
 
 static struct inode_operations msdos_dir_inode_operations = {
@@ -581,9 +654,10 @@ static struct inode_operations msdos_dir_inode_operations = {
        .rmdir          = msdos_rmdir,
        .rename         = msdos_rename,
        .setattr        = fat_notify_change,
+       .getattr        = fat_getattr,
 };
 
-static int msdos_fill_super(struct super_block *sb,void *data, int silent)
+static int msdos_fill_super(struct super_block *sb, void *data, int silent)
 {
        int res;
 
@@ -591,14 +665,17 @@ static int msdos_fill_super(struct super_block *sb,void *data, int silent)
        if (res)
                return res;
 
+       sb->s_flags |= MS_NOATIME;
        sb->s_root->d_op = &msdos_dentry_operations;
        return 0;
 }
 
-static struct super_block *msdos_get_sb(struct file_system_type *fs_type,
-       int flags, const char *dev_name, void *data)
+static int msdos_get_sb(struct file_system_type *fs_type,
+                       int flags, const char *dev_name,
+                       void *data, struct vfsmount *mnt)
 {
-       return get_sb_bdev(fs_type, flags, dev_name, data, msdos_fill_super);
+       return get_sb_bdev(fs_type, flags, dev_name, data, msdos_fill_super,
+                          mnt);
 }
 
 static struct file_system_type msdos_fs_type = {