brintos

brintos / linux-shallow public Read only

0
0
Text · 17.3 KiB · f06f6ba Raw
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