Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / fs / fat / namei_msdos.c
diff --git a/kernel/fs/fat/namei_msdos.c b/kernel/fs/fat/namei_msdos.c
new file mode 100644 (file)
index 0000000..b7e2b33
--- /dev/null
@@ -0,0 +1,684 @@
+/*
+ *  linux/fs/msdos/namei.c
+ *
+ *  Written 1992,1993 by Werner Almesberger
+ *  Hidden files 1995 by Albert Cahalan <albert@ccs.neu.edu> <adc@coe.neu.edu>
+ *  Rewritten for constant inumbers 1999 by Al Viro
+ */
+
+#include <linux/module.h>
+#include "fat.h"
+
+/* Characters that are undesirable in an MS-DOS file name */
+static unsigned char bad_chars[] = "*?<>|\"";
+static unsigned char bad_if_strict[] = "+=,; ";
+
+/***** 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
+        * 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;
+       unsigned char c;
+       int space;
+
+       if (name[0] == '.') {   /* dotfile because . and .. already done */
+               if (opts->dotsOK) {
+                       /* Get rid of dot - test for it elsewhere */
+                       name++;
+                       len--;
+               } else
+                       return -EINVAL;
+       }
+       /*
+        * disallow names that _really_ start with a dot
+        */
+       space = 1;
+       c = 0;
+       for (walk = res; len && walk - res < 8; walk++) {
+               c = *name++;
+               len--;
+               if (opts->name_check != 'r' && strchr(bad_chars, c))
+                       return -EINVAL;
+               if (opts->name_check == 's' && strchr(bad_if_strict, c))
+                       return -EINVAL;
+               if (c >= 'A' && c <= 'Z' && opts->name_check == 's')
+                       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;
+               space = (c == ' ');
+               *walk = (!opts->nocase && c >= 'a' && c <= 'z') ? c - 32 : c;
+       }
+       if (space)
+               return -EINVAL;
+       if (opts->name_check == 's' && len && c != '.') {
+               c = *name++;
+               len--;
+               if (c != '.')
+                       return -EINVAL;
+       }
+       while (c != '.' && len--)
+               c = *name++;
+       if (c == '.') {
+               while (walk - res < 8)
+                       *walk++ = ' ';
+               while (len > 0 && walk - res < MSDOS_NAME) {
+                       c = *name++;
+                       len--;
+                       if (opts->name_check != 'r' && strchr(bad_chars, c))
+                               return -EINVAL;
+                       if (opts->name_check == 's' &&
+                           strchr(bad_if_strict, c))
+                               return -EINVAL;
+                       if (c < ' ' || c == ':' || c == '\\')
+                               return -EINVAL;
+                       if (c == '.') {
+                               if (opts->name_check == 's')
+                                       return -EINVAL;
+                               break;
+                       }
+                       if (c >= 'A' && c <= 'Z' && opts->name_check == 's')
+                               return -EINVAL;
+                       space = 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;
+       }
+       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 fat_slot_info *sinfo)
+{
+       struct msdos_sb_info *sbi = MSDOS_SB(dir->i_sb);
+       unsigned char msdos_name[MSDOS_NAME];
+       int err;
+
+       err = msdos_format_name(name, len, msdos_name, &sbi->options);
+       if (err)
+               return -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 (sinfo->de->attr & ATTR_HIDDEN)
+                               err = -ENOENT;
+               }
+               if (err)
+                       brelse(sinfo->bh);
+       }
+       return err;
+}
+
+/*
+ * Compute the hash for the msdos name corresponding to the dentry.
+ * Note: if the name is invalid, we leave the hash code unchanged so
+ * that the existing dentry can be used. The msdos fs routines will
+ * return ENOENT or EINVAL as appropriate.
+ */
+static int msdos_hash(const struct dentry *dentry, struct qstr *qstr)
+{
+       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);
+       return 0;
+}
+
+/*
+ * Compare two msdos names. If either of the names are invalid,
+ * we fall back to doing the standard name comparison.
+ */
+static int msdos_cmp(const struct dentry *parent, const struct dentry *dentry,
+               unsigned int len, const char *str, const struct qstr *name)
+{
+       struct fat_mount_options *options = &MSDOS_SB(parent->d_sb)->options;
+       unsigned char a_msdos_name[MSDOS_NAME], b_msdos_name[MSDOS_NAME];
+       int error;
+
+       error = msdos_format_name(name->name, name->len, a_msdos_name, options);
+       if (error)
+               goto old_compare;
+       error = msdos_format_name(str, len, b_msdos_name, options);
+       if (error)
+               goto old_compare;
+       error = memcmp(a_msdos_name, b_msdos_name, MSDOS_NAME);
+out:
+       return error;
+
+old_compare:
+       error = 1;
+       if (name->len == len)
+               error = memcmp(name->name, str, len);
+       goto out;
+}
+
+static const struct dentry_operations msdos_dentry_operations = {
+       .d_hash         = msdos_hash,
+       .d_compare      = msdos_cmp,
+};
+
+/*
+ * AV. Wrappers for FAT sb operations. Is it wise?
+ */
+
+/***** Get inode using directory and name */
+static struct dentry *msdos_lookup(struct inode *dir, struct dentry *dentry,
+                                  unsigned int flags)
+{
+       struct super_block *sb = dir->i_sb;
+       struct fat_slot_info sinfo;
+       struct inode *inode;
+       int err;
+
+       mutex_lock(&MSDOS_SB(sb)->s_lock);
+       err = msdos_find(dir, dentry->d_name.name, dentry->d_name.len, &sinfo);
+       switch (err) {
+       case -ENOENT:
+               inode = NULL;
+               break;
+       case 0:
+               inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos);
+               brelse(sinfo.bh);
+               break;
+       default:
+               inode = ERR_PTR(err);
+       }
+       mutex_unlock(&MSDOS_SB(sb)->s_lock);
+       return d_splice_alias(inode, dentry);
+}
+
+/***** Creates a directory entry (name is already formatted). */
+static int msdos_add_entry(struct inode *dir, const unsigned char *name,
+                          int is_dir, int is_hid, int cluster,
+                          struct timespec *ts, struct fat_slot_info *sinfo)
+{
+       struct msdos_sb_info *sbi = MSDOS_SB(dir->i_sb);
+       struct msdos_dir_entry de;
+       __le16 time, date;
+       int err;
+
+       memcpy(de.name, name, MSDOS_NAME);
+       de.attr = is_dir ? ATTR_DIR : ATTR_ARCH;
+       if (is_hid)
+               de.attr |= ATTR_HIDDEN;
+       de.lcase = 0;
+       fat_time_unix2fat(sbi, ts, &time, &date, NULL);
+       de.cdate = de.adate = 0;
+       de.ctime = 0;
+       de.ctime_cs = 0;
+       de.time = time;
+       de.date = date;
+       fat_set_start(&de, cluster);
+       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, umode_t mode,
+                       bool excl)
+{
+       struct super_block *sb = dir->i_sb;
+       struct inode *inode = NULL;
+       struct fat_slot_info sinfo;
+       struct timespec ts;
+       unsigned char msdos_name[MSDOS_NAME];
+       int err, is_hid;
+
+       mutex_lock(&MSDOS_SB(sb)->s_lock);
+
+       err = msdos_format_name(dentry->d_name.name, dentry->d_name.len,
+                               msdos_name, &MSDOS_SB(sb)->options);
+       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, &sinfo)) {
+               brelse(sinfo.bh);
+               err = -EINVAL;
+               goto out;
+       }
+
+       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 = ts;
+       /* timestamp is already written, so mark_inode_dirty() is unneeded. */
+
+       d_instantiate(dentry, inode);
+out:
+       mutex_unlock(&MSDOS_SB(sb)->s_lock);
+       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 super_block *sb = dir->i_sb;
+       struct inode *inode = d_inode(dentry);
+       struct fat_slot_info sinfo;
+       int err;
+
+       mutex_lock(&MSDOS_SB(sb)->s_lock);
+       /*
+        * Check whether the directory is not in use, then check
+        * whether it is empty.
+        */
+       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;
+
+       err = fat_remove_entries(dir, &sinfo);  /* and releases bh */
+       if (err)
+               goto out;
+       drop_nlink(dir);
+
+       clear_nlink(inode);
+       inode->i_ctime = CURRENT_TIME_SEC;
+       fat_detach(inode);
+out:
+       mutex_unlock(&MSDOS_SB(sb)->s_lock);
+       if (!err)
+               err = fat_flush_inodes(sb, dir, inode);
+
+       return err;
+}
+
+/***** Make a directory */
+static int msdos_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
+{
+       struct super_block *sb = dir->i_sb;
+       struct fat_slot_info sinfo;
+       struct inode *inode;
+       unsigned char msdos_name[MSDOS_NAME];
+       struct timespec ts;
+       int err, is_hid, cluster;
+
+       mutex_lock(&MSDOS_SB(sb)->s_lock);
+
+       err = msdos_format_name(dentry->d_name.name, dentry->d_name.len,
+                               msdos_name, &MSDOS_SB(sb)->options);
+       if (err)
+               goto out;
+       is_hid = (dentry->d_name.name[0] == '.') && (msdos_name[0] != '.');
+       /* foo vs .foo situation */
+       if (!fat_scan(dir, msdos_name, &sinfo)) {
+               brelse(sinfo.bh);
+               err = -EINVAL;
+               goto out;
+       }
+
+       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;
+       }
+       set_nlink(inode, 2);
+       inode->i_mtime = inode->i_atime = inode->i_ctime = ts;
+       /* timestamp is already written, so mark_inode_dirty() is unneeded. */
+
+       d_instantiate(dentry, inode);
+
+       mutex_unlock(&MSDOS_SB(sb)->s_lock);
+       fat_flush_inodes(sb, dir, inode);
+       return 0;
+
+out_free:
+       fat_free_clusters(dir, cluster);
+out:
+       mutex_unlock(&MSDOS_SB(sb)->s_lock);
+       return err;
+}
+
+/***** Unlink a file */
+static int msdos_unlink(struct inode *dir, struct dentry *dentry)
+{
+       struct inode *inode = d_inode(dentry);
+       struct super_block *sb = inode->i_sb;
+       struct fat_slot_info sinfo;
+       int err;
+
+       mutex_lock(&MSDOS_SB(sb)->s_lock);
+       err = msdos_find(dir, dentry->d_name.name, dentry->d_name.len, &sinfo);
+       if (err)
+               goto out;
+
+       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);
+out:
+       mutex_unlock(&MSDOS_SB(sb)->s_lock);
+       if (!err)
+               err = fat_flush_inodes(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, int is_hid)
+{
+       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 new_i_pos;
+       int err, old_attrs, is_dir, update_dotdot, corrupt = 0;
+
+       old_sinfo.bh = sinfo.bh = dotdot_bh = NULL;
+       old_inode = d_inode(old_dentry);
+       new_inode = d_inode(new_dentry);
+
+       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)) {
+                       err = -EIO;
+                       goto out;
+               }
+       }
+
+       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 (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;
+               }
+       }
+
+       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++;
+
+       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;
+       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) {
+               fat_set_start(dotdot_de, MSDOS_I(new_dir)->i_logstart);
+               mark_buffer_dirty_inode(dotdot_bh, old_inode);
+               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;
+       }
+out:
+       brelse(sinfo.bh);
+       brelse(dotdot_bh);
+       brelse(old_sinfo.bh);
+       return err;
+
+error_dotdot:
+       /* data cluster is shared, serious corruption */
+       corrupt = 1;
+
+       if (update_dotdot) {
+               fat_set_start(dotdot_de, MSDOS_I(old_dir)->i_logstart);
+               mark_buffer_dirty_inode(dotdot_bh, old_inode);
+               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_error(new_dir->i_sb,
+                            "%s: Filesystem corrupted (i_pos %lld)",
+                            __func__, 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 super_block *sb = old_dir->i_sb;
+       unsigned char old_msdos_name[MSDOS_NAME], new_msdos_name[MSDOS_NAME];
+       int err, is_hid;
+
+       mutex_lock(&MSDOS_SB(sb)->s_lock);
+
+       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:
+       mutex_unlock(&MSDOS_SB(sb)->s_lock);
+       if (!err)
+               err = fat_flush_inodes(sb, old_dir, new_dir);
+       return err;
+}
+
+static const struct inode_operations msdos_dir_inode_operations = {
+       .create         = msdos_create,
+       .lookup         = msdos_lookup,
+       .unlink         = msdos_unlink,
+       .mkdir          = msdos_mkdir,
+       .rmdir          = msdos_rmdir,
+       .rename         = msdos_rename,
+       .setattr        = fat_setattr,
+       .getattr        = fat_getattr,
+};
+
+static void setup(struct super_block *sb)
+{
+       MSDOS_SB(sb)->dir_ops = &msdos_dir_inode_operations;
+       sb->s_d_op = &msdos_dentry_operations;
+       sb->s_flags |= MS_NOATIME;
+}
+
+static int msdos_fill_super(struct super_block *sb, void *data, int silent)
+{
+       return fat_fill_super(sb, data, silent, 0, setup);
+}
+
+static struct dentry *msdos_mount(struct file_system_type *fs_type,
+                       int flags, const char *dev_name,
+                       void *data)
+{
+       return mount_bdev(fs_type, flags, dev_name, data, msdos_fill_super);
+}
+
+static struct file_system_type msdos_fs_type = {
+       .owner          = THIS_MODULE,
+       .name           = "msdos",
+       .mount          = msdos_mount,
+       .kill_sb        = kill_block_super,
+       .fs_flags       = FS_REQUIRES_DEV,
+};
+MODULE_ALIAS_FS("msdos");
+
+static int __init init_msdos_fs(void)
+{
+       return register_filesystem(&msdos_fs_type);
+}
+
+static void __exit exit_msdos_fs(void)
+{
+       unregister_filesystem(&msdos_fs_type);
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Werner Almesberger");
+MODULE_DESCRIPTION("MS-DOS filesystem support");
+
+module_init(init_msdos_fs)
+module_exit(exit_msdos_fs)