714 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * linux/fs/msdos/namei.c4 *5 * Written 1992,1993 by Werner Almesberger6 * Hidden files 1995 by Albert Cahalan <albert@ccs.neu.edu> <adc@coe.neu.edu>7 * Rewritten for constant inumbers 1999 by Al Viro8 */9 10#include <linux/module.h>11#include <linux/iversion.h>12#include "fat.h"13 14/* Characters that are undesirable in an MS-DOS file name */15static unsigned char bad_chars[] = "*?<>|\"";16static unsigned char bad_if_strict[] = "+=,; ";17 18/***** Formats an MS-DOS file name. Rejects invalid names. */19static int msdos_format_name(const unsigned char *name, int len,20 unsigned char *res, struct fat_mount_options *opts)21 /*22 * name is the proposed name, len is its length, res is23 * the resulting name, opts->name_check is either (r)elaxed,24 * (n)ormal or (s)trict, opts->dotsOK allows dots at the25 * beginning of name (for hidden files)26 */27{28 unsigned char *walk;29 unsigned char c;30 int space;31 32 if (name[0] == '.') { /* dotfile because . and .. already done */33 if (opts->dotsOK) {34 /* Get rid of dot - test for it elsewhere */35 name++;36 len--;37 } else38 return -EINVAL;39 }40 /*41 * disallow names that _really_ start with a dot42 */43 space = 1;44 c = 0;45 for (walk = res; len && walk - res < 8; walk++) {46 c = *name++;47 len--;48 if (opts->name_check != 'r' && strchr(bad_chars, c))49 return -EINVAL;50 if (opts->name_check == 's' && strchr(bad_if_strict, c))51 return -EINVAL;52 if (c >= 'A' && c <= 'Z' && opts->name_check == 's')53 return -EINVAL;54 if (c < ' ' || c == ':' || c == '\\')55 return -EINVAL;56 /*57 * 0xE5 is legal as a first character, but we must substitute58 * 0x05 because 0xE5 marks deleted files. Yes, DOS really59 * does this.60 * It seems that Microsoft hacked DOS to support non-US61 * characters after the 0xE5 character was already in use to62 * mark deleted files.63 */64 if ((res == walk) && (c == 0xE5))65 c = 0x05;66 if (c == '.')67 break;68 space = (c == ' ');69 *walk = (!opts->nocase && c >= 'a' && c <= 'z') ? c - 32 : c;70 }71 if (space)72 return -EINVAL;73 if (opts->name_check == 's' && len && c != '.') {74 c = *name++;75 len--;76 if (c != '.')77 return -EINVAL;78 }79 while (c != '.' && len--)80 c = *name++;81 if (c == '.') {82 while (walk - res < 8)83 *walk++ = ' ';84 while (len > 0 && walk - res < MSDOS_NAME) {85 c = *name++;86 len--;87 if (opts->name_check != 'r' && strchr(bad_chars, c))88 return -EINVAL;89 if (opts->name_check == 's' &&90 strchr(bad_if_strict, c))91 return -EINVAL;92 if (c < ' ' || c == ':' || c == '\\')93 return -EINVAL;94 if (c == '.') {95 if (opts->name_check == 's')96 return -EINVAL;97 break;98 }99 if (c >= 'A' && c <= 'Z' && opts->name_check == 's')100 return -EINVAL;101 space = c == ' ';102 if (!opts->nocase && c >= 'a' && c <= 'z')103 *walk++ = c - 32;104 else105 *walk++ = c;106 }107 if (space)108 return -EINVAL;109 if (opts->name_check == 's' && len)110 return -EINVAL;111 }112 while (walk - res < MSDOS_NAME)113 *walk++ = ' ';114 115 return 0;116}117 118/***** Locates a directory entry. Uses unformatted name. */119static int msdos_find(struct inode *dir, const unsigned char *name, int len,120 struct fat_slot_info *sinfo)121{122 struct msdos_sb_info *sbi = MSDOS_SB(dir->i_sb);123 unsigned char msdos_name[MSDOS_NAME];124 int err;125 126 err = msdos_format_name(name, len, msdos_name, &sbi->options);127 if (err)128 return -ENOENT;129 130 err = fat_scan(dir, msdos_name, sinfo);131 if (!err && sbi->options.dotsOK) {132 if (name[0] == '.') {133 if (!(sinfo->de->attr & ATTR_HIDDEN))134 err = -ENOENT;135 } else {136 if (sinfo->de->attr & ATTR_HIDDEN)137 err = -ENOENT;138 }139 if (err)140 brelse(sinfo->bh);141 }142 return err;143}144 145/*146 * Compute the hash for the msdos name corresponding to the dentry.147 * Note: if the name is invalid, we leave the hash code unchanged so148 * that the existing dentry can be used. The msdos fs routines will149 * return ENOENT or EINVAL as appropriate.150 */151static int msdos_hash(const struct dentry *dentry, struct qstr *qstr)152{153 struct fat_mount_options *options = &MSDOS_SB(dentry->d_sb)->options;154 unsigned char msdos_name[MSDOS_NAME];155 int error;156 157 error = msdos_format_name(qstr->name, qstr->len, msdos_name, options);158 if (!error)159 qstr->hash = full_name_hash(dentry, msdos_name, MSDOS_NAME);160 return 0;161}162 163/*164 * Compare two msdos names. If either of the names are invalid,165 * we fall back to doing the standard name comparison.166 */167static int msdos_cmp(const struct dentry *dentry,168 unsigned int len, const char *str, const struct qstr *name)169{170 struct fat_mount_options *options = &MSDOS_SB(dentry->d_sb)->options;171 unsigned char a_msdos_name[MSDOS_NAME], b_msdos_name[MSDOS_NAME];172 int error;173 174 error = msdos_format_name(name->name, name->len, a_msdos_name, options);175 if (error)176 goto old_compare;177 error = msdos_format_name(str, len, b_msdos_name, options);178 if (error)179 goto old_compare;180 error = memcmp(a_msdos_name, b_msdos_name, MSDOS_NAME);181out:182 return error;183 184old_compare:185 error = 1;186 if (name->len == len)187 error = memcmp(name->name, str, len);188 goto out;189}190 191static const struct dentry_operations msdos_dentry_operations = {192 .d_hash = msdos_hash,193 .d_compare = msdos_cmp,194};195 196/*197 * AV. Wrappers for FAT sb operations. Is it wise?198 */199 200/***** Get inode using directory and name */201static struct dentry *msdos_lookup(struct inode *dir, struct dentry *dentry,202 unsigned int flags)203{204 struct super_block *sb = dir->i_sb;205 struct fat_slot_info sinfo;206 struct inode *inode;207 int err;208 209 mutex_lock(&MSDOS_SB(sb)->s_lock);210 err = msdos_find(dir, dentry->d_name.name, dentry->d_name.len, &sinfo);211 switch (err) {212 case -ENOENT:213 inode = NULL;214 break;215 case 0:216 inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos);217 brelse(sinfo.bh);218 break;219 default:220 inode = ERR_PTR(err);221 }222 mutex_unlock(&MSDOS_SB(sb)->s_lock);223 return d_splice_alias(inode, dentry);224}225 226/***** Creates a directory entry (name is already formatted). */227static int msdos_add_entry(struct inode *dir, const unsigned char *name,228 int is_dir, int is_hid, int cluster,229 struct timespec64 *ts, struct fat_slot_info *sinfo)230{231 struct msdos_sb_info *sbi = MSDOS_SB(dir->i_sb);232 struct msdos_dir_entry de;233 __le16 time, date;234 int err;235 236 memcpy(de.name, name, MSDOS_NAME);237 de.attr = is_dir ? ATTR_DIR : ATTR_ARCH;238 if (is_hid)239 de.attr |= ATTR_HIDDEN;240 de.lcase = 0;241 fat_time_unix2fat(sbi, ts, &time, &date, NULL);242 de.cdate = de.adate = 0;243 de.ctime = 0;244 de.ctime_cs = 0;245 de.time = time;246 de.date = date;247 fat_set_start(&de, cluster);248 de.size = 0;249 250 err = fat_add_entries(dir, &de, 1, sinfo);251 if (err)252 return err;253 254 fat_truncate_time(dir, ts, S_CTIME|S_MTIME);255 if (IS_DIRSYNC(dir))256 (void)fat_sync_inode(dir);257 else258 mark_inode_dirty(dir);259 260 return 0;261}262 263/***** Create a file */264static int msdos_create(struct mnt_idmap *idmap, struct inode *dir,265 struct dentry *dentry, umode_t mode, bool excl)266{267 struct super_block *sb = dir->i_sb;268 struct inode *inode = NULL;269 struct fat_slot_info sinfo;270 struct timespec64 ts;271 unsigned char msdos_name[MSDOS_NAME];272 int err, is_hid;273 274 mutex_lock(&MSDOS_SB(sb)->s_lock);275 276 err = msdos_format_name(dentry->d_name.name, dentry->d_name.len,277 msdos_name, &MSDOS_SB(sb)->options);278 if (err)279 goto out;280 is_hid = (dentry->d_name.name[0] == '.') && (msdos_name[0] != '.');281 /* Have to do it due to foo vs. .foo conflicts */282 if (!fat_scan(dir, msdos_name, &sinfo)) {283 brelse(sinfo.bh);284 err = -EINVAL;285 goto out;286 }287 288 ts = current_time(dir);289 err = msdos_add_entry(dir, msdos_name, 0, is_hid, 0, &ts, &sinfo);290 if (err)291 goto out;292 inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos);293 brelse(sinfo.bh);294 if (IS_ERR(inode)) {295 err = PTR_ERR(inode);296 goto out;297 }298 fat_truncate_time(inode, &ts, S_ATIME|S_CTIME|S_MTIME);299 /* timestamp is already written, so mark_inode_dirty() is unneeded. */300 301 d_instantiate(dentry, inode);302out:303 mutex_unlock(&MSDOS_SB(sb)->s_lock);304 if (!err)305 err = fat_flush_inodes(sb, dir, inode);306 return err;307}308 309/***** Remove a directory */310static int msdos_rmdir(struct inode *dir, struct dentry *dentry)311{312 struct super_block *sb = dir->i_sb;313 struct inode *inode = d_inode(dentry);314 struct fat_slot_info sinfo;315 int err;316 317 mutex_lock(&MSDOS_SB(sb)->s_lock);318 err = fat_dir_empty(inode);319 if (err)320 goto out;321 err = msdos_find(dir, dentry->d_name.name, dentry->d_name.len, &sinfo);322 if (err)323 goto out;324 325 err = fat_remove_entries(dir, &sinfo); /* and releases bh */326 if (err)327 goto out;328 drop_nlink(dir);329 330 clear_nlink(inode);331 fat_truncate_time(inode, NULL, S_CTIME);332 fat_detach(inode);333out:334 mutex_unlock(&MSDOS_SB(sb)->s_lock);335 if (!err)336 err = fat_flush_inodes(sb, dir, inode);337 338 return err;339}340 341/***** Make a directory */342static int msdos_mkdir(struct mnt_idmap *idmap, struct inode *dir,343 struct dentry *dentry, umode_t mode)344{345 struct super_block *sb = dir->i_sb;346 struct fat_slot_info sinfo;347 struct inode *inode;348 unsigned char msdos_name[MSDOS_NAME];349 struct timespec64 ts;350 int err, is_hid, cluster;351 352 mutex_lock(&MSDOS_SB(sb)->s_lock);353 354 err = msdos_format_name(dentry->d_name.name, dentry->d_name.len,355 msdos_name, &MSDOS_SB(sb)->options);356 if (err)357 goto out;358 is_hid = (dentry->d_name.name[0] == '.') && (msdos_name[0] != '.');359 /* foo vs .foo situation */360 if (!fat_scan(dir, msdos_name, &sinfo)) {361 brelse(sinfo.bh);362 err = -EINVAL;363 goto out;364 }365 366 ts = current_time(dir);367 cluster = fat_alloc_new_dir(dir, &ts);368 if (cluster < 0) {369 err = cluster;370 goto out;371 }372 err = msdos_add_entry(dir, msdos_name, 1, is_hid, cluster, &ts, &sinfo);373 if (err)374 goto out_free;375 inc_nlink(dir);376 377 inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos);378 brelse(sinfo.bh);379 if (IS_ERR(inode)) {380 err = PTR_ERR(inode);381 /* the directory was completed, just return a error */382 goto out;383 }384 set_nlink(inode, 2);385 fat_truncate_time(inode, &ts, S_ATIME|S_CTIME|S_MTIME);386 /* timestamp is already written, so mark_inode_dirty() is unneeded. */387 388 d_instantiate(dentry, inode);389 390 mutex_unlock(&MSDOS_SB(sb)->s_lock);391 fat_flush_inodes(sb, dir, inode);392 return 0;393 394out_free:395 fat_free_clusters(dir, cluster);396out:397 mutex_unlock(&MSDOS_SB(sb)->s_lock);398 return err;399}400 401/***** Unlink a file */402static int msdos_unlink(struct inode *dir, struct dentry *dentry)403{404 struct inode *inode = d_inode(dentry);405 struct super_block *sb = inode->i_sb;406 struct fat_slot_info sinfo;407 int err;408 409 mutex_lock(&MSDOS_SB(sb)->s_lock);410 err = msdos_find(dir, dentry->d_name.name, dentry->d_name.len, &sinfo);411 if (err)412 goto out;413 414 err = fat_remove_entries(dir, &sinfo); /* and releases bh */415 if (err)416 goto out;417 clear_nlink(inode);418 fat_truncate_time(inode, NULL, S_CTIME);419 fat_detach(inode);420out:421 mutex_unlock(&MSDOS_SB(sb)->s_lock);422 if (!err)423 err = fat_flush_inodes(sb, dir, inode);424 425 return err;426}427 428static int do_msdos_rename(struct inode *old_dir, unsigned char *old_name,429 struct dentry *old_dentry,430 struct inode *new_dir, unsigned char *new_name,431 struct dentry *new_dentry, int is_hid)432{433 struct buffer_head *dotdot_bh;434 struct msdos_dir_entry *dotdot_de;435 struct inode *old_inode, *new_inode;436 struct fat_slot_info old_sinfo, sinfo;437 struct timespec64 ts;438 loff_t new_i_pos;439 int err, old_attrs, is_dir, update_dotdot, corrupt = 0;440 441 old_sinfo.bh = sinfo.bh = dotdot_bh = NULL;442 old_inode = d_inode(old_dentry);443 new_inode = d_inode(new_dentry);444 445 err = fat_scan(old_dir, old_name, &old_sinfo);446 if (err) {447 err = -EIO;448 goto out;449 }450 451 is_dir = S_ISDIR(old_inode->i_mode);452 update_dotdot = (is_dir && old_dir != new_dir);453 if (update_dotdot) {454 if (fat_get_dotdot_entry(old_inode, &dotdot_bh, &dotdot_de)) {455 err = -EIO;456 goto out;457 }458 }459 460 old_attrs = MSDOS_I(old_inode)->i_attrs;461 err = fat_scan(new_dir, new_name, &sinfo);462 if (!err) {463 if (!new_inode) {464 /* "foo" -> ".foo" case. just change the ATTR_HIDDEN */465 if (sinfo.de != old_sinfo.de) {466 err = -EINVAL;467 goto out;468 }469 if (is_hid)470 MSDOS_I(old_inode)->i_attrs |= ATTR_HIDDEN;471 else472 MSDOS_I(old_inode)->i_attrs &= ~ATTR_HIDDEN;473 if (IS_DIRSYNC(old_dir)) {474 err = fat_sync_inode(old_inode);475 if (err) {476 MSDOS_I(old_inode)->i_attrs = old_attrs;477 goto out;478 }479 } else480 mark_inode_dirty(old_inode);481 482 inode_inc_iversion(old_dir);483 fat_truncate_time(old_dir, NULL, S_CTIME|S_MTIME);484 if (IS_DIRSYNC(old_dir))485 (void)fat_sync_inode(old_dir);486 else487 mark_inode_dirty(old_dir);488 goto out;489 }490 }491 492 ts = current_time(old_inode);493 if (new_inode) {494 if (err)495 goto out;496 if (is_dir) {497 err = fat_dir_empty(new_inode);498 if (err)499 goto out;500 }501 new_i_pos = MSDOS_I(new_inode)->i_pos;502 fat_detach(new_inode);503 } else {504 err = msdos_add_entry(new_dir, new_name, is_dir, is_hid, 0,505 &ts, &sinfo);506 if (err)507 goto out;508 new_i_pos = sinfo.i_pos;509 }510 inode_inc_iversion(new_dir);511 512 fat_detach(old_inode);513 fat_attach(old_inode, new_i_pos);514 if (is_hid)515 MSDOS_I(old_inode)->i_attrs |= ATTR_HIDDEN;516 else517 MSDOS_I(old_inode)->i_attrs &= ~ATTR_HIDDEN;518 if (IS_DIRSYNC(new_dir)) {519 err = fat_sync_inode(old_inode);520 if (err)521 goto error_inode;522 } else523 mark_inode_dirty(old_inode);524 525 if (update_dotdot) {526 fat_set_start(dotdot_de, MSDOS_I(new_dir)->i_logstart);527 mark_buffer_dirty_inode(dotdot_bh, old_inode);528 if (IS_DIRSYNC(new_dir)) {529 err = sync_dirty_buffer(dotdot_bh);530 if (err)531 goto error_dotdot;532 }533 drop_nlink(old_dir);534 if (!new_inode)535 inc_nlink(new_dir);536 }537 538 err = fat_remove_entries(old_dir, &old_sinfo); /* and releases bh */539 old_sinfo.bh = NULL;540 if (err)541 goto error_dotdot;542 inode_inc_iversion(old_dir);543 fat_truncate_time(old_dir, &ts, S_CTIME|S_MTIME);544 if (IS_DIRSYNC(old_dir))545 (void)fat_sync_inode(old_dir);546 else547 mark_inode_dirty(old_dir);548 549 if (new_inode) {550 drop_nlink(new_inode);551 if (is_dir)552 drop_nlink(new_inode);553 fat_truncate_time(new_inode, &ts, S_CTIME);554 }555out:556 brelse(sinfo.bh);557 brelse(dotdot_bh);558 brelse(old_sinfo.bh);559 return err;560 561error_dotdot:562 /* data cluster is shared, serious corruption */563 corrupt = 1;564 565 if (update_dotdot) {566 fat_set_start(dotdot_de, MSDOS_I(old_dir)->i_logstart);567 mark_buffer_dirty_inode(dotdot_bh, old_inode);568 corrupt |= sync_dirty_buffer(dotdot_bh);569 }570error_inode:571 fat_detach(old_inode);572 fat_attach(old_inode, old_sinfo.i_pos);573 MSDOS_I(old_inode)->i_attrs = old_attrs;574 if (new_inode) {575 fat_attach(new_inode, new_i_pos);576 if (corrupt)577 corrupt |= fat_sync_inode(new_inode);578 } else {579 /*580 * If new entry was not sharing the data cluster, it581 * shouldn't be serious corruption.582 */583 int err2 = fat_remove_entries(new_dir, &sinfo);584 if (corrupt)585 corrupt |= err2;586 sinfo.bh = NULL;587 }588 if (corrupt < 0) {589 fat_fs_error(new_dir->i_sb,590 "%s: Filesystem corrupted (i_pos %lld)",591 __func__, sinfo.i_pos);592 }593 goto out;594}595 596/***** Rename, a wrapper for rename_same_dir & rename_diff_dir */597static int msdos_rename(struct mnt_idmap *idmap,598 struct inode *old_dir, struct dentry *old_dentry,599 struct inode *new_dir, struct dentry *new_dentry,600 unsigned int flags)601{602 struct super_block *sb = old_dir->i_sb;603 unsigned char old_msdos_name[MSDOS_NAME], new_msdos_name[MSDOS_NAME];604 int err, is_hid;605 606 if (flags & ~RENAME_NOREPLACE)607 return -EINVAL;608 609 mutex_lock(&MSDOS_SB(sb)->s_lock);610 611 err = msdos_format_name(old_dentry->d_name.name,612 old_dentry->d_name.len, old_msdos_name,613 &MSDOS_SB(old_dir->i_sb)->options);614 if (err)615 goto out;616 err = msdos_format_name(new_dentry->d_name.name,617 new_dentry->d_name.len, new_msdos_name,618 &MSDOS_SB(new_dir->i_sb)->options);619 if (err)620 goto out;621 622 is_hid =623 (new_dentry->d_name.name[0] == '.') && (new_msdos_name[0] != '.');624 625 err = do_msdos_rename(old_dir, old_msdos_name, old_dentry,626 new_dir, new_msdos_name, new_dentry, is_hid);627out:628 mutex_unlock(&MSDOS_SB(sb)->s_lock);629 if (!err)630 err = fat_flush_inodes(sb, old_dir, new_dir);631 return err;632}633 634static const struct inode_operations msdos_dir_inode_operations = {635 .create = msdos_create,636 .lookup = msdos_lookup,637 .unlink = msdos_unlink,638 .mkdir = msdos_mkdir,639 .rmdir = msdos_rmdir,640 .rename = msdos_rename,641 .setattr = fat_setattr,642 .getattr = fat_getattr,643 .update_time = fat_update_time,644};645 646static void setup(struct super_block *sb)647{648 MSDOS_SB(sb)->dir_ops = &msdos_dir_inode_operations;649 sb->s_d_op = &msdos_dentry_operations;650 sb->s_flags |= SB_NOATIME;651}652 653static int msdos_fill_super(struct super_block *sb, struct fs_context *fc)654{655 return fat_fill_super(sb, fc, setup);656}657 658static int msdos_get_tree(struct fs_context *fc)659{660 return get_tree_bdev(fc, msdos_fill_super);661}662 663static int msdos_parse_param(struct fs_context *fc, struct fs_parameter *param)664{665 return fat_parse_param(fc, param, false);666}667 668static const struct fs_context_operations msdos_context_ops = {669 .parse_param = msdos_parse_param,670 .get_tree = msdos_get_tree,671 .reconfigure = fat_reconfigure,672 .free = fat_free_fc,673};674 675static int msdos_init_fs_context(struct fs_context *fc)676{677 int err;678 679 /* Initialize with is_vfat == false */680 err = fat_init_fs_context(fc, false);681 if (err)682 return err;683 684 fc->ops = &msdos_context_ops;685 return 0;686}687 688static struct file_system_type msdos_fs_type = {689 .owner = THIS_MODULE,690 .name = "msdos",691 .kill_sb = kill_block_super,692 .fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP,693 .init_fs_context = msdos_init_fs_context,694 .parameters = fat_param_spec,695};696MODULE_ALIAS_FS("msdos");697 698static int __init init_msdos_fs(void)699{700 return register_filesystem(&msdos_fs_type);701}702 703static void __exit exit_msdos_fs(void)704{705 unregister_filesystem(&msdos_fs_type);706}707 708MODULE_LICENSE("GPL");709MODULE_AUTHOR("Werner Almesberger");710MODULE_DESCRIPTION("MS-DOS filesystem support");711 712module_init(init_msdos_fs)713module_exit(exit_msdos_fs)714