162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * linux/fs/msdos/namei.c 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Written 1992,1993 by Werner Almesberger 662306a36Sopenharmony_ci * Hidden files 1995 by Albert Cahalan <albert@ccs.neu.edu> <adc@coe.neu.edu> 762306a36Sopenharmony_ci * Rewritten for constant inumbers 1999 by Al Viro 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/module.h> 1162306a36Sopenharmony_ci#include <linux/iversion.h> 1262306a36Sopenharmony_ci#include "fat.h" 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci/* Characters that are undesirable in an MS-DOS file name */ 1562306a36Sopenharmony_cistatic unsigned char bad_chars[] = "*?<>|\""; 1662306a36Sopenharmony_cistatic unsigned char bad_if_strict[] = "+=,; "; 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci/***** Formats an MS-DOS file name. Rejects invalid names. */ 1962306a36Sopenharmony_cistatic int msdos_format_name(const unsigned char *name, int len, 2062306a36Sopenharmony_ci unsigned char *res, struct fat_mount_options *opts) 2162306a36Sopenharmony_ci /* 2262306a36Sopenharmony_ci * name is the proposed name, len is its length, res is 2362306a36Sopenharmony_ci * the resulting name, opts->name_check is either (r)elaxed, 2462306a36Sopenharmony_ci * (n)ormal or (s)trict, opts->dotsOK allows dots at the 2562306a36Sopenharmony_ci * beginning of name (for hidden files) 2662306a36Sopenharmony_ci */ 2762306a36Sopenharmony_ci{ 2862306a36Sopenharmony_ci unsigned char *walk; 2962306a36Sopenharmony_ci unsigned char c; 3062306a36Sopenharmony_ci int space; 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci if (name[0] == '.') { /* dotfile because . and .. already done */ 3362306a36Sopenharmony_ci if (opts->dotsOK) { 3462306a36Sopenharmony_ci /* Get rid of dot - test for it elsewhere */ 3562306a36Sopenharmony_ci name++; 3662306a36Sopenharmony_ci len--; 3762306a36Sopenharmony_ci } else 3862306a36Sopenharmony_ci return -EINVAL; 3962306a36Sopenharmony_ci } 4062306a36Sopenharmony_ci /* 4162306a36Sopenharmony_ci * disallow names that _really_ start with a dot 4262306a36Sopenharmony_ci */ 4362306a36Sopenharmony_ci space = 1; 4462306a36Sopenharmony_ci c = 0; 4562306a36Sopenharmony_ci for (walk = res; len && walk - res < 8; walk++) { 4662306a36Sopenharmony_ci c = *name++; 4762306a36Sopenharmony_ci len--; 4862306a36Sopenharmony_ci if (opts->name_check != 'r' && strchr(bad_chars, c)) 4962306a36Sopenharmony_ci return -EINVAL; 5062306a36Sopenharmony_ci if (opts->name_check == 's' && strchr(bad_if_strict, c)) 5162306a36Sopenharmony_ci return -EINVAL; 5262306a36Sopenharmony_ci if (c >= 'A' && c <= 'Z' && opts->name_check == 's') 5362306a36Sopenharmony_ci return -EINVAL; 5462306a36Sopenharmony_ci if (c < ' ' || c == ':' || c == '\\') 5562306a36Sopenharmony_ci return -EINVAL; 5662306a36Sopenharmony_ci /* 5762306a36Sopenharmony_ci * 0xE5 is legal as a first character, but we must substitute 5862306a36Sopenharmony_ci * 0x05 because 0xE5 marks deleted files. Yes, DOS really 5962306a36Sopenharmony_ci * does this. 6062306a36Sopenharmony_ci * It seems that Microsoft hacked DOS to support non-US 6162306a36Sopenharmony_ci * characters after the 0xE5 character was already in use to 6262306a36Sopenharmony_ci * mark deleted files. 6362306a36Sopenharmony_ci */ 6462306a36Sopenharmony_ci if ((res == walk) && (c == 0xE5)) 6562306a36Sopenharmony_ci c = 0x05; 6662306a36Sopenharmony_ci if (c == '.') 6762306a36Sopenharmony_ci break; 6862306a36Sopenharmony_ci space = (c == ' '); 6962306a36Sopenharmony_ci *walk = (!opts->nocase && c >= 'a' && c <= 'z') ? c - 32 : c; 7062306a36Sopenharmony_ci } 7162306a36Sopenharmony_ci if (space) 7262306a36Sopenharmony_ci return -EINVAL; 7362306a36Sopenharmony_ci if (opts->name_check == 's' && len && c != '.') { 7462306a36Sopenharmony_ci c = *name++; 7562306a36Sopenharmony_ci len--; 7662306a36Sopenharmony_ci if (c != '.') 7762306a36Sopenharmony_ci return -EINVAL; 7862306a36Sopenharmony_ci } 7962306a36Sopenharmony_ci while (c != '.' && len--) 8062306a36Sopenharmony_ci c = *name++; 8162306a36Sopenharmony_ci if (c == '.') { 8262306a36Sopenharmony_ci while (walk - res < 8) 8362306a36Sopenharmony_ci *walk++ = ' '; 8462306a36Sopenharmony_ci while (len > 0 && walk - res < MSDOS_NAME) { 8562306a36Sopenharmony_ci c = *name++; 8662306a36Sopenharmony_ci len--; 8762306a36Sopenharmony_ci if (opts->name_check != 'r' && strchr(bad_chars, c)) 8862306a36Sopenharmony_ci return -EINVAL; 8962306a36Sopenharmony_ci if (opts->name_check == 's' && 9062306a36Sopenharmony_ci strchr(bad_if_strict, c)) 9162306a36Sopenharmony_ci return -EINVAL; 9262306a36Sopenharmony_ci if (c < ' ' || c == ':' || c == '\\') 9362306a36Sopenharmony_ci return -EINVAL; 9462306a36Sopenharmony_ci if (c == '.') { 9562306a36Sopenharmony_ci if (opts->name_check == 's') 9662306a36Sopenharmony_ci return -EINVAL; 9762306a36Sopenharmony_ci break; 9862306a36Sopenharmony_ci } 9962306a36Sopenharmony_ci if (c >= 'A' && c <= 'Z' && opts->name_check == 's') 10062306a36Sopenharmony_ci return -EINVAL; 10162306a36Sopenharmony_ci space = c == ' '; 10262306a36Sopenharmony_ci if (!opts->nocase && c >= 'a' && c <= 'z') 10362306a36Sopenharmony_ci *walk++ = c - 32; 10462306a36Sopenharmony_ci else 10562306a36Sopenharmony_ci *walk++ = c; 10662306a36Sopenharmony_ci } 10762306a36Sopenharmony_ci if (space) 10862306a36Sopenharmony_ci return -EINVAL; 10962306a36Sopenharmony_ci if (opts->name_check == 's' && len) 11062306a36Sopenharmony_ci return -EINVAL; 11162306a36Sopenharmony_ci } 11262306a36Sopenharmony_ci while (walk - res < MSDOS_NAME) 11362306a36Sopenharmony_ci *walk++ = ' '; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci return 0; 11662306a36Sopenharmony_ci} 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci/***** Locates a directory entry. Uses unformatted name. */ 11962306a36Sopenharmony_cistatic int msdos_find(struct inode *dir, const unsigned char *name, int len, 12062306a36Sopenharmony_ci struct fat_slot_info *sinfo) 12162306a36Sopenharmony_ci{ 12262306a36Sopenharmony_ci struct msdos_sb_info *sbi = MSDOS_SB(dir->i_sb); 12362306a36Sopenharmony_ci unsigned char msdos_name[MSDOS_NAME]; 12462306a36Sopenharmony_ci int err; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci err = msdos_format_name(name, len, msdos_name, &sbi->options); 12762306a36Sopenharmony_ci if (err) 12862306a36Sopenharmony_ci return -ENOENT; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci err = fat_scan(dir, msdos_name, sinfo); 13162306a36Sopenharmony_ci if (!err && sbi->options.dotsOK) { 13262306a36Sopenharmony_ci if (name[0] == '.') { 13362306a36Sopenharmony_ci if (!(sinfo->de->attr & ATTR_HIDDEN)) 13462306a36Sopenharmony_ci err = -ENOENT; 13562306a36Sopenharmony_ci } else { 13662306a36Sopenharmony_ci if (sinfo->de->attr & ATTR_HIDDEN) 13762306a36Sopenharmony_ci err = -ENOENT; 13862306a36Sopenharmony_ci } 13962306a36Sopenharmony_ci if (err) 14062306a36Sopenharmony_ci brelse(sinfo->bh); 14162306a36Sopenharmony_ci } 14262306a36Sopenharmony_ci return err; 14362306a36Sopenharmony_ci} 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci/* 14662306a36Sopenharmony_ci * Compute the hash for the msdos name corresponding to the dentry. 14762306a36Sopenharmony_ci * Note: if the name is invalid, we leave the hash code unchanged so 14862306a36Sopenharmony_ci * that the existing dentry can be used. The msdos fs routines will 14962306a36Sopenharmony_ci * return ENOENT or EINVAL as appropriate. 15062306a36Sopenharmony_ci */ 15162306a36Sopenharmony_cistatic int msdos_hash(const struct dentry *dentry, struct qstr *qstr) 15262306a36Sopenharmony_ci{ 15362306a36Sopenharmony_ci struct fat_mount_options *options = &MSDOS_SB(dentry->d_sb)->options; 15462306a36Sopenharmony_ci unsigned char msdos_name[MSDOS_NAME]; 15562306a36Sopenharmony_ci int error; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci error = msdos_format_name(qstr->name, qstr->len, msdos_name, options); 15862306a36Sopenharmony_ci if (!error) 15962306a36Sopenharmony_ci qstr->hash = full_name_hash(dentry, msdos_name, MSDOS_NAME); 16062306a36Sopenharmony_ci return 0; 16162306a36Sopenharmony_ci} 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci/* 16462306a36Sopenharmony_ci * Compare two msdos names. If either of the names are invalid, 16562306a36Sopenharmony_ci * we fall back to doing the standard name comparison. 16662306a36Sopenharmony_ci */ 16762306a36Sopenharmony_cistatic int msdos_cmp(const struct dentry *dentry, 16862306a36Sopenharmony_ci unsigned int len, const char *str, const struct qstr *name) 16962306a36Sopenharmony_ci{ 17062306a36Sopenharmony_ci struct fat_mount_options *options = &MSDOS_SB(dentry->d_sb)->options; 17162306a36Sopenharmony_ci unsigned char a_msdos_name[MSDOS_NAME], b_msdos_name[MSDOS_NAME]; 17262306a36Sopenharmony_ci int error; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci error = msdos_format_name(name->name, name->len, a_msdos_name, options); 17562306a36Sopenharmony_ci if (error) 17662306a36Sopenharmony_ci goto old_compare; 17762306a36Sopenharmony_ci error = msdos_format_name(str, len, b_msdos_name, options); 17862306a36Sopenharmony_ci if (error) 17962306a36Sopenharmony_ci goto old_compare; 18062306a36Sopenharmony_ci error = memcmp(a_msdos_name, b_msdos_name, MSDOS_NAME); 18162306a36Sopenharmony_ciout: 18262306a36Sopenharmony_ci return error; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ciold_compare: 18562306a36Sopenharmony_ci error = 1; 18662306a36Sopenharmony_ci if (name->len == len) 18762306a36Sopenharmony_ci error = memcmp(name->name, str, len); 18862306a36Sopenharmony_ci goto out; 18962306a36Sopenharmony_ci} 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_cistatic const struct dentry_operations msdos_dentry_operations = { 19262306a36Sopenharmony_ci .d_hash = msdos_hash, 19362306a36Sopenharmony_ci .d_compare = msdos_cmp, 19462306a36Sopenharmony_ci}; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci/* 19762306a36Sopenharmony_ci * AV. Wrappers for FAT sb operations. Is it wise? 19862306a36Sopenharmony_ci */ 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci/***** Get inode using directory and name */ 20162306a36Sopenharmony_cistatic struct dentry *msdos_lookup(struct inode *dir, struct dentry *dentry, 20262306a36Sopenharmony_ci unsigned int flags) 20362306a36Sopenharmony_ci{ 20462306a36Sopenharmony_ci struct super_block *sb = dir->i_sb; 20562306a36Sopenharmony_ci struct fat_slot_info sinfo; 20662306a36Sopenharmony_ci struct inode *inode; 20762306a36Sopenharmony_ci int err; 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci mutex_lock(&MSDOS_SB(sb)->s_lock); 21062306a36Sopenharmony_ci err = msdos_find(dir, dentry->d_name.name, dentry->d_name.len, &sinfo); 21162306a36Sopenharmony_ci switch (err) { 21262306a36Sopenharmony_ci case -ENOENT: 21362306a36Sopenharmony_ci inode = NULL; 21462306a36Sopenharmony_ci break; 21562306a36Sopenharmony_ci case 0: 21662306a36Sopenharmony_ci inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos); 21762306a36Sopenharmony_ci brelse(sinfo.bh); 21862306a36Sopenharmony_ci break; 21962306a36Sopenharmony_ci default: 22062306a36Sopenharmony_ci inode = ERR_PTR(err); 22162306a36Sopenharmony_ci } 22262306a36Sopenharmony_ci mutex_unlock(&MSDOS_SB(sb)->s_lock); 22362306a36Sopenharmony_ci return d_splice_alias(inode, dentry); 22462306a36Sopenharmony_ci} 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci/***** Creates a directory entry (name is already formatted). */ 22762306a36Sopenharmony_cistatic int msdos_add_entry(struct inode *dir, const unsigned char *name, 22862306a36Sopenharmony_ci int is_dir, int is_hid, int cluster, 22962306a36Sopenharmony_ci struct timespec64 *ts, struct fat_slot_info *sinfo) 23062306a36Sopenharmony_ci{ 23162306a36Sopenharmony_ci struct msdos_sb_info *sbi = MSDOS_SB(dir->i_sb); 23262306a36Sopenharmony_ci struct msdos_dir_entry de; 23362306a36Sopenharmony_ci __le16 time, date; 23462306a36Sopenharmony_ci int err; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci memcpy(de.name, name, MSDOS_NAME); 23762306a36Sopenharmony_ci de.attr = is_dir ? ATTR_DIR : ATTR_ARCH; 23862306a36Sopenharmony_ci if (is_hid) 23962306a36Sopenharmony_ci de.attr |= ATTR_HIDDEN; 24062306a36Sopenharmony_ci de.lcase = 0; 24162306a36Sopenharmony_ci fat_time_unix2fat(sbi, ts, &time, &date, NULL); 24262306a36Sopenharmony_ci de.cdate = de.adate = 0; 24362306a36Sopenharmony_ci de.ctime = 0; 24462306a36Sopenharmony_ci de.ctime_cs = 0; 24562306a36Sopenharmony_ci de.time = time; 24662306a36Sopenharmony_ci de.date = date; 24762306a36Sopenharmony_ci fat_set_start(&de, cluster); 24862306a36Sopenharmony_ci de.size = 0; 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci err = fat_add_entries(dir, &de, 1, sinfo); 25162306a36Sopenharmony_ci if (err) 25262306a36Sopenharmony_ci return err; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci fat_truncate_time(dir, ts, S_CTIME|S_MTIME); 25562306a36Sopenharmony_ci if (IS_DIRSYNC(dir)) 25662306a36Sopenharmony_ci (void)fat_sync_inode(dir); 25762306a36Sopenharmony_ci else 25862306a36Sopenharmony_ci mark_inode_dirty(dir); 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci return 0; 26162306a36Sopenharmony_ci} 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci/***** Create a file */ 26462306a36Sopenharmony_cistatic int msdos_create(struct mnt_idmap *idmap, struct inode *dir, 26562306a36Sopenharmony_ci struct dentry *dentry, umode_t mode, bool excl) 26662306a36Sopenharmony_ci{ 26762306a36Sopenharmony_ci struct super_block *sb = dir->i_sb; 26862306a36Sopenharmony_ci struct inode *inode = NULL; 26962306a36Sopenharmony_ci struct fat_slot_info sinfo; 27062306a36Sopenharmony_ci struct timespec64 ts; 27162306a36Sopenharmony_ci unsigned char msdos_name[MSDOS_NAME]; 27262306a36Sopenharmony_ci int err, is_hid; 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci mutex_lock(&MSDOS_SB(sb)->s_lock); 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci err = msdos_format_name(dentry->d_name.name, dentry->d_name.len, 27762306a36Sopenharmony_ci msdos_name, &MSDOS_SB(sb)->options); 27862306a36Sopenharmony_ci if (err) 27962306a36Sopenharmony_ci goto out; 28062306a36Sopenharmony_ci is_hid = (dentry->d_name.name[0] == '.') && (msdos_name[0] != '.'); 28162306a36Sopenharmony_ci /* Have to do it due to foo vs. .foo conflicts */ 28262306a36Sopenharmony_ci if (!fat_scan(dir, msdos_name, &sinfo)) { 28362306a36Sopenharmony_ci brelse(sinfo.bh); 28462306a36Sopenharmony_ci err = -EINVAL; 28562306a36Sopenharmony_ci goto out; 28662306a36Sopenharmony_ci } 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci ts = current_time(dir); 28962306a36Sopenharmony_ci err = msdos_add_entry(dir, msdos_name, 0, is_hid, 0, &ts, &sinfo); 29062306a36Sopenharmony_ci if (err) 29162306a36Sopenharmony_ci goto out; 29262306a36Sopenharmony_ci inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos); 29362306a36Sopenharmony_ci brelse(sinfo.bh); 29462306a36Sopenharmony_ci if (IS_ERR(inode)) { 29562306a36Sopenharmony_ci err = PTR_ERR(inode); 29662306a36Sopenharmony_ci goto out; 29762306a36Sopenharmony_ci } 29862306a36Sopenharmony_ci fat_truncate_time(inode, &ts, S_ATIME|S_CTIME|S_MTIME); 29962306a36Sopenharmony_ci /* timestamp is already written, so mark_inode_dirty() is unneeded. */ 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci d_instantiate(dentry, inode); 30262306a36Sopenharmony_ciout: 30362306a36Sopenharmony_ci mutex_unlock(&MSDOS_SB(sb)->s_lock); 30462306a36Sopenharmony_ci if (!err) 30562306a36Sopenharmony_ci err = fat_flush_inodes(sb, dir, inode); 30662306a36Sopenharmony_ci return err; 30762306a36Sopenharmony_ci} 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci/***** Remove a directory */ 31062306a36Sopenharmony_cistatic int msdos_rmdir(struct inode *dir, struct dentry *dentry) 31162306a36Sopenharmony_ci{ 31262306a36Sopenharmony_ci struct super_block *sb = dir->i_sb; 31362306a36Sopenharmony_ci struct inode *inode = d_inode(dentry); 31462306a36Sopenharmony_ci struct fat_slot_info sinfo; 31562306a36Sopenharmony_ci int err; 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci mutex_lock(&MSDOS_SB(sb)->s_lock); 31862306a36Sopenharmony_ci err = fat_dir_empty(inode); 31962306a36Sopenharmony_ci if (err) 32062306a36Sopenharmony_ci goto out; 32162306a36Sopenharmony_ci err = msdos_find(dir, dentry->d_name.name, dentry->d_name.len, &sinfo); 32262306a36Sopenharmony_ci if (err) 32362306a36Sopenharmony_ci goto out; 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci err = fat_remove_entries(dir, &sinfo); /* and releases bh */ 32662306a36Sopenharmony_ci if (err) 32762306a36Sopenharmony_ci goto out; 32862306a36Sopenharmony_ci drop_nlink(dir); 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci clear_nlink(inode); 33162306a36Sopenharmony_ci fat_truncate_time(inode, NULL, S_CTIME); 33262306a36Sopenharmony_ci fat_detach(inode); 33362306a36Sopenharmony_ciout: 33462306a36Sopenharmony_ci mutex_unlock(&MSDOS_SB(sb)->s_lock); 33562306a36Sopenharmony_ci if (!err) 33662306a36Sopenharmony_ci err = fat_flush_inodes(sb, dir, inode); 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci return err; 33962306a36Sopenharmony_ci} 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci/***** Make a directory */ 34262306a36Sopenharmony_cistatic int msdos_mkdir(struct mnt_idmap *idmap, struct inode *dir, 34362306a36Sopenharmony_ci struct dentry *dentry, umode_t mode) 34462306a36Sopenharmony_ci{ 34562306a36Sopenharmony_ci struct super_block *sb = dir->i_sb; 34662306a36Sopenharmony_ci struct fat_slot_info sinfo; 34762306a36Sopenharmony_ci struct inode *inode; 34862306a36Sopenharmony_ci unsigned char msdos_name[MSDOS_NAME]; 34962306a36Sopenharmony_ci struct timespec64 ts; 35062306a36Sopenharmony_ci int err, is_hid, cluster; 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci mutex_lock(&MSDOS_SB(sb)->s_lock); 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci err = msdos_format_name(dentry->d_name.name, dentry->d_name.len, 35562306a36Sopenharmony_ci msdos_name, &MSDOS_SB(sb)->options); 35662306a36Sopenharmony_ci if (err) 35762306a36Sopenharmony_ci goto out; 35862306a36Sopenharmony_ci is_hid = (dentry->d_name.name[0] == '.') && (msdos_name[0] != '.'); 35962306a36Sopenharmony_ci /* foo vs .foo situation */ 36062306a36Sopenharmony_ci if (!fat_scan(dir, msdos_name, &sinfo)) { 36162306a36Sopenharmony_ci brelse(sinfo.bh); 36262306a36Sopenharmony_ci err = -EINVAL; 36362306a36Sopenharmony_ci goto out; 36462306a36Sopenharmony_ci } 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_ci ts = current_time(dir); 36762306a36Sopenharmony_ci cluster = fat_alloc_new_dir(dir, &ts); 36862306a36Sopenharmony_ci if (cluster < 0) { 36962306a36Sopenharmony_ci err = cluster; 37062306a36Sopenharmony_ci goto out; 37162306a36Sopenharmony_ci } 37262306a36Sopenharmony_ci err = msdos_add_entry(dir, msdos_name, 1, is_hid, cluster, &ts, &sinfo); 37362306a36Sopenharmony_ci if (err) 37462306a36Sopenharmony_ci goto out_free; 37562306a36Sopenharmony_ci inc_nlink(dir); 37662306a36Sopenharmony_ci 37762306a36Sopenharmony_ci inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos); 37862306a36Sopenharmony_ci brelse(sinfo.bh); 37962306a36Sopenharmony_ci if (IS_ERR(inode)) { 38062306a36Sopenharmony_ci err = PTR_ERR(inode); 38162306a36Sopenharmony_ci /* the directory was completed, just return a error */ 38262306a36Sopenharmony_ci goto out; 38362306a36Sopenharmony_ci } 38462306a36Sopenharmony_ci set_nlink(inode, 2); 38562306a36Sopenharmony_ci fat_truncate_time(inode, &ts, S_ATIME|S_CTIME|S_MTIME); 38662306a36Sopenharmony_ci /* timestamp is already written, so mark_inode_dirty() is unneeded. */ 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_ci d_instantiate(dentry, inode); 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci mutex_unlock(&MSDOS_SB(sb)->s_lock); 39162306a36Sopenharmony_ci fat_flush_inodes(sb, dir, inode); 39262306a36Sopenharmony_ci return 0; 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_ciout_free: 39562306a36Sopenharmony_ci fat_free_clusters(dir, cluster); 39662306a36Sopenharmony_ciout: 39762306a36Sopenharmony_ci mutex_unlock(&MSDOS_SB(sb)->s_lock); 39862306a36Sopenharmony_ci return err; 39962306a36Sopenharmony_ci} 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_ci/***** Unlink a file */ 40262306a36Sopenharmony_cistatic int msdos_unlink(struct inode *dir, struct dentry *dentry) 40362306a36Sopenharmony_ci{ 40462306a36Sopenharmony_ci struct inode *inode = d_inode(dentry); 40562306a36Sopenharmony_ci struct super_block *sb = inode->i_sb; 40662306a36Sopenharmony_ci struct fat_slot_info sinfo; 40762306a36Sopenharmony_ci int err; 40862306a36Sopenharmony_ci 40962306a36Sopenharmony_ci mutex_lock(&MSDOS_SB(sb)->s_lock); 41062306a36Sopenharmony_ci err = msdos_find(dir, dentry->d_name.name, dentry->d_name.len, &sinfo); 41162306a36Sopenharmony_ci if (err) 41262306a36Sopenharmony_ci goto out; 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci err = fat_remove_entries(dir, &sinfo); /* and releases bh */ 41562306a36Sopenharmony_ci if (err) 41662306a36Sopenharmony_ci goto out; 41762306a36Sopenharmony_ci clear_nlink(inode); 41862306a36Sopenharmony_ci fat_truncate_time(inode, NULL, S_CTIME); 41962306a36Sopenharmony_ci fat_detach(inode); 42062306a36Sopenharmony_ciout: 42162306a36Sopenharmony_ci mutex_unlock(&MSDOS_SB(sb)->s_lock); 42262306a36Sopenharmony_ci if (!err) 42362306a36Sopenharmony_ci err = fat_flush_inodes(sb, dir, inode); 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci return err; 42662306a36Sopenharmony_ci} 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_cistatic int do_msdos_rename(struct inode *old_dir, unsigned char *old_name, 42962306a36Sopenharmony_ci struct dentry *old_dentry, 43062306a36Sopenharmony_ci struct inode *new_dir, unsigned char *new_name, 43162306a36Sopenharmony_ci struct dentry *new_dentry, int is_hid) 43262306a36Sopenharmony_ci{ 43362306a36Sopenharmony_ci struct buffer_head *dotdot_bh; 43462306a36Sopenharmony_ci struct msdos_dir_entry *dotdot_de; 43562306a36Sopenharmony_ci struct inode *old_inode, *new_inode; 43662306a36Sopenharmony_ci struct fat_slot_info old_sinfo, sinfo; 43762306a36Sopenharmony_ci struct timespec64 ts; 43862306a36Sopenharmony_ci loff_t new_i_pos; 43962306a36Sopenharmony_ci int err, old_attrs, is_dir, update_dotdot, corrupt = 0; 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci old_sinfo.bh = sinfo.bh = dotdot_bh = NULL; 44262306a36Sopenharmony_ci old_inode = d_inode(old_dentry); 44362306a36Sopenharmony_ci new_inode = d_inode(new_dentry); 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_ci err = fat_scan(old_dir, old_name, &old_sinfo); 44662306a36Sopenharmony_ci if (err) { 44762306a36Sopenharmony_ci err = -EIO; 44862306a36Sopenharmony_ci goto out; 44962306a36Sopenharmony_ci } 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci is_dir = S_ISDIR(old_inode->i_mode); 45262306a36Sopenharmony_ci update_dotdot = (is_dir && old_dir != new_dir); 45362306a36Sopenharmony_ci if (update_dotdot) { 45462306a36Sopenharmony_ci if (fat_get_dotdot_entry(old_inode, &dotdot_bh, &dotdot_de)) { 45562306a36Sopenharmony_ci err = -EIO; 45662306a36Sopenharmony_ci goto out; 45762306a36Sopenharmony_ci } 45862306a36Sopenharmony_ci } 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_ci old_attrs = MSDOS_I(old_inode)->i_attrs; 46162306a36Sopenharmony_ci err = fat_scan(new_dir, new_name, &sinfo); 46262306a36Sopenharmony_ci if (!err) { 46362306a36Sopenharmony_ci if (!new_inode) { 46462306a36Sopenharmony_ci /* "foo" -> ".foo" case. just change the ATTR_HIDDEN */ 46562306a36Sopenharmony_ci if (sinfo.de != old_sinfo.de) { 46662306a36Sopenharmony_ci err = -EINVAL; 46762306a36Sopenharmony_ci goto out; 46862306a36Sopenharmony_ci } 46962306a36Sopenharmony_ci if (is_hid) 47062306a36Sopenharmony_ci MSDOS_I(old_inode)->i_attrs |= ATTR_HIDDEN; 47162306a36Sopenharmony_ci else 47262306a36Sopenharmony_ci MSDOS_I(old_inode)->i_attrs &= ~ATTR_HIDDEN; 47362306a36Sopenharmony_ci if (IS_DIRSYNC(old_dir)) { 47462306a36Sopenharmony_ci err = fat_sync_inode(old_inode); 47562306a36Sopenharmony_ci if (err) { 47662306a36Sopenharmony_ci MSDOS_I(old_inode)->i_attrs = old_attrs; 47762306a36Sopenharmony_ci goto out; 47862306a36Sopenharmony_ci } 47962306a36Sopenharmony_ci } else 48062306a36Sopenharmony_ci mark_inode_dirty(old_inode); 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ci inode_inc_iversion(old_dir); 48362306a36Sopenharmony_ci fat_truncate_time(old_dir, NULL, S_CTIME|S_MTIME); 48462306a36Sopenharmony_ci if (IS_DIRSYNC(old_dir)) 48562306a36Sopenharmony_ci (void)fat_sync_inode(old_dir); 48662306a36Sopenharmony_ci else 48762306a36Sopenharmony_ci mark_inode_dirty(old_dir); 48862306a36Sopenharmony_ci goto out; 48962306a36Sopenharmony_ci } 49062306a36Sopenharmony_ci } 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci ts = current_time(old_inode); 49362306a36Sopenharmony_ci if (new_inode) { 49462306a36Sopenharmony_ci if (err) 49562306a36Sopenharmony_ci goto out; 49662306a36Sopenharmony_ci if (is_dir) { 49762306a36Sopenharmony_ci err = fat_dir_empty(new_inode); 49862306a36Sopenharmony_ci if (err) 49962306a36Sopenharmony_ci goto out; 50062306a36Sopenharmony_ci } 50162306a36Sopenharmony_ci new_i_pos = MSDOS_I(new_inode)->i_pos; 50262306a36Sopenharmony_ci fat_detach(new_inode); 50362306a36Sopenharmony_ci } else { 50462306a36Sopenharmony_ci err = msdos_add_entry(new_dir, new_name, is_dir, is_hid, 0, 50562306a36Sopenharmony_ci &ts, &sinfo); 50662306a36Sopenharmony_ci if (err) 50762306a36Sopenharmony_ci goto out; 50862306a36Sopenharmony_ci new_i_pos = sinfo.i_pos; 50962306a36Sopenharmony_ci } 51062306a36Sopenharmony_ci inode_inc_iversion(new_dir); 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_ci fat_detach(old_inode); 51362306a36Sopenharmony_ci fat_attach(old_inode, new_i_pos); 51462306a36Sopenharmony_ci if (is_hid) 51562306a36Sopenharmony_ci MSDOS_I(old_inode)->i_attrs |= ATTR_HIDDEN; 51662306a36Sopenharmony_ci else 51762306a36Sopenharmony_ci MSDOS_I(old_inode)->i_attrs &= ~ATTR_HIDDEN; 51862306a36Sopenharmony_ci if (IS_DIRSYNC(new_dir)) { 51962306a36Sopenharmony_ci err = fat_sync_inode(old_inode); 52062306a36Sopenharmony_ci if (err) 52162306a36Sopenharmony_ci goto error_inode; 52262306a36Sopenharmony_ci } else 52362306a36Sopenharmony_ci mark_inode_dirty(old_inode); 52462306a36Sopenharmony_ci 52562306a36Sopenharmony_ci if (update_dotdot) { 52662306a36Sopenharmony_ci fat_set_start(dotdot_de, MSDOS_I(new_dir)->i_logstart); 52762306a36Sopenharmony_ci mark_buffer_dirty_inode(dotdot_bh, old_inode); 52862306a36Sopenharmony_ci if (IS_DIRSYNC(new_dir)) { 52962306a36Sopenharmony_ci err = sync_dirty_buffer(dotdot_bh); 53062306a36Sopenharmony_ci if (err) 53162306a36Sopenharmony_ci goto error_dotdot; 53262306a36Sopenharmony_ci } 53362306a36Sopenharmony_ci drop_nlink(old_dir); 53462306a36Sopenharmony_ci if (!new_inode) 53562306a36Sopenharmony_ci inc_nlink(new_dir); 53662306a36Sopenharmony_ci } 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_ci err = fat_remove_entries(old_dir, &old_sinfo); /* and releases bh */ 53962306a36Sopenharmony_ci old_sinfo.bh = NULL; 54062306a36Sopenharmony_ci if (err) 54162306a36Sopenharmony_ci goto error_dotdot; 54262306a36Sopenharmony_ci inode_inc_iversion(old_dir); 54362306a36Sopenharmony_ci fat_truncate_time(old_dir, &ts, S_CTIME|S_MTIME); 54462306a36Sopenharmony_ci if (IS_DIRSYNC(old_dir)) 54562306a36Sopenharmony_ci (void)fat_sync_inode(old_dir); 54662306a36Sopenharmony_ci else 54762306a36Sopenharmony_ci mark_inode_dirty(old_dir); 54862306a36Sopenharmony_ci 54962306a36Sopenharmony_ci if (new_inode) { 55062306a36Sopenharmony_ci drop_nlink(new_inode); 55162306a36Sopenharmony_ci if (is_dir) 55262306a36Sopenharmony_ci drop_nlink(new_inode); 55362306a36Sopenharmony_ci fat_truncate_time(new_inode, &ts, S_CTIME); 55462306a36Sopenharmony_ci } 55562306a36Sopenharmony_ciout: 55662306a36Sopenharmony_ci brelse(sinfo.bh); 55762306a36Sopenharmony_ci brelse(dotdot_bh); 55862306a36Sopenharmony_ci brelse(old_sinfo.bh); 55962306a36Sopenharmony_ci return err; 56062306a36Sopenharmony_ci 56162306a36Sopenharmony_cierror_dotdot: 56262306a36Sopenharmony_ci /* data cluster is shared, serious corruption */ 56362306a36Sopenharmony_ci corrupt = 1; 56462306a36Sopenharmony_ci 56562306a36Sopenharmony_ci if (update_dotdot) { 56662306a36Sopenharmony_ci fat_set_start(dotdot_de, MSDOS_I(old_dir)->i_logstart); 56762306a36Sopenharmony_ci mark_buffer_dirty_inode(dotdot_bh, old_inode); 56862306a36Sopenharmony_ci corrupt |= sync_dirty_buffer(dotdot_bh); 56962306a36Sopenharmony_ci } 57062306a36Sopenharmony_cierror_inode: 57162306a36Sopenharmony_ci fat_detach(old_inode); 57262306a36Sopenharmony_ci fat_attach(old_inode, old_sinfo.i_pos); 57362306a36Sopenharmony_ci MSDOS_I(old_inode)->i_attrs = old_attrs; 57462306a36Sopenharmony_ci if (new_inode) { 57562306a36Sopenharmony_ci fat_attach(new_inode, new_i_pos); 57662306a36Sopenharmony_ci if (corrupt) 57762306a36Sopenharmony_ci corrupt |= fat_sync_inode(new_inode); 57862306a36Sopenharmony_ci } else { 57962306a36Sopenharmony_ci /* 58062306a36Sopenharmony_ci * If new entry was not sharing the data cluster, it 58162306a36Sopenharmony_ci * shouldn't be serious corruption. 58262306a36Sopenharmony_ci */ 58362306a36Sopenharmony_ci int err2 = fat_remove_entries(new_dir, &sinfo); 58462306a36Sopenharmony_ci if (corrupt) 58562306a36Sopenharmony_ci corrupt |= err2; 58662306a36Sopenharmony_ci sinfo.bh = NULL; 58762306a36Sopenharmony_ci } 58862306a36Sopenharmony_ci if (corrupt < 0) { 58962306a36Sopenharmony_ci fat_fs_error(new_dir->i_sb, 59062306a36Sopenharmony_ci "%s: Filesystem corrupted (i_pos %lld)", 59162306a36Sopenharmony_ci __func__, sinfo.i_pos); 59262306a36Sopenharmony_ci } 59362306a36Sopenharmony_ci goto out; 59462306a36Sopenharmony_ci} 59562306a36Sopenharmony_ci 59662306a36Sopenharmony_ci/***** Rename, a wrapper for rename_same_dir & rename_diff_dir */ 59762306a36Sopenharmony_cistatic int msdos_rename(struct mnt_idmap *idmap, 59862306a36Sopenharmony_ci struct inode *old_dir, struct dentry *old_dentry, 59962306a36Sopenharmony_ci struct inode *new_dir, struct dentry *new_dentry, 60062306a36Sopenharmony_ci unsigned int flags) 60162306a36Sopenharmony_ci{ 60262306a36Sopenharmony_ci struct super_block *sb = old_dir->i_sb; 60362306a36Sopenharmony_ci unsigned char old_msdos_name[MSDOS_NAME], new_msdos_name[MSDOS_NAME]; 60462306a36Sopenharmony_ci int err, is_hid; 60562306a36Sopenharmony_ci 60662306a36Sopenharmony_ci if (flags & ~RENAME_NOREPLACE) 60762306a36Sopenharmony_ci return -EINVAL; 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_ci mutex_lock(&MSDOS_SB(sb)->s_lock); 61062306a36Sopenharmony_ci 61162306a36Sopenharmony_ci err = msdos_format_name(old_dentry->d_name.name, 61262306a36Sopenharmony_ci old_dentry->d_name.len, old_msdos_name, 61362306a36Sopenharmony_ci &MSDOS_SB(old_dir->i_sb)->options); 61462306a36Sopenharmony_ci if (err) 61562306a36Sopenharmony_ci goto out; 61662306a36Sopenharmony_ci err = msdos_format_name(new_dentry->d_name.name, 61762306a36Sopenharmony_ci new_dentry->d_name.len, new_msdos_name, 61862306a36Sopenharmony_ci &MSDOS_SB(new_dir->i_sb)->options); 61962306a36Sopenharmony_ci if (err) 62062306a36Sopenharmony_ci goto out; 62162306a36Sopenharmony_ci 62262306a36Sopenharmony_ci is_hid = 62362306a36Sopenharmony_ci (new_dentry->d_name.name[0] == '.') && (new_msdos_name[0] != '.'); 62462306a36Sopenharmony_ci 62562306a36Sopenharmony_ci err = do_msdos_rename(old_dir, old_msdos_name, old_dentry, 62662306a36Sopenharmony_ci new_dir, new_msdos_name, new_dentry, is_hid); 62762306a36Sopenharmony_ciout: 62862306a36Sopenharmony_ci mutex_unlock(&MSDOS_SB(sb)->s_lock); 62962306a36Sopenharmony_ci if (!err) 63062306a36Sopenharmony_ci err = fat_flush_inodes(sb, old_dir, new_dir); 63162306a36Sopenharmony_ci return err; 63262306a36Sopenharmony_ci} 63362306a36Sopenharmony_ci 63462306a36Sopenharmony_cistatic const struct inode_operations msdos_dir_inode_operations = { 63562306a36Sopenharmony_ci .create = msdos_create, 63662306a36Sopenharmony_ci .lookup = msdos_lookup, 63762306a36Sopenharmony_ci .unlink = msdos_unlink, 63862306a36Sopenharmony_ci .mkdir = msdos_mkdir, 63962306a36Sopenharmony_ci .rmdir = msdos_rmdir, 64062306a36Sopenharmony_ci .rename = msdos_rename, 64162306a36Sopenharmony_ci .setattr = fat_setattr, 64262306a36Sopenharmony_ci .getattr = fat_getattr, 64362306a36Sopenharmony_ci .update_time = fat_update_time, 64462306a36Sopenharmony_ci}; 64562306a36Sopenharmony_ci 64662306a36Sopenharmony_cistatic void setup(struct super_block *sb) 64762306a36Sopenharmony_ci{ 64862306a36Sopenharmony_ci MSDOS_SB(sb)->dir_ops = &msdos_dir_inode_operations; 64962306a36Sopenharmony_ci sb->s_d_op = &msdos_dentry_operations; 65062306a36Sopenharmony_ci sb->s_flags |= SB_NOATIME; 65162306a36Sopenharmony_ci} 65262306a36Sopenharmony_ci 65362306a36Sopenharmony_cistatic int msdos_fill_super(struct super_block *sb, void *data, int silent) 65462306a36Sopenharmony_ci{ 65562306a36Sopenharmony_ci return fat_fill_super(sb, data, silent, 0, setup); 65662306a36Sopenharmony_ci} 65762306a36Sopenharmony_ci 65862306a36Sopenharmony_cistatic struct dentry *msdos_mount(struct file_system_type *fs_type, 65962306a36Sopenharmony_ci int flags, const char *dev_name, 66062306a36Sopenharmony_ci void *data) 66162306a36Sopenharmony_ci{ 66262306a36Sopenharmony_ci return mount_bdev(fs_type, flags, dev_name, data, msdos_fill_super); 66362306a36Sopenharmony_ci} 66462306a36Sopenharmony_ci 66562306a36Sopenharmony_cistatic struct file_system_type msdos_fs_type = { 66662306a36Sopenharmony_ci .owner = THIS_MODULE, 66762306a36Sopenharmony_ci .name = "msdos", 66862306a36Sopenharmony_ci .mount = msdos_mount, 66962306a36Sopenharmony_ci .kill_sb = kill_block_super, 67062306a36Sopenharmony_ci .fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP, 67162306a36Sopenharmony_ci}; 67262306a36Sopenharmony_ciMODULE_ALIAS_FS("msdos"); 67362306a36Sopenharmony_ci 67462306a36Sopenharmony_cistatic int __init init_msdos_fs(void) 67562306a36Sopenharmony_ci{ 67662306a36Sopenharmony_ci return register_filesystem(&msdos_fs_type); 67762306a36Sopenharmony_ci} 67862306a36Sopenharmony_ci 67962306a36Sopenharmony_cistatic void __exit exit_msdos_fs(void) 68062306a36Sopenharmony_ci{ 68162306a36Sopenharmony_ci unregister_filesystem(&msdos_fs_type); 68262306a36Sopenharmony_ci} 68362306a36Sopenharmony_ci 68462306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 68562306a36Sopenharmony_ciMODULE_AUTHOR("Werner Almesberger"); 68662306a36Sopenharmony_ciMODULE_DESCRIPTION("MS-DOS filesystem support"); 68762306a36Sopenharmony_ci 68862306a36Sopenharmony_cimodule_init(init_msdos_fs) 68962306a36Sopenharmony_cimodule_exit(exit_msdos_fs) 690