File Index
Files and directories
Sign in
FILE

namei_msdos.c

File preview with syntax highlighting.

linux-6.19.14/fs/fat/namei_msdos.c C
  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	if (dir->i_nlink >= 3)
329		drop_nlink(dir);
330	else {
331		fat_fs_error(sb, "parent dir link count too low (%u)",
332			dir->i_nlink);
333	}
334
335	clear_nlink(inode);
336	fat_truncate_time(inode, NULL, S_CTIME);
337	fat_detach(inode);
338out:
339	mutex_unlock(&MSDOS_SB(sb)->s_lock);
340	if (!err)
341		err = fat_flush_inodes(sb, dir, inode);
342
343	return err;
344}
345
346/***** Make a directory */
347static struct dentry *msdos_mkdir(struct mnt_idmap *idmap, struct inode *dir,
348				  struct dentry *dentry, umode_t mode)
349{
350	struct super_block *sb = dir->i_sb;
351	struct fat_slot_info sinfo;
352	struct inode *inode;
353	unsigned char msdos_name[MSDOS_NAME];
354	struct timespec64 ts;
355	int err, is_hid, cluster;
356
357	mutex_lock(&MSDOS_SB(sb)->s_lock);
358
359	err = msdos_format_name(dentry->d_name.name, dentry->d_name.len,
360				msdos_name, &MSDOS_SB(sb)->options);
361	if (err)
362		goto out;
363	is_hid = (dentry->d_name.name[0] == '.') && (msdos_name[0] != '.');
364	/* foo vs .foo situation */
365	if (!fat_scan(dir, msdos_name, &sinfo)) {
366		brelse(sinfo.bh);
367		err = -EINVAL;
368		goto out;
369	}
370
371	ts = current_time(dir);
372	cluster = fat_alloc_new_dir(dir, &ts);
373	if (cluster < 0) {
374		err = cluster;
375		goto out;
376	}
377	err = msdos_add_entry(dir, msdos_name, 1, is_hid, cluster, &ts, &sinfo);
378	if (err)
379		goto out_free;
380	inc_nlink(dir);
381
382	inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos);
383	brelse(sinfo.bh);
384	if (IS_ERR(inode)) {
385		err = PTR_ERR(inode);
386		/* the directory was completed, just return a error */
387		goto out;
388	}
389	set_nlink(inode, 2);
390	fat_truncate_time(inode, &ts, S_ATIME|S_CTIME|S_MTIME);
391	/* timestamp is already written, so mark_inode_dirty() is unneeded. */
392
393	d_instantiate(dentry, inode);
394
395	mutex_unlock(&MSDOS_SB(sb)->s_lock);
396	fat_flush_inodes(sb, dir, inode);
397	return NULL;
398
399out_free:
400	fat_free_clusters(dir, cluster);
401out:
402	mutex_unlock(&MSDOS_SB(sb)->s_lock);
403	return ERR_PTR(err);
404}
405
406/***** Unlink a file */
407static int msdos_unlink(struct inode *dir, struct dentry *dentry)
408{
409	struct inode *inode = d_inode(dentry);
410	struct super_block *sb = inode->i_sb;
411	struct fat_slot_info sinfo;
412	int err;
413
414	mutex_lock(&MSDOS_SB(sb)->s_lock);
415	err = msdos_find(dir, dentry->d_name.name, dentry->d_name.len, &sinfo);
416	if (err)
417		goto out;
418
419	err = fat_remove_entries(dir, &sinfo);	/* and releases bh */
420	if (err)
421		goto out;
422	clear_nlink(inode);
423	fat_truncate_time(inode, NULL, S_CTIME);
424	fat_detach(inode);
425out:
426	mutex_unlock(&MSDOS_SB(sb)->s_lock);
427	if (!err)
428		err = fat_flush_inodes(sb, dir, inode);
429
430	return err;
431}
432
433static int do_msdos_rename(struct inode *old_dir, unsigned char *old_name,
434			   struct dentry *old_dentry,
435			   struct inode *new_dir, unsigned char *new_name,
436			   struct dentry *new_dentry, int is_hid)
437{
438	struct buffer_head *dotdot_bh;
439	struct msdos_dir_entry *dotdot_de;
440	struct inode *old_inode, *new_inode;
441	struct fat_slot_info old_sinfo, sinfo;
442	struct timespec64 ts;
443	loff_t new_i_pos;
444	int err, old_attrs, is_dir, update_dotdot, corrupt = 0;
445
446	old_sinfo.bh = sinfo.bh = dotdot_bh = NULL;
447	old_inode = d_inode(old_dentry);
448	new_inode = d_inode(new_dentry);
449
450	err = fat_scan(old_dir, old_name, &old_sinfo);
451	if (err) {
452		err = -EIO;
453		goto out;
454	}
455
456	is_dir = S_ISDIR(old_inode->i_mode);
457	update_dotdot = (is_dir && old_dir != new_dir);
458	if (update_dotdot) {
459		if (fat_get_dotdot_entry(old_inode, &dotdot_bh, &dotdot_de)) {
460			err = -EIO;
461			goto out;
462		}
463	}
464
465	old_attrs = MSDOS_I(old_inode)->i_attrs;
466	err = fat_scan(new_dir, new_name, &sinfo);
467	if (!err) {
468		if (!new_inode) {
469			/* "foo" -> ".foo" case. just change the ATTR_HIDDEN */
470			if (sinfo.de != old_sinfo.de) {
471				err = -EINVAL;
472				goto out;
473			}
474			if (is_hid)
475				MSDOS_I(old_inode)->i_attrs |= ATTR_HIDDEN;
476			else
477				MSDOS_I(old_inode)->i_attrs &= ~ATTR_HIDDEN;
478			if (IS_DIRSYNC(old_dir)) {
479				err = fat_sync_inode(old_inode);
480				if (err) {
481					MSDOS_I(old_inode)->i_attrs = old_attrs;
482					goto out;
483				}
484			} else
485				mark_inode_dirty(old_inode);
486
487			inode_inc_iversion(old_dir);
488			fat_truncate_time(old_dir, NULL, S_CTIME|S_MTIME);
489			if (IS_DIRSYNC(old_dir))
490				(void)fat_sync_inode(old_dir);
491			else
492				mark_inode_dirty(old_dir);
493			goto out;
494		}
495	}
496
497	ts = current_time(old_inode);
498	if (new_inode) {
499		if (err)
500			goto out;
501		if (is_dir) {
502			err = fat_dir_empty(new_inode);
503			if (err)
504				goto out;
505		}
506		new_i_pos = MSDOS_I(new_inode)->i_pos;
507		fat_detach(new_inode);
508	} else {
509		err = msdos_add_entry(new_dir, new_name, is_dir, is_hid, 0,
510				      &ts, &sinfo);
511		if (err)
512			goto out;
513		new_i_pos = sinfo.i_pos;
514	}
515	inode_inc_iversion(new_dir);
516
517	fat_detach(old_inode);
518	fat_attach(old_inode, new_i_pos);
519	if (is_hid)
520		MSDOS_I(old_inode)->i_attrs |= ATTR_HIDDEN;
521	else
522		MSDOS_I(old_inode)->i_attrs &= ~ATTR_HIDDEN;
523	if (IS_DIRSYNC(new_dir)) {
524		err = fat_sync_inode(old_inode);
525		if (err)
526			goto error_inode;
527	} else
528		mark_inode_dirty(old_inode);
529
530	if (update_dotdot) {
531		fat_set_start(dotdot_de, MSDOS_I(new_dir)->i_logstart);
532		mark_buffer_dirty_inode(dotdot_bh, old_inode);
533		if (IS_DIRSYNC(new_dir)) {
534			err = sync_dirty_buffer(dotdot_bh);
535			if (err)
536				goto error_dotdot;
537		}
538		drop_nlink(old_dir);
539		if (!new_inode)
540			inc_nlink(new_dir);
541	}
542
543	err = fat_remove_entries(old_dir, &old_sinfo);	/* and releases bh */
544	old_sinfo.bh = NULL;
545	if (err)
546		goto error_dotdot;
547	inode_inc_iversion(old_dir);
548	fat_truncate_time(old_dir, &ts, S_CTIME|S_MTIME);
549	if (IS_DIRSYNC(old_dir))
550		(void)fat_sync_inode(old_dir);
551	else
552		mark_inode_dirty(old_dir);
553
554	if (new_inode) {
555		drop_nlink(new_inode);
556		if (is_dir)
557			drop_nlink(new_inode);
558		fat_truncate_time(new_inode, &ts, S_CTIME);
559	}
560out:
561	brelse(sinfo.bh);
562	brelse(dotdot_bh);
563	brelse(old_sinfo.bh);
564	return err;
565
566error_dotdot:
567	/* data cluster is shared, serious corruption */
568	corrupt = 1;
569
570	if (update_dotdot) {
571		fat_set_start(dotdot_de, MSDOS_I(old_dir)->i_logstart);
572		mark_buffer_dirty_inode(dotdot_bh, old_inode);
573		corrupt |= sync_dirty_buffer(dotdot_bh);
574	}
575error_inode:
576	fat_detach(old_inode);
577	fat_attach(old_inode, old_sinfo.i_pos);
578	MSDOS_I(old_inode)->i_attrs = old_attrs;
579	if (new_inode) {
580		fat_attach(new_inode, new_i_pos);
581		if (corrupt)
582			corrupt |= fat_sync_inode(new_inode);
583	} else {
584		/*
585		 * If new entry was not sharing the data cluster, it
586		 * shouldn't be serious corruption.
587		 */
588		int err2 = fat_remove_entries(new_dir, &sinfo);
589		if (corrupt)
590			corrupt |= err2;
591		sinfo.bh = NULL;
592	}
593	if (corrupt < 0) {
594		fat_fs_error(new_dir->i_sb,
595			     "%s: Filesystem corrupted (i_pos %lld)",
596			     __func__, sinfo.i_pos);
597	}
598	goto out;
599}
600
601/***** Rename, a wrapper for rename_same_dir & rename_diff_dir */
602static int msdos_rename(struct mnt_idmap *idmap,
603			struct inode *old_dir, struct dentry *old_dentry,
604			struct inode *new_dir, struct dentry *new_dentry,
605			unsigned int flags)
606{
607	struct super_block *sb = old_dir->i_sb;
608	unsigned char old_msdos_name[MSDOS_NAME], new_msdos_name[MSDOS_NAME];
609	int err, is_hid;
610
611	if (flags & ~RENAME_NOREPLACE)
612		return -EINVAL;
613
614	mutex_lock(&MSDOS_SB(sb)->s_lock);
615
616	err = msdos_format_name(old_dentry->d_name.name,
617				old_dentry->d_name.len, old_msdos_name,
618				&MSDOS_SB(old_dir->i_sb)->options);
619	if (err)
620		goto out;
621	err = msdos_format_name(new_dentry->d_name.name,
622				new_dentry->d_name.len, new_msdos_name,
623				&MSDOS_SB(new_dir->i_sb)->options);
624	if (err)
625		goto out;
626
627	is_hid =
628	     (new_dentry->d_name.name[0] == '.') && (new_msdos_name[0] != '.');
629
630	err = do_msdos_rename(old_dir, old_msdos_name, old_dentry,
631			      new_dir, new_msdos_name, new_dentry, is_hid);
632out:
633	mutex_unlock(&MSDOS_SB(sb)->s_lock);
634	if (!err)
635		err = fat_flush_inodes(sb, old_dir, new_dir);
636	return err;
637}
638
639static const struct inode_operations msdos_dir_inode_operations = {
640	.create		= msdos_create,
641	.lookup		= msdos_lookup,
642	.unlink		= msdos_unlink,
643	.mkdir		= msdos_mkdir,
644	.rmdir		= msdos_rmdir,
645	.rename		= msdos_rename,
646	.setattr	= fat_setattr,
647	.getattr	= fat_getattr,
648	.update_time	= fat_update_time,
649};
650
651static void setup(struct super_block *sb)
652{
653	MSDOS_SB(sb)->dir_ops = &msdos_dir_inode_operations;
654	set_default_d_op(sb, &msdos_dentry_operations);
655	sb->s_flags |= SB_NOATIME;
656}
657
658static int msdos_fill_super(struct super_block *sb, struct fs_context *fc)
659{
660	return fat_fill_super(sb, fc, setup);
661}
662
663static int msdos_get_tree(struct fs_context *fc)
664{
665	return get_tree_bdev(fc, msdos_fill_super);
666}
667
668static int msdos_parse_param(struct fs_context *fc, struct fs_parameter *param)
669{
670	return fat_parse_param(fc, param, false);
671}
672
673static const struct fs_context_operations msdos_context_ops = {
674	.parse_param	= msdos_parse_param,
675	.get_tree	= msdos_get_tree,
676	.reconfigure	= fat_reconfigure,
677	.free		= fat_free_fc,
678};
679
680static int msdos_init_fs_context(struct fs_context *fc)
681{
682	int err;
683
684	/* Initialize with is_vfat == false */
685	err = fat_init_fs_context(fc, false);
686	if (err)
687		return err;
688
689	fc->ops = &msdos_context_ops;
690	return 0;
691}
692
693static struct file_system_type msdos_fs_type = {
694	.owner		= THIS_MODULE,
695	.name		= "msdos",
696	.kill_sb	= kill_block_super,
697	.fs_flags	= FS_REQUIRES_DEV | FS_ALLOW_IDMAP,
698	.init_fs_context = msdos_init_fs_context,
699	.parameters	= fat_param_spec,
700};
701MODULE_ALIAS_FS("msdos");
702
703static int __init init_msdos_fs(void)
704{
705	return register_filesystem(&msdos_fs_type);
706}
707
708static void __exit exit_msdos_fs(void)
709{
710	unregister_filesystem(&msdos_fs_type);
711}
712
713MODULE_LICENSE("GPL");
714MODULE_AUTHOR("Werner Almesberger");
715MODULE_DESCRIPTION("MS-DOS filesystem support");
716
717module_init(init_msdos_fs)
718module_exit(exit_msdos_fs)
~/ Read-only file preview.