984ca3d36fd54a89acab9c04c28a5c18078dba11
[dragonfly.git] / sbin / fsck_msdosfs / dir.c
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank
5  * Copyright (c) 1995 Martin Husemann
6  * Some structure declaration borrowed from Paul Popelka
7  * (paulp@uts.amdahl.com), see /sys/msdosfs/ for reference.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21  * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
22  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #include <assert.h>
31 #include <inttypes.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <ctype.h>
36 #include <unistd.h>
37 #include <time.h>
38
39 #include <sys/param.h>
40
41 #include "ext.h"
42 #include "fsutil.h"
43
44 #define SLOT_EMPTY      0x00            /* slot has never been used */
45 #define SLOT_E5         0x05            /* the real value is 0xe5 */
46 #define SLOT_DELETED    0xe5            /* file in this slot deleted */
47
48 #define ATTR_NORMAL     0x00            /* normal file */
49 #define ATTR_READONLY   0x01            /* file is readonly */
50 #define ATTR_HIDDEN     0x02            /* file is hidden */
51 #define ATTR_SYSTEM     0x04            /* file is a system file */
52 #define ATTR_VOLUME     0x08            /* entry is a volume label */
53 #define ATTR_DIRECTORY  0x10            /* entry is a directory name */
54 #define ATTR_ARCHIVE    0x20            /* file is new or modified */
55
56 #define ATTR_WIN95      0x0f            /* long name record */
57
58 /*
59  * This is the format of the contents of the deTime field in the direntry
60  * structure.
61  * We don't use bitfields because we don't know how compilers for
62  * arbitrary machines will lay them out.
63  */
64 #define DT_2SECONDS_MASK        0x1F    /* seconds divided by 2 */
65 #define DT_2SECONDS_SHIFT       0
66 #define DT_MINUTES_MASK         0x7E0   /* minutes */
67 #define DT_MINUTES_SHIFT        5
68 #define DT_HOURS_MASK           0xF800  /* hours */
69 #define DT_HOURS_SHIFT          11
70
71 /*
72  * This is the format of the contents of the deDate field in the direntry
73  * structure.
74  */
75 #define DD_DAY_MASK             0x1F    /* day of month */
76 #define DD_DAY_SHIFT            0
77 #define DD_MONTH_MASK           0x1E0   /* month */
78 #define DD_MONTH_SHIFT          5
79 #define DD_YEAR_MASK            0xFE00  /* year - 1980 */
80 #define DD_YEAR_SHIFT           9
81
82
83 /* dir.c */
84 static struct dosDirEntry *newDosDirEntry(void);
85 static void freeDosDirEntry(struct dosDirEntry *);
86 static struct dirTodoNode *newDirTodo(void);
87 static void freeDirTodo(struct dirTodoNode *);
88 static char *fullpath(struct dosDirEntry *);
89 static u_char calcShortSum(u_char *);
90 static int delete(int, struct bootblock *, struct fatEntry *, cl_t, int,
91     cl_t, int, int);
92 static int removede(int, struct bootblock *, struct fatEntry *, u_char *,
93     u_char *, cl_t, cl_t, cl_t, char *, int);
94 static int checksize(struct bootblock *, struct fatEntry *, u_char *,
95     struct dosDirEntry *);
96 static int readDosDirSection(int, struct bootblock *, struct fatEntry *,
97     struct dosDirEntry *);
98
99 /*
100  * Manage free dosDirEntry structures.
101  */
102 static struct dosDirEntry *freede;
103
104 static struct dosDirEntry *
105 newDosDirEntry(void)
106 {
107         struct dosDirEntry *de;
108
109         if (!(de = freede)) {
110                 if (!(de = malloc(sizeof(*de))))
111                         return 0;
112         } else
113                 freede = de->next;
114         return de;
115 }
116
117 static void
118 freeDosDirEntry(struct dosDirEntry *de)
119 {
120         de->next = freede;
121         freede = de;
122 }
123
124 /*
125  * The same for dirTodoNode structures.
126  */
127 static struct dirTodoNode *freedt;
128
129 static struct dirTodoNode *
130 newDirTodo(void)
131 {
132         struct dirTodoNode *dt;
133
134         if (!(dt = freedt)) {
135                 if (!(dt = malloc(sizeof(*dt))))
136                         return 0;
137         } else
138                 freedt = dt->next;
139         return dt;
140 }
141
142 static void
143 freeDirTodo(struct dirTodoNode *dt)
144 {
145         dt->next = freedt;
146         freedt = dt;
147 }
148
149 /*
150  * The stack of unread directories
151  */
152 static struct dirTodoNode *pendingDirectories = NULL;
153
154 /*
155  * Return the full pathname for a directory entry.
156  */
157 static char *
158 fullpath(struct dosDirEntry *dir)
159 {
160         static char namebuf[MAXPATHLEN + 1];
161         char *cp, *np;
162         int nl;
163
164         cp = namebuf + sizeof namebuf;
165         *--cp = '\0';
166
167         for(;;) {
168                 np = dir->lname[0] ? dir->lname : dir->name;
169                 nl = strlen(np);
170                 if (cp <= namebuf + 1 + nl) {
171                         *--cp = '?';
172                         break;
173                 }
174                 cp -= nl;
175                 memcpy(cp, np, nl);
176                 dir = dir->parent;
177                 if (!dir)
178                         break;
179                 *--cp = '/';
180         }
181
182         return cp;
183 }
184
185 /*
186  * Calculate a checksum over an 8.3 alias name
187  */
188 static u_char
189 calcShortSum(u_char *p)
190 {
191         u_char sum = 0;
192         int i;
193
194         for (i = 0; i < 11; i++) {
195                 sum = (sum << 7)|(sum >> 1);    /* rotate right */
196                 sum += p[i];
197         }
198
199         return sum;
200 }
201
202 /*
203  * Global variables temporarily used during a directory scan
204  */
205 static char longName[DOSLONGNAMELEN] = "";
206 static u_char *buffer = NULL;
207 static u_char *delbuf = NULL;
208
209 static struct dosDirEntry *rootDir;
210 static struct dosDirEntry *lostDir;
211
212 /*
213  * Init internal state for a new directory scan.
214  */
215 int
216 resetDosDirSection(struct bootblock *boot, struct fatEntry *fat)
217 {
218         int b1, b2;
219         int ret = FSOK;
220         size_t len;
221
222         b1 = boot->bpbRootDirEnts * 32;
223         b2 = boot->bpbSecPerClust * boot->bpbBytesPerSec;
224
225         if ((buffer = malloc(len = MAX(b1, b2))) == NULL) {
226                 perr("No space for directory buffer (%zu)", len);
227                 return FSFATAL;
228         }
229
230         if ((delbuf = malloc(len = b2)) == NULL) {
231                 free(buffer);
232                 perr("No space for directory delbuf (%zu)", len);
233                 return FSFATAL;
234         }
235
236         if ((rootDir = newDosDirEntry()) == NULL) {
237                 free(buffer);
238                 free(delbuf);
239                 perr("No space for directory entry");
240                 return FSFATAL;
241         }
242
243         memset(rootDir, 0, sizeof *rootDir);
244         if (boot->flags & FAT32) {
245                 if (boot->bpbRootClust < CLUST_FIRST ||
246                     boot->bpbRootClust >= boot->NumClusters) {
247                         pfatal("Root directory starts with cluster out of range(%u)",
248                                boot->bpbRootClust);
249                         return FSFATAL;
250                 }
251                 if (fat[boot->bpbRootClust].head != boot->bpbRootClust) {
252                         pfatal("Root directory doesn't start a cluster chain");
253                         return FSFATAL;
254                 }
255
256                 fat[boot->bpbRootClust].flags |= FAT_USED;
257                 rootDir->head = boot->bpbRootClust;
258         }
259
260         return ret;
261 }
262
263 /*
264  * Cleanup after a directory scan
265  */
266 void
267 finishDosDirSection(void)
268 {
269         struct dirTodoNode *p, *np;
270         struct dosDirEntry *d, *nd;
271
272         for (p = pendingDirectories; p; p = np) {
273                 np = p->next;
274                 freeDirTodo(p);
275         }
276         pendingDirectories = NULL;
277         for (d = rootDir; d; d = nd) {
278                 if ((nd = d->child) != NULL) {
279                         d->child = 0;
280                         continue;
281                 }
282                 if (!(nd = d->next))
283                         nd = d->parent;
284                 freeDosDirEntry(d);
285         }
286         rootDir = lostDir = NULL;
287         free(buffer);
288         free(delbuf);
289         buffer = NULL;
290         delbuf = NULL;
291 }
292
293 /*
294  * Delete directory entries between startcl, startoff and endcl, endoff.
295  */
296 static int
297 delete(int f, struct bootblock *boot, struct fatEntry *fat, cl_t startcl,
298     int startoff, cl_t endcl, int endoff, int notlast)
299 {
300         u_char *s, *e;
301         off_t off;
302         int clsz = boot->bpbSecPerClust * boot->bpbBytesPerSec;
303
304         s = delbuf + startoff;
305         e = delbuf + clsz;
306         while (startcl >= CLUST_FIRST && startcl < boot->NumClusters) {
307                 if (startcl == endcl) {
308                         if (notlast)
309                                 break;
310                         e = delbuf + endoff;
311                 }
312                 off = startcl * boot->bpbSecPerClust + boot->ClusterOffset;
313                 off *= boot->bpbBytesPerSec;
314                 if (lseek(f, off, SEEK_SET) != off) {
315                         perr("Unable to lseek to %" PRId64, off);
316                         return FSFATAL;
317                 }
318                 if (read(f, delbuf, clsz) != clsz) {
319                         perr("Unable to read directory");
320                         return FSFATAL;
321                 }
322                 while (s < e) {
323                         *s = SLOT_DELETED;
324                         s += 32;
325                 }
326                 if (lseek(f, off, SEEK_SET) != off) {
327                         perr("Unable to lseek to %" PRId64, off);
328                         return FSFATAL;
329                 }
330                 if (write(f, delbuf, clsz) != clsz) {
331                         perr("Unable to write directory");
332                         return FSFATAL;
333                 }
334                 if (startcl == endcl)
335                         break;
336                 startcl = fat[startcl].next;
337                 s = delbuf;
338         }
339         return FSOK;
340 }
341
342 static int
343 removede(int f, struct bootblock *boot, struct fatEntry *fat, u_char *start,
344     u_char *end, cl_t startcl, cl_t endcl, cl_t curcl, char *path, int type)
345 {
346         switch (type) {
347         case 0:
348                 pwarn("Invalid long filename entry for %s\n", path);
349                 break;
350         case 1:
351                 pwarn("Invalid long filename entry at end of directory %s\n",
352                     path);
353                 break;
354         case 2:
355                 pwarn("Invalid long filename entry for volume label\n");
356                 break;
357         }
358         if (ask(0, "Remove")) {
359                 if (startcl != curcl) {
360                         if (delete(f, boot, fat,
361                                    startcl, start - buffer,
362                                    endcl, end - buffer,
363                                    endcl == curcl) == FSFATAL)
364                                 return FSFATAL;
365                         start = buffer;
366                 }
367                 /* startcl is < CLUST_FIRST for !fat32 root */
368                 if ((endcl == curcl) || (startcl < CLUST_FIRST))
369                         for (; start < end; start += 32)
370                                 *start = SLOT_DELETED;
371                 return FSDIRMOD;
372         }
373         return FSERROR;
374 }
375
376 /*
377  * Check an in-memory file entry
378  */
379 static int
380 checksize(struct bootblock *boot, struct fatEntry *fat, u_char *p,
381     struct dosDirEntry *dir)
382 {
383         /*
384          * Check size on ordinary files
385          */
386         u_int32_t physicalSize;
387
388         if (dir->head == CLUST_FREE)
389                 physicalSize = 0;
390         else {
391                 if (dir->head < CLUST_FIRST || dir->head >= boot->NumClusters)
392                         return FSERROR;
393                 physicalSize = fat[dir->head].length * boot->ClusterSize;
394         }
395         if (physicalSize < dir->size) {
396                 pwarn("size of %s is %u, should at most be %u\n",
397                       fullpath(dir), dir->size, physicalSize);
398                 if (ask(1, "Truncate")) {
399                         dir->size = physicalSize;
400                         p[28] = (u_char)physicalSize;
401                         p[29] = (u_char)(physicalSize >> 8);
402                         p[30] = (u_char)(physicalSize >> 16);
403                         p[31] = (u_char)(physicalSize >> 24);
404                         return FSDIRMOD;
405                 } else
406                         return FSERROR;
407         } else if (physicalSize - dir->size >= boot->ClusterSize) {
408                 pwarn("%s has too many clusters allocated\n",
409                       fullpath(dir));
410                 if (ask(1, "Drop superfluous clusters")) {
411                         cl_t cl;
412                         u_int32_t sz, len;
413
414                         for (cl = dir->head, len = sz = 0;
415                             (sz += boot->ClusterSize) < dir->size; len++)
416                                 cl = fat[cl].next;
417                         clearchain(boot, fat, fat[cl].next);
418                         fat[cl].next = CLUST_EOF;
419                         fat[dir->head].length = len;
420                         return FSFATMOD;
421                 } else
422                         return FSERROR;
423         }
424         return FSOK;
425 }
426
427 static const u_char dot_name[11]    = ".          ";
428 static const u_char dotdot_name[11] = "..         ";
429
430 /*
431  * Basic sanity check if the subdirectory have good '.' and '..' entries,
432  * and they are directory entries.  Further sanity checks are performed
433  * when we traverse into it.
434  */
435 static int
436 check_subdirectory(int f, struct bootblock *boot, struct dosDirEntry *dir)
437 {
438         u_char *buf, *cp;
439         off_t off;
440         cl_t cl;
441         int retval = FSOK;
442
443         cl = dir->head;
444         if (dir->parent && (cl < CLUST_FIRST || cl >= boot->NumClusters)) {
445                 return FSERROR;
446         }
447
448         if (!(boot->flags & FAT32) && !dir->parent) {
449                 off = boot->bpbResSectors + boot->bpbFATs *
450                         boot->FATsecs;
451         } else {
452                 off = cl * boot->bpbSecPerClust + boot->ClusterOffset;
453         }
454
455         /*
456          * We only need to check the first two entries of the directory,
457          * which is found in the first sector of the directory entry,
458          * so read in only the first sector.
459          */
460         buf = malloc(boot->bpbBytesPerSec);
461         if (buf == NULL) {
462                 perr("No space for directory buffer (%u)",
463                     boot->bpbBytesPerSec);
464                 return FSFATAL;
465         }
466
467         off *= boot->bpbBytesPerSec;
468         if (lseek(f, off, SEEK_SET) != off ||
469             read(f, buf, boot->bpbBytesPerSec) != (ssize_t)boot->bpbBytesPerSec) {
470                 perr("Unable to read directory");
471                 free(buf);
472                 return FSFATAL;
473         }
474
475         /*
476          * Both `.' and `..' must be present and be the first two entries
477          * and be ATTR_DIRECTORY of a valid subdirectory.
478          */
479         cp = buf;
480         if (memcmp(cp, dot_name, sizeof(dot_name)) != 0 ||
481             (cp[11] & ATTR_DIRECTORY) != ATTR_DIRECTORY) {
482                 pwarn("%s: Incorrect `.' for %s.\n", __func__, dir->name);
483                 retval |= FSERROR;
484         }
485         cp += 32;
486         if (memcmp(cp, dotdot_name, sizeof(dotdot_name)) != 0 ||
487             (cp[11] & ATTR_DIRECTORY) != ATTR_DIRECTORY) {
488                 pwarn("%s: Incorrect `..' for %s. \n", __func__, dir->name);
489                 retval |= FSERROR;
490         }
491
492         free(buf);
493         return retval;
494 }
495
496 /*
497  * Read a directory and
498  *   - resolve long name records
499  *   - enter file and directory records into the parent's list
500  *   - push directories onto the todo-stack
501  */
502 static int
503 readDosDirSection(int f, struct bootblock *boot, struct fatEntry *fat,
504     struct dosDirEntry *dir)
505 {
506         struct dosDirEntry dirent, *d;
507         u_char *p, *vallfn, *invlfn, *empty;
508         off_t off;
509         int i, j, k, last;
510         cl_t cl, valcl = ~0, invcl = ~0, empcl = ~0;
511         char *t;
512         u_int lidx = 0;
513         int shortSum;
514         int mod = FSOK;
515 #define THISMOD 0x8000                  /* Only used within this routine */
516
517         cl = dir->head;
518         if (dir->parent && (cl < CLUST_FIRST || cl >= boot->NumClusters)) {
519                 /*
520                  * Already handled somewhere else.
521                  */
522                 return FSOK;
523         }
524         shortSum = -1;
525         vallfn = invlfn = empty = NULL;
526         do {
527                 if (!(boot->flags & FAT32) && !dir->parent) {
528                         last = boot->bpbRootDirEnts * 32;
529                         off = boot->bpbResSectors + boot->bpbFATs *
530                             boot->FATsecs;
531                 } else {
532                         last = boot->bpbSecPerClust * boot->bpbBytesPerSec;
533                         off = cl * boot->bpbSecPerClust + boot->ClusterOffset;
534                 }
535
536                 off *= boot->bpbBytesPerSec;
537                 if (lseek(f, off, SEEK_SET) != off
538                     || read(f, buffer, last) != last) {
539                         perr("Unable to read directory");
540                         return FSFATAL;
541                 }
542                 last /= 32;
543                 for (p = buffer, i = 0; i < last; i++, p += 32) {
544                         if (dir->fsckflags & DIREMPWARN) {
545                                 *p = SLOT_EMPTY;
546                                 continue;
547                         }
548
549                         if (*p == SLOT_EMPTY || *p == SLOT_DELETED) {
550                                 if (*p == SLOT_EMPTY) {
551                                         dir->fsckflags |= DIREMPTY;
552                                         empty = p;
553                                         empcl = cl;
554                                 }
555                                 continue;
556                         }
557
558                         if (dir->fsckflags & DIREMPTY) {
559                                 if (!(dir->fsckflags & DIREMPWARN)) {
560                                         pwarn("%s has entries after end of directory\n",
561                                               fullpath(dir));
562                                         if (ask(1, "Extend")) {
563                                                 u_char *q;
564
565                                                 dir->fsckflags &= ~DIREMPTY;
566                                                 if (delete(f, boot, fat,
567                                                            empcl, empty - buffer,
568                                                            cl, p - buffer, 1) == FSFATAL)
569                                                         return FSFATAL;
570                                                 q = ((empcl == cl) ? empty : buffer);
571                                                 assert(q != NULL);
572                                                 for (; q < p; q += 32)
573                                                         *q = SLOT_DELETED;
574                                                 mod |= THISMOD|FSDIRMOD;
575                                         } else if (ask(0, "Truncate"))
576                                                 dir->fsckflags |= DIREMPWARN;
577                                 }
578                                 if (dir->fsckflags & DIREMPWARN) {
579                                         *p = SLOT_DELETED;
580                                         mod |= THISMOD|FSDIRMOD;
581                                         continue;
582                                 } else if (dir->fsckflags & DIREMPTY)
583                                         mod |= FSERROR;
584                                 empty = NULL;
585                         }
586
587                         if (p[11] == ATTR_WIN95) {
588                                 if (*p & LRFIRST) {
589                                         if (shortSum != -1) {
590                                                 if (!invlfn) {
591                                                         invlfn = vallfn;
592                                                         invcl = valcl;
593                                                 }
594                                         }
595                                         memset(longName, 0, sizeof longName);
596                                         shortSum = p[13];
597                                         vallfn = p;
598                                         valcl = cl;
599                                 } else if (shortSum != p[13]
600                                            || lidx != (*p & LRNOMASK)) {
601                                         if (!invlfn) {
602                                                 invlfn = vallfn;
603                                                 invcl = valcl;
604                                         }
605                                         if (!invlfn) {
606                                                 invlfn = p;
607                                                 invcl = cl;
608                                         }
609                                         vallfn = NULL;
610                                 }
611                                 lidx = *p & LRNOMASK;
612                                 if (lidx == 0) {
613                                         pwarn("invalid long name\n");
614                                         if (!invlfn) {
615                                                 invlfn = vallfn;
616                                                 invcl = valcl;
617                                         }
618                                         vallfn = NULL;
619                                         continue;
620                                 }
621                                 t = longName + --lidx * 13;
622                                 for (k = 1; k < 11 && t < longName +
623                                     sizeof(longName); k += 2) {
624                                         if (!p[k] && !p[k + 1])
625                                                 break;
626                                         *t++ = p[k];
627                                         /*
628                                          * Warn about those unusable chars in msdosfs here?     XXX
629                                          */
630                                         if (p[k + 1])
631                                                 t[-1] = '?';
632                                 }
633                                 if (k >= 11)
634                                         for (k = 14; k < 26 && t < longName + sizeof(longName); k += 2) {
635                                                 if (!p[k] && !p[k + 1])
636                                                         break;
637                                                 *t++ = p[k];
638                                                 if (p[k + 1])
639                                                         t[-1] = '?';
640                                         }
641                                 if (k >= 26)
642                                         for (k = 28; k < 32 && t < longName + sizeof(longName); k += 2) {
643                                                 if (!p[k] && !p[k + 1])
644                                                         break;
645                                                 *t++ = p[k];
646                                                 if (p[k + 1])
647                                                         t[-1] = '?';
648                                         }
649                                 if (t >= longName + sizeof(longName)) {
650                                         pwarn("long filename too long\n");
651                                         if (!invlfn) {
652                                                 invlfn = vallfn;
653                                                 invcl = valcl;
654                                         }
655                                         vallfn = NULL;
656                                 }
657                                 if (p[26] | (p[27] << 8)) {
658                                         pwarn("long filename record cluster start != 0\n");
659                                         if (!invlfn) {
660                                                 invlfn = vallfn;
661                                                 invcl = cl;
662                                         }
663                                         vallfn = NULL;
664                                 }
665                                 continue;       /* long records don't carry further
666                                                  * information */
667                         }
668
669                         /*
670                          * This is a standard msdosfs directory entry.
671                          */
672                         memset(&dirent, 0, sizeof dirent);
673
674                         /*
675                          * it's a short name record, but we need to know
676                          * more, so get the flags first.
677                          */
678                         dirent.flags = p[11];
679
680                         /*
681                          * Translate from 850 to ISO here               XXX
682                          */
683                         for (j = 0; j < 8; j++)
684                                 dirent.name[j] = p[j];
685                         dirent.name[8] = '\0';
686                         for (k = 7; k >= 0 && dirent.name[k] == ' '; k--)
687                                 dirent.name[k] = '\0';
688                         if (k < 0 || dirent.name[k] != '\0')
689                                 k++;
690                         if (dirent.name[0] == SLOT_E5)
691                                 dirent.name[0] = 0xe5;
692
693                         if (dirent.flags & ATTR_VOLUME) {
694                                 if (vallfn || invlfn) {
695                                         mod |= removede(f, boot, fat,
696                                                         invlfn ? invlfn : vallfn, p,
697                                                         invlfn ? invcl : valcl, -1, 0,
698                                                         fullpath(dir), 2);
699                                         vallfn = NULL;
700                                         invlfn = NULL;
701                                 }
702                                 continue;
703                         }
704
705                         if (p[8] != ' ')
706                                 dirent.name[k++] = '.';
707                         for (j = 0; j < 3; j++)
708                                 dirent.name[k++] = p[j+8];
709                         dirent.name[k] = '\0';
710                         for (k--; k >= 0 && dirent.name[k] == ' '; k--)
711                                 dirent.name[k] = '\0';
712
713                         if (vallfn && shortSum != calcShortSum(p)) {
714                                 if (!invlfn) {
715                                         invlfn = vallfn;
716                                         invcl = valcl;
717                                 }
718                                 vallfn = NULL;
719                         }
720                         dirent.head = p[26] | (p[27] << 8);
721                         if (boot->ClustMask == CLUST32_MASK)
722                                 dirent.head |= (p[20] << 16) | (p[21] << 24);
723                         dirent.size = p[28] | (p[29] << 8) | (p[30] << 16) | (p[31] << 24);
724                         if (vallfn) {
725                                 strlcpy(dirent.lname, longName,
726                                     sizeof(dirent.lname));
727                                 longName[0] = '\0';
728                                 shortSum = -1;
729                         }
730
731                         dirent.parent = dir;
732                         dirent.next = dir->child;
733
734                         if (invlfn) {
735                                 mod |= k = removede(f, boot, fat,
736                                                     invlfn, vallfn ? vallfn : p,
737                                                     invcl, vallfn ? valcl : cl, cl,
738                                                     fullpath(&dirent), 0);
739                                 if (mod & FSFATAL)
740                                         return FSFATAL;
741                                 if (vallfn
742                                     ? (valcl == cl && vallfn != buffer)
743                                     : p != buffer)
744                                         if (k & FSDIRMOD)
745                                                 mod |= THISMOD;
746                         }
747
748                         vallfn = NULL; /* not used any longer */
749                         invlfn = NULL;
750
751                         if (dirent.size == 0 && !(dirent.flags & ATTR_DIRECTORY)) {
752                                 if (dirent.head != 0) {
753                                         pwarn("%s has clusters, but size 0\n",
754                                               fullpath(&dirent));
755                                         if (ask(1, "Drop allocated clusters")) {
756                                                 p[26] = p[27] = 0;
757                                                 if (boot->ClustMask == CLUST32_MASK)
758                                                         p[20] = p[21] = 0;
759                                                 clearchain(boot, fat, dirent.head);
760                                                 dirent.head = 0;
761                                                 mod |= THISMOD|FSDIRMOD|FSFATMOD;
762                                         } else
763                                                 mod |= FSERROR;
764                                 }
765                         } else if (dirent.head == 0
766                                    && !strcmp(dirent.name, "..")
767                                    && dir->parent                       /* XXX */
768                                    && !dir->parent->parent) {
769                                 /*
770                                  *  Do nothing, the parent is the root
771                                  */
772                         } else if (dirent.head < CLUST_FIRST
773                                    || dirent.head >= boot->NumClusters
774                                    || fat[dirent.head].next == CLUST_FREE
775                                    || (fat[dirent.head].next >= CLUST_RSRVD
776                                        && fat[dirent.head].next < CLUST_EOFS)
777                                    || fat[dirent.head].head != dirent.head) {
778                                 if (dirent.head == 0)
779                                         pwarn("%s has no clusters\n",
780                                               fullpath(&dirent));
781                                 else if (dirent.head < CLUST_FIRST
782                                          || dirent.head >= boot->NumClusters)
783                                         pwarn("%s starts with cluster out of range(%u)\n",
784                                               fullpath(&dirent),
785                                               dirent.head);
786                                 else if (fat[dirent.head].next == CLUST_FREE)
787                                         pwarn("%s starts with free cluster\n",
788                                               fullpath(&dirent));
789                                 else if (fat[dirent.head].next >= CLUST_RSRVD)
790                                         pwarn("%s starts with cluster marked %s\n",
791                                               fullpath(&dirent),
792                                               rsrvdcltype(fat[dirent.head].next));
793                                 else
794                                         pwarn("%s doesn't start a cluster chain\n",
795                                               fullpath(&dirent));
796                                 if (dirent.flags & ATTR_DIRECTORY) {
797                                         if (ask(0, "Remove")) {
798                                                 *p = SLOT_DELETED;
799                                                 mod |= THISMOD|FSDIRMOD;
800                                         } else
801                                                 mod |= FSERROR;
802                                         continue;
803                                 } else {
804                                         if (ask(1, "Truncate")) {
805                                                 p[28] = p[29] = p[30] = p[31] = 0;
806                                                 p[26] = p[27] = 0;
807                                                 if (boot->ClustMask == CLUST32_MASK)
808                                                         p[20] = p[21] = 0;
809                                                 dirent.size = 0;
810                                                 mod |= THISMOD|FSDIRMOD;
811                                         } else
812                                                 mod |= FSERROR;
813                                 }
814                         }
815
816                         if (dirent.head >= CLUST_FIRST && dirent.head < boot->NumClusters)
817                                 fat[dirent.head].flags |= FAT_USED;
818
819                         if (dirent.flags & ATTR_DIRECTORY) {
820                                 /*
821                                  * gather more info for directories
822                                  */
823                                 struct dirTodoNode *n;
824
825                                 if (dirent.size) {
826                                         pwarn("Directory %s has size != 0\n",
827                                               fullpath(&dirent));
828                                         if (ask(1, "Correct")) {
829                                                 p[28] = p[29] = p[30] = p[31] = 0;
830                                                 dirent.size = 0;
831                                                 mod |= THISMOD|FSDIRMOD;
832                                         } else
833                                                 mod |= FSERROR;
834                                 }
835                                 /*
836                                  * handle `.' and `..' specially
837                                  */
838                                 if (strcmp(dirent.name, ".") == 0) {
839                                         if (dirent.head != dir->head) {
840                                                 pwarn("`.' entry in %s has incorrect start cluster\n",
841                                                       fullpath(dir));
842                                                 if (ask(1, "Correct")) {
843                                                         dirent.head = dir->head;
844                                                         p[26] = (u_char)dirent.head;
845                                                         p[27] = (u_char)(dirent.head >> 8);
846                                                         if (boot->ClustMask == CLUST32_MASK) {
847                                                                 p[20] = (u_char)(dirent.head >> 16);
848                                                                 p[21] = (u_char)(dirent.head >> 24);
849                                                         }
850                                                         mod |= THISMOD|FSDIRMOD;
851                                                 } else
852                                                         mod |= FSERROR;
853                                         }
854                                         continue;
855                                 }
856                                 if (strcmp(dirent.name, "..") == 0) {
857                                         if (dir->parent) {              /* XXX */
858                                                 if (!dir->parent->parent) {
859                                                         if (dirent.head) {
860                                                                 pwarn("`..' entry in %s has non-zero start cluster\n",
861                                                                       fullpath(dir));
862                                                                 if (ask(1, "Correct")) {
863                                                                         dirent.head = 0;
864                                                                         p[26] = p[27] = 0;
865                                                                         if (boot->ClustMask == CLUST32_MASK)
866                                                                                 p[20] = p[21] = 0;
867                                                                         mod |= THISMOD|FSDIRMOD;
868                                                                 } else
869                                                                         mod |= FSERROR;
870                                                         }
871                                                 } else if (dirent.head != dir->parent->head) {
872                                                         pwarn("`..' entry in %s has incorrect start cluster\n",
873                                                               fullpath(dir));
874                                                         if (ask(1, "Correct")) {
875                                                                 dirent.head = dir->parent->head;
876                                                                 p[26] = (u_char)dirent.head;
877                                                                 p[27] = (u_char)(dirent.head >> 8);
878                                                                 if (boot->ClustMask == CLUST32_MASK) {
879                                                                         p[20] = (u_char)(dirent.head >> 16);
880                                                                         p[21] = (u_char)(dirent.head >> 24);
881                                                                 }
882                                                                 mod |= THISMOD|FSDIRMOD;
883                                                         } else
884                                                                 mod |= FSERROR;
885                                                 }
886                                         }
887                                         continue;
888                                 } else {
889                                         /*
890                                          * Only one directory entry can point
891                                          * to dir->head, it's '.'.
892                                          */
893                                         if (dirent.head == dir->head) {
894                                                 pwarn("%s entry in %s has incorrect start cluster\n",
895                                                                 dirent.name, fullpath(dir));
896                                                 if (ask(1, "Remove")) {
897                                                         *p = SLOT_DELETED;
898                                                         mod |= THISMOD|FSDIRMOD;
899                                                 } else
900                                                         mod |= FSERROR;
901                                                 continue;
902                                         } else if ((check_subdirectory(f, boot,
903                                             &dirent) & FSERROR) == FSERROR) {
904                                                 /*
905                                                  * A subdirectory should have
906                                                  * a dot (.) entry and a dot-dot
907                                                  * (..) entry of ATTR_DIRECTORY,
908                                                  * we will inspect further when
909                                                  * traversing into it.
910                                                  */
911                                                 if (ask(1, "Remove")) {
912                                                         *p = SLOT_DELETED;
913                                                         mod |= THISMOD|FSDIRMOD;
914                                                 } else
915                                                         mod |= FSERROR;
916                                                 continue;
917                                         }
918                                 }
919
920                                 /* create directory tree node */
921                                 if (!(d = newDosDirEntry())) {
922                                         perr("No space for directory");
923                                         return FSFATAL;
924                                 }
925                                 memcpy(d, &dirent, sizeof(struct dosDirEntry));
926                                 /* link it into the tree */
927                                 dir->child = d;
928
929                                 /* Enter this directory into the todo list */
930                                 if (!(n = newDirTodo())) {
931                                         perr("No space for todo list");
932                                         return FSFATAL;
933                                 }
934                                 n->next = pendingDirectories;
935                                 n->dir = d;
936                                 pendingDirectories = n;
937                         } else {
938                                 mod |= k = checksize(boot, fat, p, &dirent);
939                                 if (k & FSDIRMOD)
940                                         mod |= THISMOD;
941                         }
942                         boot->NumFiles++;
943                 }
944
945                 if (!(boot->flags & FAT32) && !dir->parent)
946                         break;
947
948                 if (mod & THISMOD) {
949                         last *= 32;
950                         if (lseek(f, off, SEEK_SET) != off
951                             || write(f, buffer, last) != last) {
952                                 perr("Unable to write directory");
953                                 return FSFATAL;
954                         }
955                         mod &= ~THISMOD;
956                 }
957         } while ((cl = fat[cl].next) >= CLUST_FIRST && cl < boot->NumClusters);
958         if (invlfn || vallfn)
959                 mod |= removede(f, boot, fat,
960                                 invlfn ? invlfn : vallfn, p,
961                                 invlfn ? invcl : valcl, -1, 0,
962                                 fullpath(dir), 1);
963
964         /* The root directory of non fat32 filesystems is in a special
965          * area and may have been modified above without being written out.
966          */
967         if ((mod & FSDIRMOD) && !(boot->flags & FAT32) && !dir->parent) {
968                 last *= 32;
969                 if (lseek(f, off, SEEK_SET) != off
970                     || write(f, buffer, last) != last) {
971                         perr("Unable to write directory");
972                         return FSFATAL;
973                 }
974                 mod &= ~THISMOD;
975         }
976         return mod & ~THISMOD;
977 }
978
979 int
980 handleDirTree(int dosfs, struct bootblock *boot, struct fatEntry *fat)
981 {
982         int mod;
983
984         mod = readDosDirSection(dosfs, boot, fat, rootDir);
985         if (mod & FSFATAL)
986                 return FSFATAL;
987
988         /*
989          * process the directory todo list
990          */
991         while (pendingDirectories) {
992                 struct dosDirEntry *dir = pendingDirectories->dir;
993                 struct dirTodoNode *n = pendingDirectories->next;
994
995                 /*
996                  * remove TODO entry now, the list might change during
997                  * directory reads
998                  */
999                 freeDirTodo(pendingDirectories);
1000                 pendingDirectories = n;
1001
1002                 /*
1003                  * handle subdirectory
1004                  */
1005                 mod |= readDosDirSection(dosfs, boot, fat, dir);
1006                 if (mod & FSFATAL)
1007                         return FSFATAL;
1008         }
1009
1010         return mod;
1011 }
1012
1013 /*
1014  * Try to reconnect a FAT chain into dir
1015  */
1016 static u_char *lfbuf;
1017 static cl_t lfcl;
1018 static off_t lfoff;
1019
1020 int
1021 reconnect(int dosfs, struct bootblock *boot, struct fatEntry *fat, cl_t head)
1022 {
1023         struct dosDirEntry d;
1024         int len;
1025         u_char *p;
1026
1027         if (!ask(1, "Reconnect"))
1028                 return FSERROR;
1029
1030         if (!lostDir) {
1031                 for (lostDir = rootDir->child; lostDir; lostDir = lostDir->next) {
1032                         if (!strcmp(lostDir->name, LOSTDIR))
1033                                 break;
1034                 }
1035                 if (!lostDir) {         /* Create LOSTDIR?              XXX */
1036                         pwarn("No %s directory\n", LOSTDIR);
1037                         return FSERROR;
1038                 }
1039         }
1040         if (!lfbuf) {
1041                 lfbuf = malloc(boot->ClusterSize);
1042                 if (!lfbuf) {
1043                         perr("No space for buffer");
1044                         return FSFATAL;
1045                 }
1046                 p = NULL;
1047         } else
1048                 p = lfbuf;
1049         while (1) {
1050                 if (p)
1051                         for (; p < lfbuf + boot->ClusterSize; p += 32)
1052                                 if (*p == SLOT_EMPTY
1053                                     || *p == SLOT_DELETED)
1054                                         break;
1055                 if (p && p < lfbuf + boot->ClusterSize)
1056                         break;
1057                 lfcl = p ? fat[lfcl].next : lostDir->head;
1058                 if (lfcl < CLUST_FIRST || lfcl >= boot->NumClusters) {
1059                         /* Extend LOSTDIR?                              XXX */
1060                         pwarn("No space in %s\n", LOSTDIR);
1061                         lfcl = (lostDir->head < boot->NumClusters) ? lostDir->head : 0;
1062                         return FSERROR;
1063                 }
1064                 lfoff = lfcl * boot->ClusterSize
1065                     + boot->ClusterOffset * boot->bpbBytesPerSec;
1066                 if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
1067                     || (size_t)read(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
1068                         perr("could not read LOST.DIR");
1069                         return FSFATAL;
1070                 }
1071                 p = lfbuf;
1072         }
1073
1074         boot->NumFiles++;
1075         /* Ensure uniqueness of entry here!                             XXX */
1076         memset(&d, 0, sizeof d);
1077         /* worst case -1 = 4294967295, 10 digits */
1078         len = snprintf(d.name, sizeof(d.name), "%u", head);
1079         d.flags = 0;
1080         d.head = head;
1081         d.size = fat[head].length * boot->ClusterSize;
1082
1083         memcpy(p, d.name, len);
1084         memset(p + len, ' ', 11 - len);
1085         memset(p + 11, 0, 32 - 11);
1086         p[26] = (u_char)d.head;
1087         p[27] = (u_char)(d.head >> 8);
1088         if (boot->ClustMask == CLUST32_MASK) {
1089                 p[20] = (u_char)(d.head >> 16);
1090                 p[21] = (u_char)(d.head >> 24);
1091         }
1092         p[28] = (u_char)d.size;
1093         p[29] = (u_char)(d.size >> 8);
1094         p[30] = (u_char)(d.size >> 16);
1095         p[31] = (u_char)(d.size >> 24);
1096         fat[head].flags |= FAT_USED;
1097         if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
1098             || (size_t)write(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
1099                 perr("could not write LOST.DIR");
1100                 return FSFATAL;
1101         }
1102         return FSDIRMOD;
1103 }
1104
1105 void
1106 finishlf(void)
1107 {
1108         if (lfbuf)
1109                 free(lfbuf);
1110         lfbuf = NULL;
1111 }