"Fossies" - the Fresh Open Source Software Archive 
Member "dosfstools-4.2/src/check.c" (31 Jan 2021, 39830 Bytes) of package /linux/misc/dosfstools-4.2.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "check.c" see the
Fossies "Dox" file reference documentation and the latest
Fossies "Diffs" side-by-side code changes report:
4.1_vs_4.2.
1 /* check.c - Check and repair a PC/MS-DOS filesystem
2
3 Copyright (C) 1993 Werner Almesberger <werner.almesberger@lrc.di.epfl.ch>
4 Copyright (C) 1998 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
5 Copyright (C) 2008-2014 Daniel Baumann <mail@daniel-baumann.ch>
6 Copyright (C) 2015 Andreas Bombe <aeb@debian.org>
7 Copyright (C) 2017-2021 Pali Rohár <pali.rohar@gmail.com>
8
9 This program is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
21
22 The complete text of the GNU General Public License
23 can be found in /usr/share/common-licenses/GPL-3 file.
24 */
25
26 /* FAT32, VFAT, Atari format support, and various fixes additions May 1998
27 * by Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de> */
28
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <time.h>
33 #include <errno.h>
34 #include <ctype.h>
35 #include <wctype.h>
36
37 #include "common.h"
38 #include "fsck.fat.h"
39 #include "io.h"
40 #include "fat.h"
41 #include "file.h"
42 #include "lfn.h"
43 #include "check.h"
44 #include "boot.h"
45 #include "charconv.h"
46
47
48 /* the longest path on the filesystem that can be handled by path_name() */
49 #define PATH_NAME_MAX 1023
50
51 static DOS_FILE *root;
52
53 /* get start field of a dir entry */
54 #define FSTART(p,fs) \
55 ((uint32_t)le16toh(p->dir_ent.start) | \
56 (fs->fat_bits == 32 ? (uint32_t)le16toh(p->dir_ent.starthi) << 16 : 0))
57
58 #define MODIFY(p,i,v) \
59 do { \
60 if (p->offset) { \
61 p->dir_ent.i = v; \
62 fs_write(p->offset+offsetof(DIR_ENT,i), \
63 sizeof(p->dir_ent.i),&p->dir_ent.i); \
64 } \
65 } while(0)
66
67 #define MODIFY_START(p,v,fs) \
68 do { \
69 uint32_t __v = (v); \
70 if (!p->offset) { \
71 /* writing to fake entry for FAT32 root dir */ \
72 if (!__v) die("Oops, deleting FAT32 root dir!"); \
73 fs->root_cluster = __v; \
74 p->dir_ent.start = htole16(__v&0xffff); \
75 p->dir_ent.starthi = htole16(__v>>16); \
76 __v = htole32(__v); \
77 fs_write(offsetof(struct boot_sector,root_cluster), \
78 sizeof(((struct boot_sector *)0)->root_cluster), \
79 &__v); \
80 } \
81 else { \
82 MODIFY(p,start,htole16((__v)&0xffff)); \
83 if (fs->fat_bits == 32) \
84 MODIFY(p,starthi,htole16((__v)>>16)); \
85 } \
86 } while(0)
87
88 /**
89 * Construct a full path (starting with '/') for the specified dentry,
90 * relative to the partition. All components are "long" names where possible.
91 *
92 * @param[in] file Information about dentry (file or directory) of interest
93 *
94 * return Pointer to static string containing file's full path
95 */
96 static char *path_name(DOS_FILE * file)
97 {
98 static char path[PATH_NAME_MAX * 2];
99
100 if (!file)
101 *path = 0; /* Reached the root directory */
102 else {
103 if (strlen(path_name(file->parent)) > PATH_NAME_MAX)
104 die("Path name too long.");
105 if (strcmp(path, "/") != 0)
106 strcat(path, "/");
107
108 /* Append the long name to the path,
109 * or the short name if there isn't a long one
110 */
111 strcpy(strrchr(path, 0),
112 file->lfn ? file->lfn : file_name(file->dir_ent.name));
113 }
114 return path;
115 }
116
117 static const char *month_str[] =
118 { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
119
120 static char *file_stat(DOS_FILE * file)
121 {
122 static char temp[100];
123 unsigned int hours, minutes, secs, day, month, year;
124 unsigned short time, date;
125
126 time = le16toh(file->dir_ent.time);
127 date = le16toh(file->dir_ent.date);
128 year = 1980 + (date >> 9);
129 month = ((date >> 5) & 15);
130 if (month < 1) month = 1;
131 else if (month > 12) month = 12;
132 day = (date & 31);
133 if (day < 1) day = 1;
134 hours = (time >> 11);
135 if (hours > 23) hours = 23;
136 minutes = ((time >> 5) & 63);
137 if (minutes > 59) minutes = 59;
138 secs = (time & 31) * 2;
139 if (secs > 59) secs = 59;
140 sprintf(temp, " Size %u bytes, date %02u:%02u:%02u %s %02u %4u",
141 le32toh(file->dir_ent.size), hours, minutes, secs, month_str[month-1], day, year);
142 return temp;
143 }
144
145 static int bad_name(DOS_FILE * file)
146 {
147 int i, spc, suspicious = 0;
148 const char *bad_chars = atari_format ? "*?\\/:" : "*?<>|\"\\/:.";
149 const unsigned char *name = file->dir_ent.name;
150 const unsigned char *ext = name + 8;
151
152 /* do not check synthetic FAT32 root entry */
153 if (!file->offset)
154 return 0;
155
156 /* check if we have neither a long filename nor a short name */
157 if ((file->lfn == NULL) && (file->dir_ent.lcase & FAT_NO_83NAME)) {
158 return 1;
159 }
160
161 /* don't complain about the dummy 11 bytes used by patched Linux
162 kernels */
163 if (file->dir_ent.lcase & FAT_NO_83NAME)
164 return 0;
165
166 for (i = 0; i < MSDOS_NAME; i++) {
167 if ((name[i] < ' ' && !(i == 0 && name[0] == 0x05)) || name[i] == 0x7f)
168 return 1;
169 if (name[i] > 0x7f)
170 ++suspicious;
171 if (strchr(bad_chars, name[i]))
172 return 1;
173 }
174
175 if (name[0] == ' ')
176 return 1;
177
178 if (no_spaces_in_sfns) {
179 spc = 0;
180 for (i = 0; i < 8; i++) {
181 if (name[i] == ' ')
182 spc = 1;
183 else if (spc)
184 /* non-space after a space not allowed, space terminates the name
185 * part */
186 return 1;
187 }
188
189 spc = 0;
190 for (i = 0; i < 3; i++) {
191 if (ext[i] == ' ')
192 spc = 1;
193 else if (spc)
194 /* non-space after a space not allowed, space terminates the ext
195 * part */
196 return 1;
197 }
198 }
199
200 /* Under GEMDOS, chars >= 128 are never allowed. */
201 if (atari_format && suspicious)
202 return 1;
203
204 /* Under MS-DOS and Windows, chars >= 128 in short names are valid
205 * (but these characters can be visualised differently depending on
206 * local codepage: CP437, CP866, etc). The chars are all basically ok,
207 * so we shouldn't auto-correct such names. */
208 return 0;
209 }
210
211 static void lfn_remove(off_t from, off_t to)
212 {
213 DIR_ENT empty;
214
215 /* New dir entry is zeroed except first byte, which is set to 0xe5.
216 * This is to avoid that some FAT-reading OSes (not Linux! ;) stop reading
217 * a directory at the first zero entry...
218 */
219 memset(&empty, 0, sizeof(empty));
220 empty.name[0] = DELETED_FLAG;
221
222 for (; from < to; from += sizeof(empty)) {
223 fs_write(from, sizeof(DIR_ENT), &empty);
224 }
225 }
226
227 static void drop_file(DOS_FS * fs, DOS_FILE * file)
228 {
229 (void) fs;
230
231 MODIFY(file, name[0], DELETED_FLAG);
232 if (file->lfn)
233 lfn_remove(file->lfn_offset, file->offset);
234 --n_files;
235 }
236
237 static void truncate_file(DOS_FS * fs, DOS_FILE * file, uint32_t clusters)
238 {
239 int deleting;
240 uint32_t walk, next;
241
242 walk = FSTART(file, fs);
243 if ((deleting = !clusters))
244 MODIFY_START(file, 0, fs);
245 while (walk > 0 && walk != -1) {
246 next = next_cluster(fs, walk);
247 if (deleting)
248 set_fat(fs, walk, 0);
249 else if ((deleting = !--clusters))
250 set_fat(fs, walk, -1);
251 walk = next;
252 }
253 }
254
255 static void auto_rename(DOS_FILE * file)
256 {
257 DOS_FILE *first, *walk;
258 uint32_t number;
259
260 if (!file->offset) {
261 printf("Cannot rename FAT32 root dir\n");
262 return; /* cannot rename FAT32 root dir */
263 }
264 first = file->parent ? file->parent->first : root;
265 number = 0;
266 while (1) {
267 char num[8];
268 sprintf(num, "%07lu", (unsigned long)number);
269 memcpy(file->dir_ent.name, "FSCK", 4);
270 memcpy(file->dir_ent.name + 4, num, 7);
271 for (walk = first; walk; walk = walk->next)
272 if (walk != file
273 && !strncmp((const char *)walk->dir_ent.name,
274 (const char *)file->dir_ent.name, MSDOS_NAME))
275 break;
276 if (!walk) {
277 if (file->dir_ent.lcase & FAT_NO_83NAME) {
278 /* as we only assign a new 8.3 filename, reset flag that 8.3 name is not
279 present */
280 file->dir_ent.lcase &= ~FAT_NO_83NAME;
281 /* reset the attributes, only keep DIR and VOLUME */
282 file->dir_ent.attr &= ~(ATTR_DIR | ATTR_VOLUME);
283 fs_write(file->offset, MSDOS_NAME + 2, &file->dir_ent);
284 } else {
285 fs_write(file->offset, MSDOS_NAME, file->dir_ent.name);
286 }
287 if (file->lfn) {
288 lfn_remove(file->lfn_offset, file->offset);
289 file->lfn = NULL;
290 }
291 return;
292 }
293 number++;
294 if (number > 9999999) {
295 die("Too many files need repair.");
296 }
297 }
298 die("Can't generate a unique name.");
299 }
300
301 static void rename_file(DOS_FILE * file)
302 {
303 unsigned char name[46];
304 unsigned char *walk, *here;
305
306 if (!file->offset) {
307 printf("Cannot rename FAT32 root dir\n");
308 return; /* cannot rename FAT32 root dir */
309 }
310 while (1) {
311 if (get_line("New name", (char *)name, 45)) {
312 if ((here = (unsigned char *)strchr((const char *)name, '\n')))
313 *here = 0;
314 for (walk = (unsigned char *)strrchr((const char *)name, 0);
315 walk >= name && (*walk == ' ' || *walk == '\t'); walk--) ;
316 walk[1] = 0;
317 for (walk = name; *walk == ' ' || *walk == '\t'; walk++) ;
318 if (file_cvt(walk, file->dir_ent.name)) {
319 if (file->dir_ent.lcase & FAT_NO_83NAME) {
320 /* as we only assign a new 8.3 filename, reset flag that 8.3 name is not
321 present */
322 file->dir_ent.lcase &= ~FAT_NO_83NAME;
323 /* reset the attributes, only keep DIR and VOLUME */
324 file->dir_ent.attr &= ~(ATTR_DIR | ATTR_VOLUME);
325 fs_write(file->offset, MSDOS_NAME + 2, &file->dir_ent);
326 } else {
327 fs_write(file->offset, MSDOS_NAME, file->dir_ent.name);
328 }
329 if (file->lfn) {
330 lfn_remove(file->lfn_offset, file->offset);
331 file->lfn = NULL;
332 }
333 return;
334 }
335 }
336 }
337 }
338
339 static uint32_t scan_free_entry(DOS_FS * fs, DOS_FILE * this)
340 {
341 uint32_t clu_num, offset;
342 int i;
343 DIR_ENT de;
344
345 i = 2 * sizeof(DIR_ENT); /* Skip '.' and '..' slots */
346 clu_num = FSTART(this, fs);
347 while (clu_num > 0 && clu_num != -1) {
348 offset = cluster_start(fs, clu_num) + (i % fs->cluster_size);
349 fs_read(offset, sizeof(DIR_ENT), &de);
350 if (IS_FREE(de.name))
351 return offset;
352 i += sizeof(DIR_ENT);
353 if (!(i % fs->cluster_size))
354 if ((clu_num = next_cluster(fs, clu_num)) == 0 || clu_num == -1)
355 break;
356 }
357
358 return 0;
359 }
360
361 static int handle_dot(DOS_FS * fs, DOS_FILE * file, int dotdot)
362 {
363 const char *name, *ent;
364 uint32_t new_offset, start;
365
366 if (dotdot) {
367 name = "..";
368 ent = MSDOS_DOTDOT;
369 if (!file->parent->parent) {
370 start = 0;
371 } else {
372 start = FSTART(file->parent->parent, fs);
373 if (start == fs->root_cluster)
374 start = 0;
375 }
376 } else {
377 name = ".";
378 ent = MSDOS_DOT;
379 start = FSTART(file->parent, fs);
380 }
381
382 if (!(file->dir_ent.attr & ATTR_DIR) || (FSTART(file, fs) != start) ||
383 strncmp((const char *)(file->dir_ent.name), ent, MSDOS_NAME)) {
384
385 if (IS_FREE(file->dir_ent.name)) {
386 printf("%s\n Expected a valid '%s' entry in the %s slot, found free entry.\n",
387 path_name(file->parent), name, dotdot ? "second" : "first");
388 switch (get_choice(1, " Creating.",
389 2,
390 1, "Create entry",
391 2, "Drop parent")) {
392 case 1:
393 goto conjure;
394 case 2:
395 drop_file(fs, file->parent);
396 return 1;
397 }
398 }
399
400 if (!strncmp((const char *)(file->dir_ent.name), ent, MSDOS_NAME)) {
401 printf("%s\n Invalid '%s' entry in the %s slot. Fixing.\n",
402 path_name(file->parent), name, dotdot ? "second" : "first");
403 MODIFY_START(file, start, fs);
404 MODIFY(file, attr, ATTR_DIR);
405 } else {
406 printf("%s\n Expected a valid '%s' entry in this slot.\n",
407 path_name(file), name);
408 switch (get_choice(3, " Moving entry down.",
409 3,
410 1, "Drop entry",
411 2, "Drop parent",
412 3, "Move entry down")) {
413 case 1:
414 drop_file(fs, file);
415 goto conjure;
416 case 2:
417 drop_file(fs, file->parent);
418 return 1;
419 case 3:
420 new_offset = scan_free_entry(fs, file->parent);
421 if (!new_offset) {
422 printf("No free entry found.\n");
423 return 0;
424 }
425
426 fs_write(new_offset, sizeof(file->dir_ent), &file->dir_ent);
427 goto conjure;
428 }
429 }
430 }
431 if (file->dir_ent.lcase & FAT_NO_83NAME) {
432 /* Some versions of mtools write these directory entries with random data in
433 this field. */
434 printf("%s\n Is a dot with no 8.3 name flag set, clearing.\n", path_name(file));
435 file->dir_ent.lcase &= ~FAT_NO_83NAME;
436 MODIFY(file, lcase, file->dir_ent.lcase);
437 }
438
439 return 0;
440
441 conjure:
442 memset(&file->dir_ent, 0, sizeof(DIR_ENT));
443 memcpy(file->dir_ent.name, ent, MSDOS_NAME);
444 fs_write(file->offset, sizeof(file->dir_ent), &file->dir_ent);
445 MODIFY_START(file, start, fs);
446 MODIFY(file, attr, ATTR_DIR);
447 return 0;
448 }
449
450 static int check_file(DOS_FS * fs, DOS_FILE * file)
451 {
452 DOS_FILE *owner;
453 int restart;
454 uint32_t parent, grandp, curr, this, clusters, prev, walk, clusters2;
455
456 if (IS_FREE(file->dir_ent.name))
457 return 0;
458
459 if (file->dir_ent.attr & ATTR_DIR) {
460 if (le32toh(file->dir_ent.size)) {
461 printf("%s\n Directory has non-zero size. Fixing it.\n",
462 path_name(file));
463 MODIFY(file, size, htole32(0));
464 }
465 if (FSTART(file, fs) == 0) {
466 printf("%s\n Start does point to root directory. Deleting dir. \n",
467 path_name(file));
468 MODIFY(file, name[0], DELETED_FLAG);
469 return 0;
470 }
471 if (file->parent) {
472 parent = FSTART(file->parent, fs);
473 grandp = file->parent->parent ? FSTART(file->parent->parent, fs) : 0;
474 if (fs->root_cluster && grandp == fs->root_cluster)
475 grandp = 0;
476
477 if (FSTART(file, fs) == parent) {
478 printf("%s\n Start does point to containing directory. Deleting entry.\n",
479 path_name(file));
480 MODIFY(file, name[0], DELETED_FLAG);
481 MODIFY_START(file, 0, fs);
482 return 0;
483 }
484 if (FSTART(file, fs) == grandp) {
485 printf("%s\n Start does point to containing directory's parent. Deleting entry.\n",
486 path_name(file));
487 MODIFY(file, name[0], DELETED_FLAG);
488 MODIFY_START(file, 0, fs);
489 return 0;
490 }
491 }
492 }
493 if (FSTART(file, fs) == 1) {
494 printf("%s\n Bad start cluster 1. Truncating file.\n",
495 path_name(file));
496 if (!file->offset)
497 die("Bad FAT32 root directory! (bad start cluster 1)\n");
498 MODIFY_START(file, 0, fs);
499 }
500 if (FSTART(file, fs) >= fs->data_clusters + 2) {
501 printf
502 ("%s\n Start cluster beyond limit (%lu > %lu). Truncating file.\n",
503 path_name(file), (unsigned long)FSTART(file, fs),
504 (unsigned long)(fs->data_clusters + 1));
505 if (!file->offset)
506 die("Bad FAT32 root directory! (start cluster beyond limit: %lu > %lu)\n",
507 (unsigned long)FSTART(file, fs),
508 (unsigned long)(fs->data_clusters + 1));
509 MODIFY_START(file, 0, fs);
510 }
511 clusters = prev = 0;
512 for (curr = FSTART(file, fs) ? FSTART(file, fs) :
513 -1; curr != -1; curr = next_cluster(fs, curr)) {
514 FAT_ENTRY curEntry;
515 get_fat(&curEntry, fs->fat, curr, fs);
516
517 if (!curEntry.value || bad_cluster(fs, curr)) {
518 printf("%s\n Contains a %s cluster (%lu). Assuming EOF.\n",
519 path_name(file), curEntry.value ? "bad" : "free", (unsigned long)curr);
520 if (prev)
521 set_fat(fs, prev, -1);
522 else if (!file->offset)
523 die("FAT32 root dir starts with a bad cluster!");
524 else
525 MODIFY_START(file, 0, fs);
526 break;
527 }
528 if (!(file->dir_ent.attr & ATTR_DIR) && le32toh(file->dir_ent.size) <=
529 clusters * fs->cluster_size) {
530 printf
531 ("%s\n File size is %u bytes, cluster chain length is > %u "
532 "bytes.\n Truncating file to %u bytes.\n", path_name(file),
533 le32toh(file->dir_ent.size),
534 (unsigned)clusters * fs->cluster_size,
535 le32toh(file->dir_ent.size));
536 truncate_file(fs, file, clusters);
537 break;
538 }
539 if ((owner = get_owner(fs, curr))) {
540 int do_trunc = 0;
541 printf("%s and\n", path_name(owner));
542 printf("%s\n share clusters.\n", path_name(file));
543 clusters2 = 0;
544 for (walk = FSTART(owner, fs); walk > 0 && walk != -1; walk =
545 next_cluster(fs, walk))
546 if (walk == curr)
547 break;
548 else {
549 if ((unsigned long long)clusters2 * fs->cluster_size >= UINT32_MAX)
550 die("Internal error: File size is larger than 2^32-1");
551 clusters2++;
552 }
553 restart = file->dir_ent.attr & ATTR_DIR;
554 if (!owner->offset) {
555 printf(" Truncating second to %u bytes because first "
556 "is FAT32 root dir.\n",
557 (unsigned)clusters * fs->cluster_size);
558 do_trunc = 2;
559 } else if (!file->offset) {
560 printf(" Truncating first to %u bytes because second "
561 "is FAT32 root dir.\n",
562 (unsigned)clusters2 * fs->cluster_size);
563 do_trunc = 1;
564 } else {
565 char *trunc_first_string;
566 char *trunc_second_string;
567 char *noninteractive_string;
568
569 xasprintf(&trunc_first_string,
570 "Truncate first to %u bytes%s",
571 (unsigned)clusters2 * fs->cluster_size,
572 restart ? " and restart" : "");
573 xasprintf(&trunc_second_string,
574 "Truncate second to %u bytes",
575 (unsigned)clusters * fs->cluster_size);
576 xasprintf(&noninteractive_string,
577 " Truncating second to %u bytes.",
578 (unsigned)clusters * fs->cluster_size);
579
580 do_trunc = get_choice(2, noninteractive_string,
581 2,
582 1, trunc_first_string,
583 2, trunc_second_string);
584
585 free(trunc_first_string);
586 free(trunc_second_string);
587 free(noninteractive_string);
588 }
589
590 if (do_trunc == 1) {
591 prev = 0;
592 clusters = 0;
593 for (this = FSTART(owner, fs); this > 0 && this != -1; this =
594 next_cluster(fs, this)) {
595 if (this == curr) {
596 if (prev)
597 set_fat(fs, prev, -1);
598 else
599 MODIFY_START(owner, 0, fs);
600 MODIFY(owner, size, htole32(clusters * fs->cluster_size));
601 if (restart)
602 return 1;
603 while (this > 0 && this != -1) {
604 set_owner(fs, this, NULL);
605 this = next_cluster(fs, this);
606 }
607 this = curr;
608 break;
609 }
610 if ((unsigned long long)clusters * fs->cluster_size >= UINT32_MAX)
611 die("Internal error: File size is larger than 2^32-1");
612 clusters++;
613 prev = this;
614 }
615 if (this != curr)
616 die("Internal error: didn't find cluster %d in chain"
617 " starting at %d", curr, FSTART(owner, fs));
618 } else {
619 if (prev)
620 set_fat(fs, prev, -1);
621 else
622 MODIFY_START(file, 0, fs);
623 break;
624 }
625 }
626 set_owner(fs, curr, file);
627 if ((unsigned long long)clusters * fs->cluster_size >= UINT32_MAX)
628 die("Internal error: File size is larger than 2^32-1");
629 clusters++;
630 prev = curr;
631 }
632 if (!(file->dir_ent.attr & ATTR_DIR) && le32toh(file->dir_ent.size) >
633 clusters * fs->cluster_size) {
634 printf
635 ("%s\n File size is %u bytes, cluster chain length is %u bytes."
636 "\n Truncating file to %u bytes.\n", path_name(file),
637 le32toh(file->dir_ent.size),
638 (unsigned)clusters * fs->cluster_size,
639 (unsigned)clusters * fs->cluster_size);
640 MODIFY(file, size,
641 htole32(clusters * fs->cluster_size));
642 }
643 return 0;
644 }
645
646 static int check_files(DOS_FS * fs, DOS_FILE * start)
647 {
648 while (start) {
649 if (check_file(fs, start))
650 return 1;
651 start = start->next;
652 }
653 return 0;
654 }
655
656 static int check_dir(DOS_FS * fs, DOS_FILE ** root, int dots)
657 {
658 DOS_FILE *parent, **walk, **scan;
659 int skip, redo;
660 int good, bad;
661
662 if (!*root)
663 return 0;
664 parent = (*root)->parent;
665 good = bad = 0;
666 for (walk = root; *walk; walk = &(*walk)->next)
667 if (bad_name(*walk))
668 bad++;
669 else
670 good++;
671 if (*root && parent && good + bad > 4 && bad > good / 2) {
672 printf("%s\n Has a large number of bad entries. (%d/%d)\n",
673 path_name(parent), bad, good + bad);
674 if (!dots)
675 printf(" Not dropping root directory.\n");
676 else if (get_choice(2, " Not dropping it in auto-mode.",
677 2,
678 1, "Drop directory",
679 2, "Keep directory") == 1) {
680 truncate_file(fs, parent, 0);
681 MODIFY(parent, name[0], DELETED_FLAG);
682 /* buglet: deleted directory stays in the list. */
683 return 1;
684 }
685 }
686 redo = 0;
687 walk = root;
688 while (*walk) {
689 if (!((*walk)->dir_ent.attr & ATTR_VOLUME) && bad_name(*walk)) {
690 puts(path_name(*walk));
691 printf(" Bad short file name (%s).\n",
692 file_name((*walk)->dir_ent.name));
693 switch (get_choice(3, " Auto-renaming it.",
694 4,
695 1, "Drop file",
696 2, "Rename file",
697 3, "Auto-rename",
698 4, "Keep it")) {
699 case 1:
700 drop_file(fs, *walk);
701 walk = &(*walk)->next;
702 continue;
703 case 2:
704 rename_file(*walk);
705 redo = 1;
706 break;
707 case 3:
708 auto_rename(*walk);
709 printf(" Renamed to %s\n", file_name((*walk)->dir_ent.name));
710 break;
711 case 4:
712 break;
713 }
714 }
715 /* don't check for duplicates of the volume label */
716 if (!((*walk)->dir_ent.attr & ATTR_VOLUME)) {
717 scan = &(*walk)->next;
718 skip = 0;
719 while (*scan && !skip) {
720 if (!((*scan)->dir_ent.attr & ATTR_VOLUME) &&
721 !memcmp((*walk)->dir_ent.name, (*scan)->dir_ent.name,
722 MSDOS_NAME)) {
723 printf("%s\n Duplicate directory entry.\n First %s\n",
724 path_name(*walk), file_stat(*walk));
725 printf(" Second %s\n", file_stat(*scan));
726 switch (get_choice(6, " Auto-renaming second.",
727 6,
728 1, "Drop first",
729 2, "Drop second",
730 3, "Rename first",
731 4, "Rename second",
732 5, "Auto-rename first",
733 6, "Auto-rename second")) {
734 case 1:
735 drop_file(fs, *walk);
736 *walk = (*walk)->next;
737 skip = 1;
738 break;
739 case 2:
740 drop_file(fs, *scan);
741 *scan = (*scan)->next;
742 continue;
743 case 3:
744 rename_file(*walk);
745 printf(" Renamed to %s\n", path_name(*walk));
746 redo = 1;
747 break;
748 case 4:
749 rename_file(*scan);
750 printf(" Renamed to %s\n", path_name(*walk));
751 redo = 1;
752 break;
753 case 5:
754 auto_rename(*walk);
755 printf(" Renamed to %s\n",
756 file_name((*walk)->dir_ent.name));
757 break;
758 case 6:
759 auto_rename(*scan);
760 printf(" Renamed to %s\n",
761 file_name((*scan)->dir_ent.name));
762 break;
763 }
764 }
765 scan = &(*scan)->next;
766 }
767 if (skip)
768 continue;
769 }
770 if (!redo)
771 walk = &(*walk)->next;
772 else {
773 walk = root;
774 redo = 0;
775 }
776 }
777 return 0;
778 }
779
780 /**
781 * Check a dentry's cluster chain for bad clusters.
782 * If requested, we verify readability and mark unreadable clusters as bad.
783 *
784 * @param[inout] fs Information about the filesystem
785 * @param[in] file dentry to check
786 * @param[in] read_test Nonzero == verify that dentry's clusters can
787 * be read
788 */
789 static void test_file(DOS_FS * fs, DOS_FILE * file, int read_test)
790 {
791 DOS_FILE *owner;
792 uint32_t walk, prev, clusters, next_clu;
793
794 prev = clusters = 0;
795 for (walk = FSTART(file, fs); walk > 1 && walk < fs->data_clusters + 2;
796 walk = next_clu) {
797 next_clu = next_cluster(fs, walk);
798
799 /* In this stage we are checking only for a loop within our own
800 * cluster chain.
801 * Cross-linking of clusters is handled in check_file()
802 */
803 if ((owner = get_owner(fs, walk))) {
804 if (owner == file) {
805 printf("%s\n Circular cluster chain. Truncating to %lu "
806 "cluster%s.\n", path_name(file), (unsigned long)clusters,
807 clusters == 1 ? "" : "s");
808 if (prev)
809 set_fat(fs, prev, -1);
810 else if (!file->offset)
811 die("Bad FAT32 root directory! (bad start cluster)\n");
812 else
813 MODIFY_START(file, 0, fs);
814 }
815 break;
816 }
817 if (bad_cluster(fs, walk))
818 break;
819 if (read_test) {
820 if (fs_test(cluster_start(fs, walk), fs->cluster_size)) {
821 prev = walk;
822 clusters++;
823 } else {
824 printf("%s\n Cluster %lu (%lu) is unreadable. Skipping it.\n",
825 path_name(file), (unsigned long)clusters, (unsigned long)walk);
826 if (prev)
827 set_fat(fs, prev, next_cluster(fs, walk));
828 else
829 MODIFY_START(file, next_cluster(fs, walk), fs);
830 set_fat(fs, walk, -2);
831 }
832 } else {
833 prev = walk;
834 clusters++;
835 }
836 set_owner(fs, walk, file);
837 }
838 /* Revert ownership (for now) */
839 for (walk = FSTART(file, fs); walk > 1 && walk < fs->data_clusters + 2;
840 walk = next_cluster(fs, walk))
841 if (bad_cluster(fs, walk))
842 break;
843 else if (get_owner(fs, walk) == file)
844 set_owner(fs, walk, NULL);
845 else
846 break;
847 }
848
849 static void undelete(DOS_FS * fs, DOS_FILE * file)
850 {
851 uint32_t clusters, left, prev, walk;
852
853 clusters = left = (le32toh(file->dir_ent.size) + fs->cluster_size - 1) /
854 fs->cluster_size;
855 prev = 0;
856
857 walk = FSTART(file, fs);
858
859 while (left && (walk >= 2) && (walk < fs->data_clusters + 2)) {
860
861 FAT_ENTRY curEntry;
862 get_fat(&curEntry, fs->fat, walk, fs);
863
864 if (!curEntry.value)
865 break;
866
867 left--;
868 if (prev)
869 set_fat(fs, prev, walk);
870 prev = walk;
871 walk++;
872 }
873 if (prev)
874 set_fat(fs, prev, -1);
875 else
876 MODIFY_START(file, 0, fs);
877 if (left)
878 printf("Warning: Did only undelete %lu of %lu cluster%s.\n",
879 (unsigned long)clusters - left, (unsigned long)clusters, clusters == 1 ? "" : "s");
880
881 }
882
883 static void new_dir(void)
884 {
885 lfn_reset();
886 }
887
888 /**
889 * Create a description for a referenced dentry and insert it in our dentry
890 * tree. Then, go check the dentry's cluster chain for bad clusters and
891 * cluster loops.
892 *
893 * @param[inout] fs Information about the filesystem
894 * @param[out] chain
895 * @param[in] parent Information about parent directory of this file
896 * NULL == no parent ('file' is root directory)
897 * @param[in] offset Partition-relative byte offset of directory entry of interest
898 * 0 == Root directory
899 * @param cp
900 */
901 static void add_file(DOS_FS * fs, DOS_FILE *** chain, DOS_FILE * parent,
902 off_t offset, FDSC ** cp)
903 {
904 DOS_FILE *new;
905 DIR_ENT de;
906 FD_TYPE type;
907
908 if (offset)
909 fs_read(offset, sizeof(DIR_ENT), &de);
910 else {
911 /* Construct a DIR_ENT for the root directory */
912 memset(&de, 0, sizeof de);
913 memcpy(de.name, " ", MSDOS_NAME);
914 de.attr = ATTR_DIR;
915 de.start = htole16(fs->root_cluster & 0xffff);
916 de.starthi = htole16((fs->root_cluster >> 16) & 0xffff);
917 }
918 if ((type = file_type(cp, (char *)de.name)) != fdt_none) {
919 if (type == fdt_undelete && (de.attr & ATTR_DIR))
920 die("Can't undelete directories.");
921 file_modify(cp, (char *)de.name);
922 fs_write(offset, 1, &de);
923 }
924 if (IS_FREE(de.name)) {
925 lfn_check_orphaned();
926 return;
927 }
928 if (de.attr == VFAT_LN_ATTR) {
929 lfn_add_slot(&de, offset);
930 return;
931 }
932 new = qalloc(&mem_queue, sizeof(DOS_FILE));
933 new->lfn = lfn_get(&de, &new->lfn_offset);
934 new->offset = offset;
935 memcpy(&new->dir_ent, &de, sizeof(de));
936 new->next = new->first = NULL;
937 new->parent = parent;
938 if (type == fdt_undelete)
939 undelete(fs, new);
940 **chain = new;
941 *chain = &new->next;
942 if (list) {
943 printf("Checking file %s", path_name(new));
944 if (new->lfn)
945 printf(" (%s)", file_name(new->dir_ent.name)); /* (8.3) */
946 printf("\n");
947 }
948 /* Don't include root directory in the total file count */
949 if (offset)
950 ++n_files;
951 test_file(fs, new, test); /* Bad cluster check */
952 }
953
954 static int subdirs(DOS_FS * fs, DOS_FILE * parent, FDSC ** cp);
955
956 static int scan_dir(DOS_FS * fs, DOS_FILE * this, FDSC ** cp)
957 {
958 DOS_FILE **chain;
959 int i;
960 uint32_t clu_num;
961
962 chain = &this->first;
963 i = 0;
964 clu_num = FSTART(this, fs);
965 new_dir();
966 if (clu_num != 0 && clu_num != -1 && this->offset) {
967 DOS_FILE file;
968
969 file.lfn = NULL;
970 file.lfn_offset = 0;
971 file.next = NULL;
972 file.parent = this;
973 file.first = NULL;
974
975 file.offset = cluster_start(fs, clu_num) + (i % fs->cluster_size);
976 fs_read(file.offset, sizeof(DIR_ENT), &file.dir_ent);
977 if (handle_dot(fs, &file, 0))
978 return 1;
979 i += sizeof(DIR_ENT);
980
981 file.offset = cluster_start(fs, clu_num) + (i % fs->cluster_size);
982 fs_read(file.offset, sizeof(DIR_ENT), &file.dir_ent);
983 if (handle_dot(fs, &file, 1))
984 return 1;
985 i += sizeof(DIR_ENT);
986 }
987 while (clu_num > 0 && clu_num != -1) {
988 add_file(fs, &chain, this,
989 cluster_start(fs, clu_num) + (i % fs->cluster_size), cp);
990 i += sizeof(DIR_ENT);
991 if (!(i % fs->cluster_size))
992 if ((clu_num = next_cluster(fs, clu_num)) == 0 || clu_num == -1)
993 break;
994 }
995 lfn_check_orphaned();
996 if (check_dir(fs, &this->first, this->offset))
997 return 0;
998 if (check_files(fs, this->first))
999 return 1;
1000 return subdirs(fs, this, cp);
1001 }
1002
1003 /**
1004 * Recursively scan subdirectories of the specified parent directory.
1005 *
1006 * @param[inout] fs Information about the filesystem
1007 * @param[in] parent Identifies the directory to scan
1008 * @param[in] cp
1009 *
1010 * @return 0 Success
1011 * @return 1 Error
1012 */
1013 static int subdirs(DOS_FS * fs, DOS_FILE * parent, FDSC ** cp)
1014 {
1015 DOS_FILE *walk;
1016
1017 for (walk = parent ? parent->first : root; walk; walk = walk->next)
1018 if (!IS_FREE(walk->dir_ent.name) && (walk->dir_ent.attr & ATTR_DIR))
1019 if (scan_dir(fs, walk, file_cd(cp, (char *)walk->dir_ent.name)))
1020 return 1;
1021 return 0;
1022 }
1023
1024 /**
1025 * Scan all directory and file information for errors.
1026 *
1027 * @param[inout] fs Information about the filesystem
1028 *
1029 * @return 0 Success
1030 * @return 1 Error
1031 */
1032 int scan_root(DOS_FS * fs)
1033 {
1034 DOS_FILE **chain;
1035 int i;
1036
1037 root = NULL;
1038 chain = &root;
1039 new_dir();
1040 if (fs->root_cluster) {
1041 add_file(fs, &chain, NULL, 0, &fp_root);
1042 } else {
1043 for (i = 0; i < fs->root_entries; i++)
1044 add_file(fs, &chain, NULL, fs->root_start + i * sizeof(DIR_ENT),
1045 &fp_root);
1046 }
1047 lfn_check_orphaned();
1048 (void)check_dir(fs, &root, 0);
1049 if (check_files(fs, root))
1050 return 1;
1051 return subdirs(fs, NULL, &fp_root);
1052 }
1053
1054 static char print_fat_dirty_state(void)
1055 {
1056 printf("Dirty bit is set. Fs was not properly unmounted and"
1057 " some data may be corrupt.\n");
1058
1059 return get_choice(1, " Automatically removing dirty bit.",
1060 2,
1061 1, "Remove dirty bit",
1062 2, "No action");
1063 }
1064
1065 void check_dirty_bits(DOS_FS * fs)
1066 {
1067 if (fs->fat_bits == 32) {
1068 struct boot_sector b32;
1069 FAT_ENTRY fat32_flags;
1070
1071 get_fat(&fat32_flags, fs->fat, 1, fs);
1072 fs_read(0, sizeof(b32), &b32);
1073
1074 if ((b32.boot_flags & FAT_STATE_DIRTY) || !(fat32_flags.value & FAT32_FLAG_CLEAN_SHUTDOWN)) {
1075 if (print_fat_dirty_state() == 1) {
1076 if (b32.boot_flags & FAT_STATE_DIRTY) {
1077 b32.boot_flags &= ~FAT_STATE_DIRTY;
1078 fs_write(0, sizeof(b32), &b32);
1079 }
1080 if (!(fat32_flags.value & FAT32_FLAG_CLEAN_SHUTDOWN)) {
1081 uint32_t *new_flags_ptr = (uint32_t *)(fs->fat + 4);
1082 *new_flags_ptr = htole32(fat32_flags.value | FAT32_FLAG_CLEAN_SHUTDOWN | (fat32_flags.reserved << 28));
1083 fs_write(fs->fat_start + 4, 4, new_flags_ptr);
1084 if (fs->nfats > 1)
1085 fs_write(fs->fat_start + 4 + fs->fat_size, 4, new_flags_ptr);
1086 }
1087 }
1088 }
1089 } else {
1090 struct boot_sector_16 b16;
1091 FAT_ENTRY fat16_flags;
1092 int fat16_is_dirty = 0;
1093
1094 fs_read(0, sizeof(b16), &b16);
1095
1096 if (fs->fat_bits == 16) {
1097 get_fat(&fat16_flags, fs->fat, 1, fs);
1098 fat16_is_dirty = !(fat16_flags.value & FAT16_FLAG_CLEAN_SHUTDOWN);
1099 }
1100
1101 if ((b16.boot_flags & FAT_STATE_DIRTY) || fat16_is_dirty) {
1102 if (print_fat_dirty_state() == 1) {
1103 if (b16.boot_flags & FAT_STATE_DIRTY) {
1104 b16.boot_flags &= ~FAT_STATE_DIRTY;
1105 fs_write(0, sizeof(b16), &b16);
1106 }
1107 if (fat16_is_dirty) {
1108 uint16_t *new_flags_ptr = (uint16_t *)(fs->fat + 2);
1109 *new_flags_ptr = htole16(fat16_flags.value | FAT16_FLAG_CLEAN_SHUTDOWN);
1110 fs_write(fs->fat_start + 2, 2, new_flags_ptr);
1111 if (fs->nfats > 1)
1112 fs_write(fs->fat_start + 2 + fs->fat_size, 2, new_flags_ptr);
1113 }
1114 }
1115 }
1116 }
1117 }
1118
1119 static void get_new_label(char doslabel[12])
1120 {
1121 char newlabel[256];
1122 size_t len;
1123 char *p;
1124 int ret;
1125 int i;
1126
1127 while (1) {
1128 if (get_line("New label", newlabel, sizeof(newlabel))) {
1129 if ((p = strchr(newlabel, '\n')))
1130 *p = 0;
1131
1132 len = mbstowcs(NULL, newlabel, 0);
1133 if (len != (size_t)-1 && len > 11) {
1134 printf("Label can be no longer than 11 characters\n");
1135 continue;
1136 }
1137
1138 if (!local_string_to_dos_string(doslabel, newlabel, 12)) {
1139 printf("Error when processing label\n");
1140 continue;
1141 }
1142
1143 for (i = strlen(doslabel); i < 11; ++i)
1144 doslabel[i] = ' ';
1145 doslabel[11] = 0;
1146
1147 ret = validate_volume_label(doslabel);
1148 if ((ret && only_uppercase_label) || (ret & ~0x1)) {
1149 printf("New label is invalid\n");
1150 continue;
1151 } else if (ret & 0x1) {
1152 printf("Warning: lowercase labels might not work properly on some systems\n");
1153 }
1154
1155 break;
1156 }
1157 }
1158 }
1159
1160 static int check_boot_label(DOS_FS *fs)
1161 {
1162 char doslabel[12];
1163 wchar_t wlabel[12];
1164 int ret;
1165 int i;
1166
1167 ret = validate_volume_label(fs->label);
1168 if (ret & ~0x1) {
1169 printf("Label '%s' stored in boot sector is not valid.\n", pretty_label(fs->label));
1170 switch (get_choice(1, " Auto-removing label from boot sector.",
1171 2,
1172 1, "Remove invalid label from boot sector",
1173 2, "Enter new label")) {
1174 case 1:
1175 write_boot_label(fs, "NO NAME ");
1176 memcpy(fs->label, "NO NAME ", 11);
1177 return 1;
1178 case 2:
1179 get_new_label(doslabel);
1180 write_boot_label(fs, doslabel);
1181 memcpy(fs->label, doslabel, 11);
1182 return 1;
1183 }
1184 } else if ((ret & 0x1) && only_uppercase_label) {
1185 printf("Label '%s' stored in boot sector contains lowercase characters.\n", pretty_label(fs->label));
1186 switch (get_choice(1, " Auto-changing lowercase characters to uppercase",
1187 3,
1188 1, "Change lowercase characters to uppercase",
1189 2, "Remove invalid label",
1190 2, "Set new label")) {
1191 case 1:
1192 if (!dos_string_to_wchar_string(wlabel, fs->label, sizeof(wlabel)))
1193 die("Cannot change lowercase characters to uppercase.");
1194 for (i = 0; i < 11; ++i)
1195 wlabel[i] = towupper(wlabel[i]);
1196 if (!wchar_string_to_dos_string(doslabel, wlabel, sizeof(doslabel)))
1197 die("Cannot change lowercase characters to uppercase.");
1198 write_boot_label(fs, doslabel);
1199 memcpy(fs->label, doslabel, 11);
1200 return 1;
1201 case 2:
1202 write_boot_label(fs, "NO NAME ");
1203 memcpy(fs->label, "NO NAME ", 11);
1204 return 1;
1205 case 3:
1206 get_new_label(doslabel);
1207 write_boot_label(fs, doslabel);
1208 memcpy(fs->label, doslabel, 11);
1209 return 1;
1210 }
1211 }
1212
1213 return 0;
1214 }
1215
1216 void check_label(DOS_FS *fs)
1217 {
1218 DIR_ENT de;
1219 off_t offset;
1220 char buffer[256];
1221 char doslabel[12];
1222 wchar_t wlabel[12];
1223 int ret;
1224 int i;
1225
1226 offset = find_volume_de(fs, &de);
1227
1228 if (offset == 0 && memcmp(fs->label, "NO NAME ", 11) != 0)
1229 check_boot_label(fs);
1230
1231 if (offset == 0 && memcmp(fs->label, "NO NAME ", 11) != 0) {
1232 printf("Label in boot sector is '%s', but there is no volume label in root directory.\n", pretty_label(fs->label));
1233 switch (get_choice(1, " Auto-removing label from boot sector.",
1234 2,
1235 1, "Remove label from boot sector",
1236 2, "Copy label from boot sector to root directory")) {
1237 case 1:
1238 write_boot_label(fs, "NO NAME ");
1239 memcpy(fs->label, "NO NAME ", 11);
1240 break;
1241 case 2:
1242 write_volume_label(fs, fs->label);
1243 offset = find_volume_de(fs, &de);
1244 break;
1245 }
1246 }
1247
1248 if (offset != 0) {
1249 memcpy(doslabel, de.name, 11);
1250 if (doslabel[0] == 0x05)
1251 doslabel[0] = 0xe5;
1252 ret = validate_volume_label(doslabel);
1253 if (ret & ~0x1) {
1254 printf("Volume label '%s' stored in root directory is not valid.\n", pretty_label(doslabel));
1255 switch (get_choice(1, " Auto-removing label.",
1256 2,
1257 1, "Remove invalid label",
1258 2, "Set new label")) {
1259 case 1:
1260 remove_label(fs);
1261 memcpy(fs->label, "NO NAME ", 11);
1262 offset = 0;
1263 break;
1264 case 2:
1265 get_new_label(doslabel);
1266 write_label(fs, doslabel);
1267 memcpy(fs->label, doslabel, 11);
1268 break;
1269 }
1270 } else if ((ret & 0x1) && only_uppercase_label) {
1271 printf("Volume label '%s' stored in root directory contains lowercase characters.\n", pretty_label(doslabel));
1272 switch (get_choice(1, " Auto-changing lowercase characters to uppercase",
1273 3,
1274 1, "Change lowercase characters to uppercase",
1275 2, "Remove invalid label",
1276 2, "Set new label")) {
1277 case 1:
1278 if (!dos_string_to_wchar_string(wlabel, doslabel, sizeof(wlabel)))
1279 die("Cannot change lowercase characters to uppercase.");
1280 for (i = 0; i < 11; ++i)
1281 wlabel[i] = towupper(wlabel[i]);
1282 if (!wchar_string_to_dos_string(doslabel, wlabel, sizeof(doslabel)))
1283 die("Cannot change lowercase characters to uppercase.");
1284 write_label(fs, doslabel);
1285 memcpy(fs->label, doslabel, 11);
1286 break;
1287 case 2:
1288 remove_label(fs);
1289 memcpy(fs->label, "NO NAME ", 11);
1290 offset = 0;
1291 break;
1292 case 3:
1293 get_new_label(doslabel);
1294 write_label(fs, doslabel);
1295 memcpy(fs->label, doslabel, 11);
1296 break;
1297 }
1298 }
1299 }
1300
1301 again:
1302
1303 if (offset != 0 && memcmp(fs->label, "NO NAME ", 11) == 0 && memcmp(doslabel, "NO NAME ", 11) != 0) {
1304 printf("There is no label in boot sector, but there is volume label '%s' stored in root directory\n", pretty_label(doslabel));
1305 switch (get_choice(1, " Auto-copying volume label from root directory to boot sector.",
1306 2,
1307 1, "Copy volume label from root directory to boot sector",
1308 2, "Remove volume label from root directory")) {
1309 case 1:
1310 write_boot_label(fs, doslabel);
1311 memcpy(fs->label, doslabel, 11);
1312 break;
1313 case 2:
1314 remove_label(fs);
1315 offset = 0;
1316 break;
1317 }
1318 }
1319
1320 if (offset != 0 && memcmp(fs->label, "NO NAME ", 11) != 0 && memcmp(fs->label, doslabel, 11) != 0) {
1321 strncpy(buffer, pretty_label(doslabel), sizeof(buffer)-1);
1322 buffer[sizeof(buffer)-1] = 0;
1323 printf("Volume label '%s' stored in root directory and label '%s' stored in boot sector and different.\n", buffer, pretty_label(fs->label));
1324 switch (get_choice(1, " Auto-copying volume label from root directory to boot sector.",
1325 2,
1326 1, "Copy volume label from root directory to boot sector",
1327 2, "Copy label from boot sector to root directory")) {
1328 case 1:
1329 write_boot_label(fs, doslabel);
1330 memcpy(fs->label, doslabel, 11);
1331 break;
1332 case 2:
1333 ret = check_boot_label(fs);
1334 if (ret)
1335 goto again;
1336 write_volume_label(fs, fs->label);
1337 offset = find_volume_de(fs, &de);
1338 /* NOTE: doslabel is not updated */
1339 break;
1340 }
1341 }
1342 }