Linux Audio

Check our new training course

Loading...
v3.15
 
  1/*
  2 *  linux/fs/msdos/namei.c
  3 *
  4 *  Written 1992,1993 by Werner Almesberger
  5 *  Hidden files 1995 by Albert Cahalan <albert@ccs.neu.edu> <adc@coe.neu.edu>
  6 *  Rewritten for constant inumbers 1999 by Al Viro
  7 */
  8
  9#include <linux/module.h>
 10#include <linux/time.h>
 11#include <linux/buffer_head.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(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 *parent, const struct dentry *dentry,
168		unsigned int len, const char *str, const struct qstr *name)
169{
170	struct fat_mount_options *options = &MSDOS_SB(parent->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 timespec *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	dir->i_ctime = dir->i_mtime = *ts;
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 inode *dir, struct dentry *dentry, umode_t mode,
265			bool excl)
266{
267	struct super_block *sb = dir->i_sb;
268	struct inode *inode = NULL;
269	struct fat_slot_info sinfo;
270	struct timespec 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_SEC;
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	inode->i_mtime = inode->i_atime = inode->i_ctime = ts;
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 = dentry->d_inode;
314	struct fat_slot_info sinfo;
315	int err;
316
317	mutex_lock(&MSDOS_SB(sb)->s_lock);
318	/*
319	 * Check whether the directory is not in use, then check
320	 * whether it is empty.
321	 */
322	err = fat_dir_empty(inode);
323	if (err)
324		goto out;
325	err = msdos_find(dir, dentry->d_name.name, dentry->d_name.len, &sinfo);
326	if (err)
327		goto out;
328
329	err = fat_remove_entries(dir, &sinfo);	/* and releases bh */
330	if (err)
331		goto out;
332	drop_nlink(dir);
333
334	clear_nlink(inode);
335	inode->i_ctime = CURRENT_TIME_SEC;
336	fat_detach(inode);
337out:
338	mutex_unlock(&MSDOS_SB(sb)->s_lock);
339	if (!err)
340		err = fat_flush_inodes(sb, dir, inode);
341
342	return err;
343}
344
345/***** Make a directory */
346static int msdos_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
 
347{
348	struct super_block *sb = dir->i_sb;
349	struct fat_slot_info sinfo;
350	struct inode *inode;
351	unsigned char msdos_name[MSDOS_NAME];
352	struct timespec ts;
353	int err, is_hid, cluster;
354
355	mutex_lock(&MSDOS_SB(sb)->s_lock);
356
357	err = msdos_format_name(dentry->d_name.name, dentry->d_name.len,
358				msdos_name, &MSDOS_SB(sb)->options);
359	if (err)
360		goto out;
361	is_hid = (dentry->d_name.name[0] == '.') && (msdos_name[0] != '.');
362	/* foo vs .foo situation */
363	if (!fat_scan(dir, msdos_name, &sinfo)) {
364		brelse(sinfo.bh);
365		err = -EINVAL;
366		goto out;
367	}
368
369	ts = CURRENT_TIME_SEC;
370	cluster = fat_alloc_new_dir(dir, &ts);
371	if (cluster < 0) {
372		err = cluster;
373		goto out;
374	}
375	err = msdos_add_entry(dir, msdos_name, 1, is_hid, cluster, &ts, &sinfo);
376	if (err)
377		goto out_free;
378	inc_nlink(dir);
379
380	inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos);
381	brelse(sinfo.bh);
382	if (IS_ERR(inode)) {
383		err = PTR_ERR(inode);
384		/* the directory was completed, just return a error */
385		goto out;
386	}
387	set_nlink(inode, 2);
388	inode->i_mtime = inode->i_atime = inode->i_ctime = ts;
389	/* timestamp is already written, so mark_inode_dirty() is unneeded. */
390
391	d_instantiate(dentry, inode);
392
393	mutex_unlock(&MSDOS_SB(sb)->s_lock);
394	fat_flush_inodes(sb, dir, inode);
395	return 0;
396
397out_free:
398	fat_free_clusters(dir, cluster);
399out:
400	mutex_unlock(&MSDOS_SB(sb)->s_lock);
401	return err;
402}
403
404/***** Unlink a file */
405static int msdos_unlink(struct inode *dir, struct dentry *dentry)
406{
407	struct inode *inode = dentry->d_inode;
408	struct super_block *sb = inode->i_sb;
409	struct fat_slot_info sinfo;
410	int err;
411
412	mutex_lock(&MSDOS_SB(sb)->s_lock);
413	err = msdos_find(dir, dentry->d_name.name, dentry->d_name.len, &sinfo);
414	if (err)
415		goto out;
416
417	err = fat_remove_entries(dir, &sinfo);	/* and releases bh */
418	if (err)
419		goto out;
420	clear_nlink(inode);
421	inode->i_ctime = CURRENT_TIME_SEC;
422	fat_detach(inode);
423out:
424	mutex_unlock(&MSDOS_SB(sb)->s_lock);
425	if (!err)
426		err = fat_flush_inodes(sb, dir, inode);
427
428	return err;
429}
430
431static int do_msdos_rename(struct inode *old_dir, unsigned char *old_name,
432			   struct dentry *old_dentry,
433			   struct inode *new_dir, unsigned char *new_name,
434			   struct dentry *new_dentry, int is_hid)
435{
436	struct buffer_head *dotdot_bh;
437	struct msdos_dir_entry *dotdot_de;
438	struct inode *old_inode, *new_inode;
439	struct fat_slot_info old_sinfo, sinfo;
440	struct timespec ts;
441	loff_t new_i_pos;
442	int err, old_attrs, is_dir, update_dotdot, corrupt = 0;
443
444	old_sinfo.bh = sinfo.bh = dotdot_bh = NULL;
445	old_inode = old_dentry->d_inode;
446	new_inode = new_dentry->d_inode;
447
448	err = fat_scan(old_dir, old_name, &old_sinfo);
449	if (err) {
450		err = -EIO;
451		goto out;
452	}
453
454	is_dir = S_ISDIR(old_inode->i_mode);
455	update_dotdot = (is_dir && old_dir != new_dir);
456	if (update_dotdot) {
457		if (fat_get_dotdot_entry(old_inode, &dotdot_bh, &dotdot_de)) {
458			err = -EIO;
459			goto out;
460		}
461	}
462
463	old_attrs = MSDOS_I(old_inode)->i_attrs;
464	err = fat_scan(new_dir, new_name, &sinfo);
465	if (!err) {
466		if (!new_inode) {
467			/* "foo" -> ".foo" case. just change the ATTR_HIDDEN */
468			if (sinfo.de != old_sinfo.de) {
469				err = -EINVAL;
470				goto out;
471			}
472			if (is_hid)
473				MSDOS_I(old_inode)->i_attrs |= ATTR_HIDDEN;
474			else
475				MSDOS_I(old_inode)->i_attrs &= ~ATTR_HIDDEN;
476			if (IS_DIRSYNC(old_dir)) {
477				err = fat_sync_inode(old_inode);
478				if (err) {
479					MSDOS_I(old_inode)->i_attrs = old_attrs;
480					goto out;
481				}
482			} else
483				mark_inode_dirty(old_inode);
484
485			old_dir->i_version++;
486			old_dir->i_ctime = old_dir->i_mtime = CURRENT_TIME_SEC;
487			if (IS_DIRSYNC(old_dir))
488				(void)fat_sync_inode(old_dir);
489			else
490				mark_inode_dirty(old_dir);
491			goto out;
492		}
493	}
494
495	ts = CURRENT_TIME_SEC;
496	if (new_inode) {
497		if (err)
498			goto out;
499		if (is_dir) {
500			err = fat_dir_empty(new_inode);
501			if (err)
502				goto out;
503		}
504		new_i_pos = MSDOS_I(new_inode)->i_pos;
505		fat_detach(new_inode);
506	} else {
507		err = msdos_add_entry(new_dir, new_name, is_dir, is_hid, 0,
508				      &ts, &sinfo);
509		if (err)
510			goto out;
511		new_i_pos = sinfo.i_pos;
512	}
513	new_dir->i_version++;
514
515	fat_detach(old_inode);
516	fat_attach(old_inode, new_i_pos);
517	if (is_hid)
518		MSDOS_I(old_inode)->i_attrs |= ATTR_HIDDEN;
519	else
520		MSDOS_I(old_inode)->i_attrs &= ~ATTR_HIDDEN;
521	if (IS_DIRSYNC(new_dir)) {
522		err = fat_sync_inode(old_inode);
523		if (err)
524			goto error_inode;
525	} else
526		mark_inode_dirty(old_inode);
527
528	if (update_dotdot) {
529		fat_set_start(dotdot_de, MSDOS_I(new_dir)->i_logstart);
530		mark_buffer_dirty_inode(dotdot_bh, old_inode);
531		if (IS_DIRSYNC(new_dir)) {
532			err = sync_dirty_buffer(dotdot_bh);
533			if (err)
534				goto error_dotdot;
535		}
536		drop_nlink(old_dir);
537		if (!new_inode)
538			inc_nlink(new_dir);
539	}
540
541	err = fat_remove_entries(old_dir, &old_sinfo);	/* and releases bh */
542	old_sinfo.bh = NULL;
543	if (err)
544		goto error_dotdot;
545	old_dir->i_version++;
546	old_dir->i_ctime = old_dir->i_mtime = ts;
547	if (IS_DIRSYNC(old_dir))
548		(void)fat_sync_inode(old_dir);
549	else
550		mark_inode_dirty(old_dir);
551
552	if (new_inode) {
553		drop_nlink(new_inode);
554		if (is_dir)
555			drop_nlink(new_inode);
556		new_inode->i_ctime = ts;
557	}
558out:
559	brelse(sinfo.bh);
560	brelse(dotdot_bh);
561	brelse(old_sinfo.bh);
562	return err;
563
564error_dotdot:
565	/* data cluster is shared, serious corruption */
566	corrupt = 1;
567
568	if (update_dotdot) {
569		fat_set_start(dotdot_de, MSDOS_I(old_dir)->i_logstart);
570		mark_buffer_dirty_inode(dotdot_bh, old_inode);
571		corrupt |= sync_dirty_buffer(dotdot_bh);
572	}
573error_inode:
574	fat_detach(old_inode);
575	fat_attach(old_inode, old_sinfo.i_pos);
576	MSDOS_I(old_inode)->i_attrs = old_attrs;
577	if (new_inode) {
578		fat_attach(new_inode, new_i_pos);
579		if (corrupt)
580			corrupt |= fat_sync_inode(new_inode);
581	} else {
582		/*
583		 * If new entry was not sharing the data cluster, it
584		 * shouldn't be serious corruption.
585		 */
586		int err2 = fat_remove_entries(new_dir, &sinfo);
587		if (corrupt)
588			corrupt |= err2;
589		sinfo.bh = NULL;
590	}
591	if (corrupt < 0) {
592		fat_fs_error(new_dir->i_sb,
593			     "%s: Filesystem corrupted (i_pos %lld)",
594			     __func__, sinfo.i_pos);
595	}
596	goto out;
597}
598
599/***** Rename, a wrapper for rename_same_dir & rename_diff_dir */
600static int msdos_rename(struct inode *old_dir, struct dentry *old_dentry,
601			struct inode *new_dir, struct dentry *new_dentry)
 
 
602{
603	struct super_block *sb = old_dir->i_sb;
604	unsigned char old_msdos_name[MSDOS_NAME], new_msdos_name[MSDOS_NAME];
605	int err, is_hid;
606
 
 
 
607	mutex_lock(&MSDOS_SB(sb)->s_lock);
608
609	err = msdos_format_name(old_dentry->d_name.name,
610				old_dentry->d_name.len, old_msdos_name,
611				&MSDOS_SB(old_dir->i_sb)->options);
612	if (err)
613		goto out;
614	err = msdos_format_name(new_dentry->d_name.name,
615				new_dentry->d_name.len, new_msdos_name,
616				&MSDOS_SB(new_dir->i_sb)->options);
617	if (err)
618		goto out;
619
620	is_hid =
621	     (new_dentry->d_name.name[0] == '.') && (new_msdos_name[0] != '.');
622
623	err = do_msdos_rename(old_dir, old_msdos_name, old_dentry,
624			      new_dir, new_msdos_name, new_dentry, is_hid);
625out:
626	mutex_unlock(&MSDOS_SB(sb)->s_lock);
627	if (!err)
628		err = fat_flush_inodes(sb, old_dir, new_dir);
629	return err;
630}
631
632static const struct inode_operations msdos_dir_inode_operations = {
633	.create		= msdos_create,
634	.lookup		= msdos_lookup,
635	.unlink		= msdos_unlink,
636	.mkdir		= msdos_mkdir,
637	.rmdir		= msdos_rmdir,
638	.rename		= msdos_rename,
639	.setattr	= fat_setattr,
640	.getattr	= fat_getattr,
 
641};
642
643static void setup(struct super_block *sb)
644{
645	MSDOS_SB(sb)->dir_ops = &msdos_dir_inode_operations;
646	sb->s_d_op = &msdos_dentry_operations;
647	sb->s_flags |= MS_NOATIME;
648}
649
650static int msdos_fill_super(struct super_block *sb, void *data, int silent)
651{
652	return fat_fill_super(sb, data, silent, 0, setup);
653}
654
655static struct dentry *msdos_mount(struct file_system_type *fs_type,
656			int flags, const char *dev_name,
657			void *data)
658{
659	return mount_bdev(fs_type, flags, dev_name, data, msdos_fill_super);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
660}
661
662static struct file_system_type msdos_fs_type = {
663	.owner		= THIS_MODULE,
664	.name		= "msdos",
665	.mount		= msdos_mount,
666	.kill_sb	= kill_block_super,
667	.fs_flags	= FS_REQUIRES_DEV,
 
 
668};
669MODULE_ALIAS_FS("msdos");
670
671static int __init init_msdos_fs(void)
672{
673	return register_filesystem(&msdos_fs_type);
674}
675
676static void __exit exit_msdos_fs(void)
677{
678	unregister_filesystem(&msdos_fs_type);
679}
680
681MODULE_LICENSE("GPL");
682MODULE_AUTHOR("Werner Almesberger");
683MODULE_DESCRIPTION("MS-DOS filesystem support");
684
685module_init(init_msdos_fs)
686module_exit(exit_msdos_fs)
v6.13.7
  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, 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)