1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 *  linux/fs/msdos/namei.c
4 *
5 *  Written 1992,1993 by Werner Almesberger
6 *  Hidden files 1995 by Albert Cahalan <albert@ccs.neu.edu> <adc@coe.neu.edu>
7 *  Rewritten for constant inumbers 1999 by Al Viro
8 */
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 is
23	 * the resulting name, opts->name_check is either (r)elaxed,
24	 * (n)ormal or (s)trict, opts->dotsOK allows dots at the
25	 * 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		} else
38			return -EINVAL;
39	}
40	/*
41	 * disallow names that _really_ start with a dot
42	 */
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 substitute
58	 * 0x05 because 0xE5 marks deleted files.  Yes, DOS really
59	 * does this.
60	 * It seems that Microsoft hacked DOS to support non-US
61	 * characters after the 0xE5 character was already in use to
62	 * 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			else
105				*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 so
148 * that the existing dentry can be used. The msdos fs routines will
149 * 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	else
258		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			else
472				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			} else
480				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			else
487				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	else
517		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	} else
523		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	else
547		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, it
581		 * 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, void *data, int silent)
654{
655	return fat_fill_super(sb, data, silent, 0, setup);
656}
657
658static struct dentry *msdos_mount(struct file_system_type *fs_type,
659			int flags, const char *dev_name,
660			void *data)
661{
662	return mount_bdev(fs_type, flags, dev_name, data, msdos_fill_super);
663}
664
665static struct file_system_type msdos_fs_type = {
666	.owner		= THIS_MODULE,
667	.name		= "msdos",
668	.mount		= msdos_mount,
669	.kill_sb	= kill_block_super,
670	.fs_flags	= FS_REQUIRES_DEV | FS_ALLOW_IDMAP,
671};
672MODULE_ALIAS_FS("msdos");
673
674static int __init init_msdos_fs(void)
675{
676	return register_filesystem(&msdos_fs_type);
677}
678
679static void __exit exit_msdos_fs(void)
680{
681	unregister_filesystem(&msdos_fs_type);
682}
683
684MODULE_LICENSE("GPL");
685MODULE_AUTHOR("Werner Almesberger");
686MODULE_DESCRIPTION("MS-DOS filesystem support");
687
688module_init(init_msdos_fs)
689module_exit(exit_msdos_fs)
690