import source from 3.0.10
[external/dosfstools.git] / src / check.c
1 /* check.c - Check and repair a PC/MS-DOS file system
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
6    This program is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation, either version 3 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19    On Debian systems, the complete text of the GNU General Public License
20    can be found in /usr/share/common-licenses/GPL-3 file.
21 */
22
23 /* FAT32, VFAT, Atari format support, and various fixes additions May 1998
24  * by Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de> */
25
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <limits.h>
31 #include <time.h>
32
33 #include "common.h"
34 #include "dosfsck.h"
35 #include "io.h"
36 #include "fat.h"
37 #include "file.h"
38 #include "lfn.h"
39 #include "check.h"
40
41
42 static DOS_FILE *root;
43
44 /* get start field of a dir entry */
45 #define FSTART(p,fs) \
46   ((unsigned long)CF_LE_W(p->dir_ent.start) | \
47    (fs->fat_bits == 32 ? CF_LE_W(p->dir_ent.starthi) << 16 : 0))
48
49 #define MODIFY(p,i,v)                                   \
50   do {                                                  \
51     if (p->offset) {                                    \
52         p->dir_ent.i = v;                               \
53         fs_write(p->offset+offsetof(DIR_ENT,i),         \
54                  sizeof(p->dir_ent.i),&p->dir_ent.i);   \
55     }                                                   \
56   } while(0)
57
58 #define MODIFY_START(p,v,fs)                                            \
59   do {                                                                  \
60     unsigned long __v = (v);                                            \
61     if (!p->offset) {                                                   \
62         /* writing to fake entry for FAT32 root dir */                  \
63         if (!__v) die("Oops, deleting FAT32 root dir!");                \
64         fs->root_cluster = __v;                                         \
65         p->dir_ent.start = CT_LE_W(__v&0xffff);                         \
66         p->dir_ent.starthi = CT_LE_W(__v>>16);                          \
67         __v = CT_LE_L(__v);                                             \
68         fs_write((loff_t)offsetof(struct boot_sector,root_cluster),     \
69                  sizeof(((struct boot_sector *)0)->root_cluster),       \
70                  &__v);                                                 \
71     }                                                                   \
72     else {                                                              \
73         MODIFY(p,start,CT_LE_W((__v)&0xffff));                          \
74         if (fs->fat_bits == 32)                                         \
75             MODIFY(p,starthi,CT_LE_W((__v)>>16));                       \
76     }                                                                   \
77   } while(0)
78
79
80 loff_t alloc_rootdir_entry(DOS_FS *fs, DIR_ENT *de, const char *pattern)
81 {
82     static int curr_num = 0;
83     loff_t offset;
84
85     if (fs->root_cluster) {
86         DIR_ENT d2;
87         int i = 0, got = 0;
88         unsigned long clu_num, prev = 0;
89         loff_t offset2;
90
91         clu_num = fs->root_cluster;
92         offset = cluster_start(fs,clu_num);
93         while (clu_num > 0 && clu_num != -1) {
94             fs_read(offset,sizeof(DIR_ENT),&d2);
95             if (IS_FREE(d2.name) && d2.attr != VFAT_LN_ATTR) {
96                 got = 1;
97                 break;
98             }
99             i += sizeof(DIR_ENT);
100             offset += sizeof(DIR_ENT);
101             if ((i % fs->cluster_size) == 0) {
102                 prev = clu_num;
103                 if ((clu_num = next_cluster(fs,clu_num)) == 0 || clu_num == -1)
104                     break;
105                 offset = cluster_start(fs,clu_num);
106             }
107         }
108         if (!got) {
109             /* no free slot, need to extend root dir: alloc next free cluster
110              * after previous one */
111             if (!prev)
112                 die("Root directory has no cluster allocated!");
113             for (clu_num = prev+1; clu_num != prev; clu_num++) {
114                 FAT_ENTRY entry;
115
116                 if (clu_num >= fs->clusters+2) clu_num = 2;
117                 get_fat(&entry, fs->fat, clu_num, fs);
118                 if (!entry.value)
119                     break;
120             }
121             if (clu_num == prev)
122                 die("Root directory full and no free cluster");
123             set_fat(fs,prev,clu_num);
124             set_fat(fs,clu_num,-1);
125             set_owner(fs, clu_num, get_owner(fs, fs->root_cluster));
126             /* clear new cluster */
127             memset( &d2, 0, sizeof(d2) );
128             offset = cluster_start(fs,clu_num);
129             for( i = 0; i < fs->cluster_size; i += sizeof(DIR_ENT) )
130                 fs_write( offset+i, sizeof(d2), &d2 );
131         }
132         memset(de,0,sizeof(DIR_ENT));
133         while (1) {
134             char expanded[12];
135             sprintf(expanded, pattern, curr_num);
136             memcpy(de->name+4, expanded, 4);
137             memcpy(de->ext, expanded+4, 3);
138             clu_num = fs->root_cluster;
139             i = 0;
140             offset2 = cluster_start(fs,clu_num);
141             while (clu_num > 0 && clu_num != -1) {
142                 fs_read(offset2,sizeof(DIR_ENT),&d2);
143                 if (offset2 != offset &&
144                     !strncmp(d2.name,de->name,MSDOS_NAME))
145                     break;
146                 i += sizeof(DIR_ENT);
147                 offset2 += sizeof(DIR_ENT);
148                 if ((i % fs->cluster_size) == 0) {
149                     if ((clu_num = next_cluster(fs,clu_num)) == 0 ||
150                         clu_num == -1)
151                         break;
152                     offset2 = cluster_start(fs,clu_num);
153                 }
154             }
155             if (clu_num == 0 || clu_num == -1)
156                 break;
157             if (++curr_num >= 10000) die("Unable to create unique name");
158         }
159     }
160     else {
161         DIR_ENT *root;
162         int next_free = 0, scan;
163
164         root = alloc(fs->root_entries*sizeof(DIR_ENT));
165         fs_read(fs->root_start,fs->root_entries*sizeof(DIR_ENT),root);
166
167         while (next_free < fs->root_entries)
168             if (IS_FREE(root[next_free].name) &&
169                 root[next_free].attr != VFAT_LN_ATTR)
170                 break;
171             else next_free++;
172         if (next_free == fs->root_entries)
173             die("Root directory is full.");
174         offset = fs->root_start+next_free*sizeof(DIR_ENT);
175         memset(de,0,sizeof(DIR_ENT));
176         while (1) {
177             sprintf(de->name,pattern,curr_num);
178             for (scan = 0; scan < fs->root_entries; scan++)
179                 if (scan != next_free &&
180                     !strncmp(root[scan].name,de->name,MSDOS_NAME))
181                     break;
182             if (scan == fs->root_entries) break;
183             if (++curr_num >= 10000) die("Unable to create unique name");
184         }
185         free(root);
186     }
187     ++n_files;
188     return offset;
189 }
190
191
192 /**
193  * Construct a full path (starting with '/') for the specified dentry,
194  * relative to the partition. All components are "long" names where possible.
195  *
196  * @param[in]   file    Information about dentry (file or directory) of interest
197  *
198  * return       Pointer to static string containing file's full path
199  */
200 static char *path_name(DOS_FILE *file)
201 {
202     static char path[PATH_MAX*2];
203
204     if (!file) *path = 0;       /* Reached the root directory */
205     else {
206         if (strlen(path_name(file->parent)) > PATH_MAX)
207             die("Path name too long.");
208         if (strcmp(path,"/") != 0) strcat(path,"/");
209
210         /* Append the long name to the path,
211          * or the short name if there isn't a long one
212          */
213         strcpy(strrchr(path,0),file->lfn?file->lfn:file_name(file->dir_ent.name));
214     }
215     return path;
216 }
217
218
219 static int day_n[] = { 0,31,59,90,120,151,181,212,243,273,304,334,0,0,0,0 };
220                   /* JanFebMarApr May Jun Jul Aug Sep Oct Nov Dec */
221
222
223 /* Convert a MS-DOS time/date pair to a UNIX date (seconds since 1 1 70). */
224
225 time_t date_dos2unix(unsigned short time,unsigned short date)
226 {
227     int month,year;
228     time_t secs;
229
230     month = ((date >> 5) & 15)-1;
231     year = date >> 9;
232     secs = (time & 31)*2+60*((time >> 5) & 63)+(time >> 11)*3600+86400*
233       ((date & 31)-1+day_n[month]+(year/4)+year*365-((year & 3) == 0 &&
234       month < 2 ? 1 : 0)+3653);
235                        /* days since 1.1.70 plus 80's leap day */
236     return secs;
237 }
238
239
240 static char *file_stat(DOS_FILE *file)
241 {
242     static char temp[100];
243     struct tm *tm;
244     char tmp[100];
245     time_t date;
246
247     date = date_dos2unix(CF_LE_W(file->dir_ent.time),CF_LE_W(file->
248       dir_ent.date));
249     tm = localtime(&date);
250     strftime(tmp,99,"%H:%M:%S %b %d %Y",tm);
251     sprintf(temp,"  Size %u bytes, date %s",CF_LE_L(file->dir_ent.size),tmp);
252     return temp;
253 }
254
255
256 static int bad_name(DOS_FILE *file)
257 {
258     int i, spc, suspicious = 0;
259     char *bad_chars = atari_format ? "*?\\/:" : "*?<>|\"\\/:";
260     unsigned char *name = file->dir_ent.name;
261
262     /* Do not complain about (and auto-correct) the extended attribute files
263      * of OS/2. */
264     if (strncmp(name,"EA DATA  SF",11) == 0 ||
265         strncmp(name,"WP ROOT  SF",11) == 0) return 0;
266
267     /* don't complain about the dummy 11 bytes used by patched Linux
268        kernels */
269     if (file->dir_ent.lcase & FAT_NO_83NAME) return 0;
270
271     for (i = 0; i < 8; i++) {
272         if (name[i] < ' ' || name[i] == 0x7f) return 1;
273         if (name[i] > 0x7f) ++suspicious;
274         if (strchr(bad_chars,name[i])) return 1;
275     }
276
277     for (i = 8; i < 11; i++) {
278         if (name[i] < ' ' || name[i] == 0x7f) return 1;
279         if (name[i] > 0x7f) ++suspicious;
280         if (strchr(bad_chars,name[i])) return 1;
281     }
282
283     spc = 0;
284     for (i = 0; i < 8; i++) {
285         if (name[i] == ' ')
286             spc = 1;
287         else if (spc)
288             /* non-space after a space not allowed, space terminates the name
289              * part */
290             return 1;
291     }
292
293     spc = 0;
294     for (i = 8; i < 11; i++) {
295         if (name[i] == ' ')
296             spc = 1;
297         else if (spc)
298             /* non-space after a space not allowed, space terminates the name
299              * part */
300             return 1;
301     }
302
303     /* Under GEMDOS, chars >= 128 are never allowed. */
304     if (atari_format && suspicious)
305         return 1;
306
307     /* Under MS-DOS and Windows, chars >= 128 in short names are valid
308      * (but these characters can be visualised differently depending on
309      * local codepage: CP437, CP866, etc). The chars are all basically ok,
310      * so we shouldn't auto-correct such names. */
311     return 0;
312 }
313
314 static void lfn_remove(loff_t from, loff_t to)
315 {
316     int i;
317     DIR_ENT empty;
318
319     /* New dir entry is zeroed except first byte, which is set to 0xe5.
320      * This is to avoid that some FAT-reading OSes (not Linux! ;) stop reading
321      * a directory at the first zero entry...
322      */
323     memset( &empty, 0, sizeof(empty) );
324     empty.name[0] = DELETED_FLAG;
325
326     for( ; from < to; from += sizeof(empty) ) {
327         fs_write( from, sizeof(DIR_ENT), &empty );
328     }
329 }
330
331 static void drop_file(DOS_FS *fs,DOS_FILE *file)
332 {
333     unsigned long cluster;
334
335     MODIFY(file,name[0],DELETED_FLAG);
336     if (file->lfn) lfn_remove(file->lfn_offset, file->offset);
337     for (cluster = FSTART(file,fs); cluster > 0 && cluster <
338       fs->clusters+2; cluster = next_cluster(fs,cluster))
339         set_owner(fs,cluster,NULL);
340     --n_files;
341 }
342
343
344 static void truncate_file(DOS_FS *fs,DOS_FILE *file,unsigned long clusters)
345 {
346     int deleting;
347     unsigned long walk,next,prev;
348
349     walk = FSTART(file,fs);
350     prev = 0;
351     if ((deleting = !clusters)) MODIFY_START(file,0,fs);
352     while (walk > 0 && walk != -1) {
353         next = next_cluster(fs,walk);
354         if (deleting) set_fat(fs,walk,0);
355         else if ((deleting = !--clusters)) set_fat(fs,walk,-1);
356         prev = walk;
357         walk = next;
358     }
359 }
360
361
362 static void auto_rename(DOS_FILE *file)
363 {
364     DOS_FILE *first,*walk;
365     unsigned long int number;
366
367     if (!file->offset) return;  /* cannot rename FAT32 root dir */
368     first = file->parent ? file->parent->first : root;
369     number = 0;
370     while (1) {
371         char num[8];
372         sprintf(num, "%07d", number);
373         memcpy(file->dir_ent.name, "FSCK", 4);
374         memcpy(file->dir_ent.name+4, num, 4);
375         memcpy(file->dir_ent.ext, num+4, 3);
376         for (walk = first; walk; walk = walk->next)
377             if (walk != file && !strncmp(walk->dir_ent.name,file->dir_ent.
378               name,MSDOS_NAME)) break;
379         if (!walk) {
380             fs_write(file->offset,MSDOS_NAME,file->dir_ent.name);
381             if (file->lfn) lfn_fix_checksum(file->lfn_offset, file->offset, file->dir_ent.name);
382             return;
383         }
384         number++;
385         if (number > 9999999) {
386                 die("Too many files need repair.");
387         }
388     }
389     die("Can't generate a unique name.");
390 }
391
392
393 static void rename_file(DOS_FILE *file)
394 {
395     unsigned char name[46];
396     unsigned char *walk,*here;
397
398     if (!file->offset) {
399         printf( "Cannot rename FAT32 root dir\n" );
400         return; /* cannot rename FAT32 root dir */
401     }
402     while (1) {
403         printf("New name: ");
404         fflush(stdout);
405         if (fgets(name,45,stdin)) {
406             if ((here = strchr(name,'\n'))) *here = 0;
407             for (walk = strrchr(name,0); walk >= name && (*walk == ' ' ||
408               *walk == '\t'); walk--);
409             walk[1] = 0;
410             for (walk = name; *walk == ' ' || *walk == '\t'; walk++);
411             if (file_cvt(walk,file->dir_ent.name)) {
412                 fs_write(file->offset,MSDOS_NAME,file->dir_ent.name);
413                 if (file->lfn) lfn_fix_checksum(file->lfn_offset, file->offset, file->dir_ent.name);
414                 return;
415             }
416         }
417     }
418 }
419
420
421 static int handle_dot(DOS_FS *fs,DOS_FILE *file,int dots)
422 {
423     char *name;
424
425     name = strncmp(file->dir_ent.name,MSDOS_DOT,MSDOS_NAME) ? ".." : ".";
426     if (!(file->dir_ent.attr & ATTR_DIR)) {
427         printf("%s\n  Is a non-directory.\n",path_name(file));
428         if (interactive)
429             printf("1) Drop it\n2) Auto-rename\n3) Rename\n"
430               "4) Convert to directory\n");
431         else printf("  Auto-renaming it.\n");
432         switch (interactive ? get_key("1234","?") : '2') {
433             case '1':
434                 drop_file(fs,file);
435                 return 1;
436             case '2':
437                 auto_rename(file);
438                 printf("  Renamed to %s\n",file_name(file->dir_ent.name));
439                 return 0;
440             case '3':
441                 rename_file(file);
442                 return 0;
443             case '4':
444                 MODIFY(file,size,CT_LE_L(0));
445                 MODIFY(file,attr,file->dir_ent.attr | ATTR_DIR);
446                 break;
447         }
448     }
449     if (!dots) {
450         printf("Root contains directory \"%s\". Dropping it.\n",name);
451         drop_file(fs,file);
452         return 1;
453     }
454     return 0;
455 }
456
457
458 static int check_file(DOS_FS *fs,DOS_FILE *file)
459 {
460     DOS_FILE *owner;
461     int restart;
462     unsigned long expect,curr,this,clusters,prev,walk,clusters2;
463
464     if (file->dir_ent.attr & ATTR_DIR) {
465         if (CF_LE_L(file->dir_ent.size)) {
466             printf("%s\n  Directory has non-zero size. Fixing it.\n",
467               path_name(file));
468             MODIFY(file,size,CT_LE_L(0));
469         }
470         if (file->parent && !strncmp(file->dir_ent.name,MSDOS_DOT,MSDOS_NAME)) {
471             expect = FSTART(file->parent,fs);
472             if (FSTART(file,fs) != expect) {
473                 printf("%s\n  Start (%ld) does not point to parent (%ld)\n",
474                   path_name(file),FSTART(file,fs),expect);
475                 MODIFY_START(file,expect,fs);
476             }
477             return 0;
478         }
479         if (file->parent && !strncmp(file->dir_ent.name,MSDOS_DOTDOT,
480           MSDOS_NAME)) {
481             expect = file->parent->parent ? FSTART(file->parent->parent,fs):0;
482             if (fs->root_cluster && expect == fs->root_cluster)
483                 expect = 0;
484             if (FSTART(file,fs) != expect) {
485                 printf("%s\n  Start (%lu) does not point to .. (%lu)\n",
486                   path_name(file),FSTART(file,fs),expect);
487                 MODIFY_START(file,expect,fs);
488             }
489             return 0;
490         }
491         if (FSTART(file,fs)==0){
492                 printf ("%s\n Start does point to root directory. Deleting dir. \n",
493                                 path_name(file));
494                 MODIFY(file,name[0],DELETED_FLAG);
495                 return 0;
496         }
497     }
498     if (FSTART(file,fs) >= fs->clusters+2) {
499         printf("%s\n  Start cluster beyond limit (%lu > %lu). Truncating file.\n",
500           path_name(file),FSTART(file,fs),fs->clusters+1);
501         if (!file->offset)
502             die( "Bad FAT32 root directory! (bad start cluster)\n" );
503         MODIFY_START(file,0,fs);
504     }
505     clusters = prev = 0;
506     for (curr = FSTART(file,fs) ? FSTART(file,fs) :
507       -1; curr != -1; curr = next_cluster(fs,curr)) {
508         FAT_ENTRY curEntry;
509         get_fat(&curEntry, fs->fat, curr, fs);
510
511         if (!curEntry.value || bad_cluster(fs,curr)) {
512             printf("%s\n  Contains a %s cluster (%lu). Assuming EOF.\n",
513               path_name(file), curEntry.value ? "bad" : "free",curr);
514             if (prev) set_fat(fs,prev,-1);
515             else if (!file->offset)
516                 die( "FAT32 root dir starts with a bad cluster!" );
517             else MODIFY_START(file,0,fs);
518             break;
519         }
520         if (!(file->dir_ent.attr & ATTR_DIR) && CF_LE_L(file->dir_ent.size) <=
521           (unsigned long long)clusters*fs->cluster_size) {
522             printf("%s\n  File size is %u bytes, cluster chain length is > %llu "
523               "bytes.\n  Truncating file to %u bytes.\n",path_name(file),
524               CF_LE_L(file->dir_ent.size),(unsigned long long)clusters*fs->cluster_size,
525               CF_LE_L(file->dir_ent.size));
526             truncate_file(fs,file,clusters);
527             break;
528         }
529         if ((owner = get_owner(fs,curr))) {
530             int do_trunc = 0;
531             printf("%s  and\n",path_name(owner));
532             printf("%s\n  share clusters.\n",path_name(file));
533             clusters2 = 0;
534             for (walk = FSTART(owner,fs); walk > 0 && walk != -1; walk =
535               next_cluster(fs,walk))
536                 if (walk == curr) break;
537                 else clusters2++;
538             restart = file->dir_ent.attr & ATTR_DIR;
539             if (!owner->offset) {
540                 printf( "  Truncating second to %llu bytes because first "
541                         "is FAT32 root dir.\n", (unsigned long long)clusters2*fs->cluster_size );
542                 do_trunc = 2;
543             }
544             else if (!file->offset) {
545                 printf( "  Truncating first to %llu bytes because second "
546                         "is FAT32 root dir.\n", (unsigned long long)clusters*fs->cluster_size );
547                 do_trunc = 1;
548             }
549             else if (interactive)
550                 printf("1) Truncate first to %llu bytes%s\n"
551                   "2) Truncate second to %llu bytes\n",(unsigned long long)clusters*fs->cluster_size,
552                   restart ? " and restart" : "",(unsigned long long)clusters2*fs->cluster_size);
553             else printf("  Truncating second to %llu bytes.\n",(unsigned long long)clusters2*
554                   fs->cluster_size);
555             if (do_trunc != 2 &&
556                 (do_trunc == 1 ||
557                  (interactive && get_key("12","?") == '1'))) {
558                 prev = 0;
559                 clusters = 0;
560                 for (this = FSTART(owner,fs); this > 0 && this != -1; this =
561                   next_cluster(fs,this)) {
562                     if (this == curr) {
563                         if (prev) set_fat(fs,prev,-1);
564                         else MODIFY_START(owner,0,fs);
565                         MODIFY(owner,size,CT_LE_L((unsigned long long)clusters*fs->cluster_size));
566                         if (restart) return 1;
567                         while (this > 0 && this != -1) {
568                             set_owner(fs,this,NULL);
569                             this = next_cluster(fs,this);
570                         }
571                         this = curr;
572                         break;
573                     }
574                     clusters++;
575                     prev = this;
576                 }
577                 if (this != curr)
578                     die("Internal error: didn't find cluster %d in chain"
579                       " starting at %d",curr,FSTART(owner,fs));
580             }
581             else {
582                 if (prev) set_fat(fs,prev,-1);
583                 else MODIFY_START(file,0,fs);
584                 break;
585             }
586         }
587         set_owner(fs,curr,file);
588         clusters++;
589         prev = curr;
590     }
591     if (!(file->dir_ent.attr & ATTR_DIR) && CF_LE_L(file->dir_ent.size) >
592       (unsigned long long)clusters*fs->cluster_size) {
593         printf("%s\n  File size is %u bytes, cluster chain length is %llu bytes."
594           "\n  Truncating file to %lu bytes.\n",path_name(file),CF_LE_L(file->
595           dir_ent.size),(unsigned long long)clusters*fs->cluster_size,(unsigned long long)clusters*fs->cluster_size);
596         MODIFY(file,size,CT_LE_L((unsigned long long)clusters*fs->cluster_size));
597     }
598     return 0;
599 }
600
601
602 static int check_files(DOS_FS *fs,DOS_FILE *start)
603 {
604     while (start) {
605         if (check_file(fs,start)) return 1;
606         start = start->next;
607     }
608     return 0;
609 }
610
611
612 static int check_dir(DOS_FS *fs,DOS_FILE **root,int dots)
613 {
614     DOS_FILE *parent,**walk,**scan;
615     int dot,dotdot,skip,redo;
616     int good,bad;
617
618     if (!*root) return 0;
619     parent = (*root)->parent;
620     good = bad = 0;
621     for (walk = root; *walk; walk = &(*walk)->next)
622         if (bad_name(*walk)) bad++;
623         else good++;
624     if (*root && parent && good+bad > 4 && bad > good/2) {
625         printf("%s\n  Has a large number of bad entries. (%d/%d)\n",
626           path_name(parent),bad,good+bad);
627         if (!dots) printf( "  Not dropping root directory.\n" );
628         else if (!interactive) printf("  Not dropping it in auto-mode.\n");
629         else if (get_key("yn","Drop directory ? (y/n)") == 'y') {
630             truncate_file(fs,parent,0);
631             MODIFY(parent,name[0],DELETED_FLAG);
632             /* buglet: deleted directory stays in the list. */
633             return 1;
634         }
635     }
636     dot = dotdot = redo = 0;
637     walk = root;
638     while (*walk) {
639         if (!strncmp((*walk)->dir_ent.name,MSDOS_DOT,MSDOS_NAME) ||
640           !strncmp((*walk)->dir_ent.name,MSDOS_DOTDOT,MSDOS_NAME)) {
641             if (handle_dot(fs,*walk,dots)) {
642                 *walk = (*walk)->next;
643                 continue;
644             }
645             if (!strncmp((*walk)->dir_ent.name,MSDOS_DOT,MSDOS_NAME)) dot++;
646             else dotdot++;
647         }
648         if (!((*walk)->dir_ent.attr & ATTR_VOLUME) &&
649             bad_name(*walk)) {
650             puts(path_name(*walk));
651             printf("  Bad short file name (%s).\n", file_name((*walk)->dir_ent.name));
652             if (interactive)
653                 printf("1) Drop file\n2) Rename file\n3) Auto-rename\n"
654                   "4) Keep it\n");
655             else printf("  Auto-renaming it.\n");
656             switch (interactive ? get_key("1234","?") : '3') {
657                 case '1':
658                     drop_file(fs,*walk);
659                     walk = &(*walk)->next;
660                     continue;
661                 case '2':
662                     rename_file(*walk);
663                     redo = 1;
664                     break;
665                 case '3':
666                     auto_rename(*walk);
667                     printf("  Renamed to %s\n",file_name((*walk)->dir_ent.
668                       name));
669                     break;
670                 case '4':
671                     break;
672             }
673         }
674         /* don't check for duplicates of the volume label */
675         if (!((*walk)->dir_ent.attr & ATTR_VOLUME)) {
676             scan = &(*walk)->next;
677             skip = 0;
678             while (*scan && !skip) {
679                 if (!((*scan)->dir_ent.attr & ATTR_VOLUME) &&
680                     !memcmp((*walk)->dir_ent.name,(*scan)->dir_ent.name,MSDOS_NAME)) {
681                     printf("%s\n  Duplicate directory entry.\n  First  %s\n",
682                            path_name(*walk),file_stat(*walk));
683                     printf("  Second %s\n",file_stat(*scan));
684                     if (interactive)
685                         printf("1) Drop first\n2) Drop second\n3) Rename first\n"
686                                "4) Rename second\n5) Auto-rename first\n"
687                                "6) Auto-rename second\n");
688                     else printf("  Auto-renaming second.\n");
689                     switch (interactive ? get_key("123456","?") : '6') {
690                       case '1':
691                         drop_file(fs,*walk);
692                         *walk = (*walk)->next;
693                         skip = 1;
694                         break;
695                       case '2':
696                         drop_file(fs,*scan);
697                         *scan = (*scan)->next;
698                         continue;
699                       case '3':
700                         rename_file(*walk);
701                         printf("  Renamed to %s\n",path_name(*walk));
702                         redo = 1;
703                         break;
704                       case '4':
705                         rename_file(*scan);
706                         printf("  Renamed to %s\n",path_name(*walk));
707                         redo = 1;
708                         break;
709                       case '5':
710                         auto_rename(*walk);
711                         printf("  Renamed to %s\n",file_name((*walk)->dir_ent.
712                           name));
713                         break;
714                       case '6':
715                         auto_rename(*scan);
716                         printf("  Renamed to %s\n",file_name((*scan)->dir_ent.
717                           name));
718                         break;
719                     }
720                 }
721                 scan = &(*scan)->next;
722             }
723             if (skip) continue;
724         }
725         if (!redo) walk = &(*walk)->next;
726         else {
727             walk = root;
728             dot = dotdot = redo = 0;
729         }
730     }
731     if (dots && !dot)
732         printf("%s\n  \".\" is missing. Can't fix this yet.\n",
733           path_name(parent));
734     if (dots && !dotdot)
735         printf("%s\n  \"..\" is missing. Can't fix this yet.\n",
736           path_name(parent));
737     return 0;
738 }
739
740
741 /**
742  * Check a dentry's cluster chain for bad clusters.
743  * If requested, we verify readability and mark unreadable clusters as bad.
744  *
745  * @param[inout]    fs          Information about the filesystem
746  * @param[in]       file        dentry to check
747  * @param[in]       read_test   Nonzero == verify that dentry's clusters can
748  *                              be read
749  */
750 static void test_file(DOS_FS *fs,DOS_FILE *file,int read_test)
751 {
752     DOS_FILE *owner;
753     unsigned long walk,prev,clusters,next_clu;
754
755     prev = clusters = 0;
756     for (walk = FSTART(file,fs); walk > 0 && walk < fs->clusters+2;
757       walk = next_clu) {
758         next_clu = next_cluster(fs,walk);
759
760         /* In this stage we are checking only for a loop within our own
761          * cluster chain.
762          * Cross-linking of clusters is handled in check_file()
763          */
764         if ((owner = get_owner(fs,walk))) {
765             if (owner == file) {
766                 printf("%s\n  Circular cluster chain. Truncating to %lu "
767                   "cluster%s.\n",path_name(file),clusters,clusters == 1 ? "" :
768                   "s");
769                 if (prev) set_fat(fs,prev,-1);
770                 else if (!file->offset)
771                     die( "Bad FAT32 root directory! (bad start cluster)\n" );
772                 else MODIFY_START(file,0,fs);
773             }
774             break;
775         }
776         if (bad_cluster(fs,walk)) break;
777         if (read_test) {
778             if (fs_test(cluster_start(fs,walk),fs->cluster_size)) {
779                 prev = walk;
780                 clusters++;
781             }
782             else {
783                 printf("%s\n  Cluster %lu (%lu) is unreadable. Skipping it.\n",
784                   path_name(file),clusters,walk);
785                 if (prev) set_fat(fs,prev,next_cluster(fs,walk));
786                 else MODIFY_START(file,next_cluster(fs,walk),fs);
787                 set_fat(fs,walk,-2);
788             }
789         }
790         set_owner(fs,walk,file);
791     }
792     /* Revert ownership (for now) */
793     for (walk = FSTART(file,fs); walk > 0 && walk < fs->clusters+2;
794       walk = next_cluster(fs,walk))
795         if (bad_cluster(fs,walk)) break;
796         else if (get_owner(fs,walk) == file) set_owner(fs,walk,NULL);
797             else break;
798 }
799
800
801 static void undelete(DOS_FS *fs,DOS_FILE *file)
802 {
803     unsigned long clusters,left,prev,walk;
804
805     clusters = left = (CF_LE_L(file->dir_ent.size)+fs->cluster_size-1)/
806       fs->cluster_size;
807     prev = 0;
808
809     walk = FSTART(file,fs);
810
811     while (left && (walk >= 2) && (walk < fs->clusters+2)) {
812
813         FAT_ENTRY curEntry;
814         get_fat(&curEntry, fs->fat, walk, fs);
815
816         if (!curEntry.value)
817             break;
818
819         left--;
820         if (prev) set_fat(fs,prev,walk);
821         prev = walk;
822         walk++;
823     }
824     if (prev) set_fat(fs,prev,-1);
825     else MODIFY_START(file,0,fs);
826     if (left)
827         printf("Warning: Did only undelete %lu of %lu cluster%s.\n",clusters-left,
828           clusters,clusters == 1 ? "" : "s");
829
830 }
831
832
833 static void new_dir( void )
834 {
835     lfn_reset();
836 }
837
838
839 /**
840  * Create a description for a referenced dentry and insert it in our dentry
841  * tree. Then, go check the dentry's cluster chain for bad clusters and
842  * cluster loops.
843  *
844  * @param[inout]    fs      Information about the filesystem
845  * @param[out]      chain
846  * @param[in]       parent  Information about parent directory of this file
847  *                          NULL == no parent ('file' is root directory)
848  * @param[in]       offset  Partition-relative byte offset of directory entry of interest
849  *                          0 == Root directory
850  * @param           cp
851  */
852 static void add_file(DOS_FS *fs,DOS_FILE ***chain,DOS_FILE *parent,
853                                          loff_t offset,FDSC **cp)
854 {
855     DOS_FILE *new;
856     DIR_ENT de;
857     FD_TYPE type;
858
859     if (offset)
860         fs_read(offset,sizeof(DIR_ENT),&de);
861     else {
862         /* Construct a DIR_ENT for the root directory */
863         memcpy(de.name,"           ",MSDOS_NAME);
864         de.attr = ATTR_DIR;
865         de.size = de.time = de.date = 0;
866         de.start = CT_LE_W(fs->root_cluster & 0xffff);
867         de.starthi = CT_LE_W((fs->root_cluster >> 16) & 0xffff);
868     }
869     if ((type = file_type(cp,de.name)) != fdt_none) {
870         if (type == fdt_undelete && (de.attr & ATTR_DIR))
871             die("Can't undelete directories.");
872         file_modify(cp,de.name);
873         fs_write(offset,1,&de);
874     }
875     if (IS_FREE(de.name)) {
876         lfn_check_orphaned();
877         return;
878     }
879     if (de.attr == VFAT_LN_ATTR) {
880         lfn_add_slot(&de,offset);
881         return;
882     }
883     new = qalloc(&mem_queue,sizeof(DOS_FILE));
884     new->lfn = lfn_get(&de, &new->lfn_offset);
885     new->offset = offset;
886     memcpy(&new->dir_ent,&de,sizeof(de));
887     new->next = new->first = NULL;
888     new->parent = parent;
889     if (type == fdt_undelete) undelete(fs,new);
890     **chain = new;
891     *chain = &new->next;
892     if (list) {
893         printf("Checking file %s",path_name(new));
894         if (new->lfn)
895             printf(" (%s)", file_name(new->dir_ent.name) );     /* (8.3) */
896         printf("\n");
897     }
898     /* Don't include root directory, '.', or '..' in the total file count */
899     if (offset &&
900         strncmp(de.name,MSDOS_DOT,MSDOS_NAME) != 0 &&
901         strncmp(de.name,MSDOS_DOTDOT,MSDOS_NAME) != 0)
902         ++n_files;
903     test_file(fs,new,test);     /* Bad cluster check */
904 }
905
906
907 static int subdirs(DOS_FS *fs,DOS_FILE *parent,FDSC **cp);
908
909
910 static int scan_dir(DOS_FS *fs,DOS_FILE *this,FDSC **cp)
911 {
912     DOS_FILE **chain;
913     int i;
914     unsigned long clu_num;
915
916     chain = &this->first;
917     i = 0;
918     clu_num = FSTART(this,fs);
919     new_dir();
920     while (clu_num > 0 && clu_num != -1) {
921         add_file(fs,&chain,this,cluster_start(fs,clu_num)+(i % fs->
922           cluster_size),cp);
923         i += sizeof(DIR_ENT);
924         if (!(i % fs->cluster_size))
925             if ((clu_num = next_cluster(fs,clu_num)) == 0 || clu_num == -1)
926                 break;
927     }
928     lfn_check_orphaned();
929     if (check_dir(fs,&this->first,this->offset)) return 0;
930     if (check_files(fs,this->first)) return 1;
931     return subdirs(fs,this,cp);
932 }
933
934
935 /**
936  * Recursively scan subdirectories of the specified parent directory.
937  *
938  * @param[inout]    fs      Information about the filesystem
939  * @param[in]       parent  Identifies the directory to scan
940  * @param[in]       cp
941  *
942  * @return  0   Success
943  * @return  1   Error
944  */
945 static int subdirs(DOS_FS *fs,DOS_FILE *parent,FDSC **cp)
946 {
947     DOS_FILE *walk;
948
949     for (walk = parent ? parent->first : root; walk; walk = walk->next)
950         if (walk->dir_ent.attr & ATTR_DIR)
951             if (strncmp(walk->dir_ent.name,MSDOS_DOT,MSDOS_NAME) &&
952               strncmp(walk->dir_ent.name,MSDOS_DOTDOT,MSDOS_NAME))
953                 if (scan_dir(fs,walk,file_cd(cp,walk->dir_ent.name))) return 1;
954     return 0;
955 }
956
957
958 /**
959  * Scan all directory and file information for errors.
960  *
961  * @param[inout]    fs      Information about the filesystem
962  *
963  * @return  0   Success
964  * @return  1   Error
965  */
966 int scan_root(DOS_FS *fs)
967 {
968     DOS_FILE **chain;
969     int i;
970
971     root = NULL;
972     chain = &root;
973     new_dir();
974     if (fs->root_cluster) {
975         add_file(fs,&chain,NULL,0,&fp_root);
976     }
977     else {
978         for (i = 0; i < fs->root_entries; i++)
979             add_file(fs,&chain,NULL,fs->root_start+i*sizeof(DIR_ENT),&fp_root);
980     }
981     lfn_check_orphaned();
982     (void) check_dir(fs,&root,0);
983     if (check_files(fs,root)) return 1;
984     return subdirs(fs,NULL,&fp_root);
985 }