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