123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684 |
- /*
- * 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)
|