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 */ |
15 | static unsigned char bad_chars[] = "*?<>|\"" ; |
16 | static unsigned char bad_if_strict[] = "+=,; " ; |
17 | |
18 | /***** Formats an MS-DOS file name. Rejects invalid names. */ |
19 | static 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. */ |
119 | static 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(sb: dir->i_sb); |
123 | unsigned char msdos_name[MSDOS_NAME]; |
124 | int err; |
125 | |
126 | err = msdos_format_name(name, len, res: msdos_name, opts: &sbi->options); |
127 | if (err) |
128 | return -ENOENT; |
129 | |
130 | err = fat_scan(dir, name: 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(bh: 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 | */ |
151 | static int msdos_hash(const struct dentry *dentry, struct qstr *qstr) |
152 | { |
153 | struct fat_mount_options *options = &MSDOS_SB(sb: dentry->d_sb)->options; |
154 | unsigned char msdos_name[MSDOS_NAME]; |
155 | int error; |
156 | |
157 | error = msdos_format_name(name: qstr->name, len: qstr->len, res: msdos_name, opts: options); |
158 | if (!error) |
159 | qstr->hash = full_name_hash(salt: 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 | */ |
167 | static 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(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: name->len, res: a_msdos_name, opts: options); |
175 | if (error) |
176 | goto old_compare; |
177 | error = msdos_format_name(name: str, len, res: b_msdos_name, opts: options); |
178 | if (error) |
179 | goto old_compare; |
180 | error = memcmp(p: a_msdos_name, q: b_msdos_name, MSDOS_NAME); |
181 | out: |
182 | return error; |
183 | |
184 | old_compare: |
185 | error = 1; |
186 | if (name->len == len) |
187 | error = memcmp(p: name->name, q: str, size: len); |
188 | goto out; |
189 | } |
190 | |
191 | static 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 */ |
201 | static 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, name: dentry->d_name.name, len: dentry->d_name.len, sinfo: &sinfo); |
211 | switch (err) { |
212 | case -ENOENT: |
213 | inode = NULL; |
214 | break; |
215 | case 0: |
216 | inode = fat_build_inode(sb, de: sinfo.de, i_pos: sinfo.i_pos); |
217 | brelse(bh: sinfo.bh); |
218 | break; |
219 | default: |
220 | inode = ERR_PTR(error: err); |
221 | } |
222 | mutex_unlock(lock: &MSDOS_SB(sb)->s_lock); |
223 | return d_splice_alias(inode, dentry); |
224 | } |
225 | |
226 | /***** Creates a directory entry (name is already formatted). */ |
227 | static 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(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: &time, date: &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: &de, cluster); |
248 | de.size = 0; |
249 | |
250 | err = fat_add_entries(dir, slots: &de, nr_slots: 1, sinfo); |
251 | if (err) |
252 | return err; |
253 | |
254 | fat_truncate_time(inode: dir, now: ts, flags: S_CTIME|S_MTIME); |
255 | if (IS_DIRSYNC(dir)) |
256 | (void)fat_sync_inode(inode: dir); |
257 | else |
258 | mark_inode_dirty(inode: dir); |
259 | |
260 | return 0; |
261 | } |
262 | |
263 | /***** Create a file */ |
264 | static 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(name: dentry->d_name.name, len: dentry->d_name.len, |
277 | res: msdos_name, opts: &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, name: msdos_name, sinfo: &sinfo)) { |
283 | brelse(bh: sinfo.bh); |
284 | err = -EINVAL; |
285 | goto out; |
286 | } |
287 | |
288 | ts = current_time(inode: dir); |
289 | err = msdos_add_entry(dir, name: msdos_name, is_dir: 0, is_hid, cluster: 0, ts: &ts, sinfo: &sinfo); |
290 | if (err) |
291 | goto out; |
292 | inode = fat_build_inode(sb, de: sinfo.de, i_pos: sinfo.i_pos); |
293 | brelse(bh: sinfo.bh); |
294 | if (IS_ERR(ptr: inode)) { |
295 | err = PTR_ERR(ptr: inode); |
296 | goto out; |
297 | } |
298 | fat_truncate_time(inode, now: &ts, flags: S_ATIME|S_CTIME|S_MTIME); |
299 | /* timestamp is already written, so mark_inode_dirty() is unneeded. */ |
300 | |
301 | d_instantiate(dentry, inode); |
302 | out: |
303 | mutex_unlock(lock: &MSDOS_SB(sb)->s_lock); |
304 | if (!err) |
305 | err = fat_flush_inodes(sb, i1: dir, i2: inode); |
306 | return err; |
307 | } |
308 | |
309 | /***** Remove a directory */ |
310 | static 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(dir: inode); |
319 | if (err) |
320 | goto out; |
321 | err = msdos_find(dir, name: dentry->d_name.name, len: dentry->d_name.len, sinfo: &sinfo); |
322 | if (err) |
323 | goto out; |
324 | |
325 | err = fat_remove_entries(dir, sinfo: &sinfo); /* and releases bh */ |
326 | if (err) |
327 | goto out; |
328 | drop_nlink(inode: dir); |
329 | |
330 | clear_nlink(inode); |
331 | fat_truncate_time(inode, NULL, flags: S_CTIME); |
332 | fat_detach(inode); |
333 | out: |
334 | mutex_unlock(lock: &MSDOS_SB(sb)->s_lock); |
335 | if (!err) |
336 | err = fat_flush_inodes(sb, i1: dir, i2: inode); |
337 | |
338 | return err; |
339 | } |
340 | |
341 | /***** Make a directory */ |
342 | static 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(name: dentry->d_name.name, len: dentry->d_name.len, |
355 | res: msdos_name, opts: &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, name: msdos_name, sinfo: &sinfo)) { |
361 | brelse(bh: sinfo.bh); |
362 | err = -EINVAL; |
363 | goto out; |
364 | } |
365 | |
366 | ts = current_time(inode: dir); |
367 | cluster = fat_alloc_new_dir(dir, ts: &ts); |
368 | if (cluster < 0) { |
369 | err = cluster; |
370 | goto out; |
371 | } |
372 | err = msdos_add_entry(dir, name: msdos_name, is_dir: 1, is_hid, cluster, ts: &ts, sinfo: &sinfo); |
373 | if (err) |
374 | goto out_free; |
375 | inc_nlink(inode: dir); |
376 | |
377 | inode = fat_build_inode(sb, de: sinfo.de, i_pos: sinfo.i_pos); |
378 | brelse(bh: sinfo.bh); |
379 | if (IS_ERR(ptr: inode)) { |
380 | err = PTR_ERR(ptr: inode); |
381 | /* the directory was completed, just return a error */ |
382 | goto out; |
383 | } |
384 | set_nlink(inode, nlink: 2); |
385 | fat_truncate_time(inode, now: &ts, flags: 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(lock: &MSDOS_SB(sb)->s_lock); |
391 | fat_flush_inodes(sb, i1: dir, i2: inode); |
392 | return 0; |
393 | |
394 | out_free: |
395 | fat_free_clusters(inode: dir, cluster); |
396 | out: |
397 | mutex_unlock(lock: &MSDOS_SB(sb)->s_lock); |
398 | return err; |
399 | } |
400 | |
401 | /***** Unlink a file */ |
402 | static 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, name: dentry->d_name.name, len: dentry->d_name.len, sinfo: &sinfo); |
411 | if (err) |
412 | goto out; |
413 | |
414 | err = fat_remove_entries(dir, sinfo: &sinfo); /* and releases bh */ |
415 | if (err) |
416 | goto out; |
417 | clear_nlink(inode); |
418 | fat_truncate_time(inode, NULL, flags: S_CTIME); |
419 | fat_detach(inode); |
420 | out: |
421 | mutex_unlock(lock: &MSDOS_SB(sb)->s_lock); |
422 | if (!err) |
423 | err = fat_flush_inodes(sb, i1: dir, i2: inode); |
424 | |
425 | return err; |
426 | } |
427 | |
428 | static 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(dentry: old_dentry); |
443 | new_inode = d_inode(dentry: new_dentry); |
444 | |
445 | err = fat_scan(dir: old_dir, name: old_name, sinfo: &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(dir: old_inode, bh: &dotdot_bh, de: &dotdot_de)) { |
455 | err = -EIO; |
456 | goto out; |
457 | } |
458 | } |
459 | |
460 | old_attrs = MSDOS_I(inode: old_inode)->i_attrs; |
461 | err = fat_scan(dir: new_dir, name: new_name, sinfo: &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(inode: old_inode)->i_attrs |= ATTR_HIDDEN; |
471 | else |
472 | MSDOS_I(inode: old_inode)->i_attrs &= ~ATTR_HIDDEN; |
473 | if (IS_DIRSYNC(old_dir)) { |
474 | err = fat_sync_inode(inode: old_inode); |
475 | if (err) { |
476 | MSDOS_I(inode: old_inode)->i_attrs = old_attrs; |
477 | goto out; |
478 | } |
479 | } else |
480 | mark_inode_dirty(inode: old_inode); |
481 | |
482 | inode_inc_iversion(inode: old_dir); |
483 | fat_truncate_time(inode: old_dir, NULL, flags: S_CTIME|S_MTIME); |
484 | if (IS_DIRSYNC(old_dir)) |
485 | (void)fat_sync_inode(inode: old_dir); |
486 | else |
487 | mark_inode_dirty(inode: old_dir); |
488 | goto out; |
489 | } |
490 | } |
491 | |
492 | ts = current_time(inode: old_inode); |
493 | if (new_inode) { |
494 | if (err) |
495 | goto out; |
496 | if (is_dir) { |
497 | err = fat_dir_empty(dir: new_inode); |
498 | if (err) |
499 | goto out; |
500 | } |
501 | new_i_pos = MSDOS_I(inode: new_inode)->i_pos; |
502 | fat_detach(inode: new_inode); |
503 | } else { |
504 | err = msdos_add_entry(dir: new_dir, name: new_name, is_dir, is_hid, cluster: 0, |
505 | ts: &ts, sinfo: &sinfo); |
506 | if (err) |
507 | goto out; |
508 | new_i_pos = sinfo.i_pos; |
509 | } |
510 | inode_inc_iversion(inode: new_dir); |
511 | |
512 | fat_detach(inode: old_inode); |
513 | fat_attach(inode: old_inode, i_pos: new_i_pos); |
514 | if (is_hid) |
515 | MSDOS_I(inode: old_inode)->i_attrs |= ATTR_HIDDEN; |
516 | else |
517 | MSDOS_I(inode: old_inode)->i_attrs &= ~ATTR_HIDDEN; |
518 | if (IS_DIRSYNC(new_dir)) { |
519 | err = fat_sync_inode(inode: old_inode); |
520 | if (err) |
521 | goto error_inode; |
522 | } else |
523 | mark_inode_dirty(inode: old_inode); |
524 | |
525 | if (update_dotdot) { |
526 | fat_set_start(de: dotdot_de, cluster: MSDOS_I(inode: new_dir)->i_logstart); |
527 | mark_buffer_dirty_inode(bh: dotdot_bh, inode: old_inode); |
528 | if (IS_DIRSYNC(new_dir)) { |
529 | err = sync_dirty_buffer(bh: dotdot_bh); |
530 | if (err) |
531 | goto error_dotdot; |
532 | } |
533 | drop_nlink(inode: old_dir); |
534 | if (!new_inode) |
535 | inc_nlink(inode: new_dir); |
536 | } |
537 | |
538 | err = fat_remove_entries(dir: old_dir, sinfo: &old_sinfo); /* and releases bh */ |
539 | old_sinfo.bh = NULL; |
540 | if (err) |
541 | goto error_dotdot; |
542 | inode_inc_iversion(inode: old_dir); |
543 | fat_truncate_time(inode: old_dir, now: &ts, flags: S_CTIME|S_MTIME); |
544 | if (IS_DIRSYNC(old_dir)) |
545 | (void)fat_sync_inode(inode: old_dir); |
546 | else |
547 | mark_inode_dirty(inode: old_dir); |
548 | |
549 | if (new_inode) { |
550 | drop_nlink(inode: new_inode); |
551 | if (is_dir) |
552 | drop_nlink(inode: new_inode); |
553 | fat_truncate_time(inode: new_inode, now: &ts, flags: S_CTIME); |
554 | } |
555 | out: |
556 | brelse(bh: sinfo.bh); |
557 | brelse(bh: dotdot_bh); |
558 | brelse(bh: old_sinfo.bh); |
559 | return err; |
560 | |
561 | error_dotdot: |
562 | /* data cluster is shared, serious corruption */ |
563 | corrupt = 1; |
564 | |
565 | if (update_dotdot) { |
566 | fat_set_start(de: dotdot_de, cluster: MSDOS_I(inode: old_dir)->i_logstart); |
567 | mark_buffer_dirty_inode(bh: dotdot_bh, inode: old_inode); |
568 | corrupt |= sync_dirty_buffer(bh: dotdot_bh); |
569 | } |
570 | error_inode: |
571 | fat_detach(inode: old_inode); |
572 | fat_attach(inode: old_inode, i_pos: old_sinfo.i_pos); |
573 | MSDOS_I(inode: old_inode)->i_attrs = old_attrs; |
574 | if (new_inode) { |
575 | fat_attach(inode: new_inode, i_pos: new_i_pos); |
576 | if (corrupt) |
577 | corrupt |= fat_sync_inode(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(dir: new_dir, sinfo: &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 */ |
597 | static 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(name: old_dentry->d_name.name, |
612 | len: old_dentry->d_name.len, res: old_msdos_name, |
613 | opts: &MSDOS_SB(sb: old_dir->i_sb)->options); |
614 | if (err) |
615 | goto out; |
616 | err = msdos_format_name(name: new_dentry->d_name.name, |
617 | len: new_dentry->d_name.len, res: new_msdos_name, |
618 | opts: &MSDOS_SB(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_name: old_msdos_name, old_dentry, |
626 | new_dir, new_name: new_msdos_name, new_dentry, is_hid); |
627 | out: |
628 | mutex_unlock(lock: &MSDOS_SB(sb)->s_lock); |
629 | if (!err) |
630 | err = fat_flush_inodes(sb, i1: old_dir, i2: new_dir); |
631 | return err; |
632 | } |
633 | |
634 | static 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 | |
646 | static 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 | |
653 | static int msdos_fill_super(struct super_block *sb, void *data, int silent) |
654 | { |
655 | return fat_fill_super(sb, data, silent, isvfat: 0, setup); |
656 | } |
657 | |
658 | static 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, fill_super: msdos_fill_super); |
663 | } |
664 | |
665 | static 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 | }; |
672 | MODULE_ALIAS_FS("msdos" ); |
673 | |
674 | static int __init init_msdos_fs(void) |
675 | { |
676 | return register_filesystem(&msdos_fs_type); |
677 | } |
678 | |
679 | static void __exit exit_msdos_fs(void) |
680 | { |
681 | unregister_filesystem(&msdos_fs_type); |
682 | } |
683 | |
684 | MODULE_LICENSE("GPL" ); |
685 | MODULE_AUTHOR("Werner Almesberger" ); |
686 | MODULE_DESCRIPTION("MS-DOS filesystem support" ); |
687 | |
688 | module_init(init_msdos_fs) |
689 | module_exit(exit_msdos_fs) |
690 | |