b5cb53ea2ad4069dd98d59a4b35831b5fd583be7
[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         cl_t cl;
220         int ret = FSOK;
221
222         b1 = boot->bpbRootDirEnts * 32;
223         b2 = boot->bpbSecPerClust * boot->bpbBytesPerSec;
224
225         if ((buffer = malloc( b1 > b2 ? b1 : b2)) == NULL) {
226                 perr("No space for directory buffer");
227                 return FSFATAL;
228         }
229
230         if ((delbuf = malloc(b2)) == NULL) {
231                 free(buffer);
232                 perr("No space for directory delbuf");
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 || boot->bpbRootClust >= boot->NumClusters) {
246                         pfatal("Root directory starts with cluster out of range(%u)",
247                                boot->bpbRootClust);
248                         return FSFATAL;
249                 }
250                 cl = fat[boot->bpbRootClust].next;
251                 if (cl < CLUST_FIRST
252                     || (cl >= CLUST_RSRVD && cl< CLUST_EOFS)
253                     || fat[boot->bpbRootClust].head != boot->bpbRootClust) {
254                         if (cl == CLUST_FREE)
255                                 pwarn("Root directory starts with free cluster\n");
256                         else if (cl >= CLUST_RSRVD)
257                                 pwarn("Root directory starts with cluster marked %s\n",
258                                       rsrvdcltype(cl));
259                         else {
260                                 pfatal("Root directory doesn't start a cluster chain");
261                                 return FSFATAL;
262                         }
263                         if (ask(1, "Fix")) {
264                                 fat[boot->bpbRootClust].next = CLUST_FREE;
265                                 ret = FSFATMOD;
266                         } else
267                                 ret = FSFATAL;
268                 }
269
270                 fat[boot->bpbRootClust].flags |= FAT_USED;
271                 rootDir->head = boot->bpbRootClust;
272         }
273
274         return ret;
275 }
276
277 /*
278  * Cleanup after a directory scan
279  */
280 void
281 finishDosDirSection(void)
282 {
283         struct dirTodoNode *p, *np;
284         struct dosDirEntry *d, *nd;
285
286         for (p = pendingDirectories; p; p = np) {
287                 np = p->next;
288                 freeDirTodo(p);
289         }
290         pendingDirectories = NULL;
291         for (d = rootDir; d; d = nd) {
292                 if ((nd = d->child) != NULL) {
293                         d->child = 0;
294                         continue;
295                 }
296                 if (!(nd = d->next))
297                         nd = d->parent;
298                 freeDosDirEntry(d);
299         }
300         rootDir = lostDir = NULL;
301         free(buffer);
302         free(delbuf);
303         buffer = NULL;
304         delbuf = NULL;
305 }
306
307 /*
308  * Delete directory entries between startcl, startoff and endcl, endoff.
309  */
310 static int
311 delete(int f, struct bootblock *boot, struct fatEntry *fat, cl_t startcl,
312     int startoff, cl_t endcl, int endoff, int notlast)
313 {
314         u_char *s, *e;
315         off_t off;
316         int clsz = boot->bpbSecPerClust * boot->bpbBytesPerSec;
317
318         s = delbuf + startoff;
319         e = delbuf + clsz;
320         while (startcl >= CLUST_FIRST && startcl < boot->NumClusters) {
321                 if (startcl == endcl) {
322                         if (notlast)
323                                 break;
324                         e = delbuf + endoff;
325                 }
326                 off = startcl * boot->bpbSecPerClust + boot->ClusterOffset;
327                 off *= boot->bpbBytesPerSec;
328                 if (lseek(f, off, SEEK_SET) != off) {
329                         perr("Unable to lseek to %" PRId64, off);
330                         return FSFATAL;
331                 }
332                 if (read(f, delbuf, clsz) != clsz) {
333                         perr("Unable to read directory");
334                         return FSFATAL;
335                 }
336                 while (s < e) {
337                         *s = SLOT_DELETED;
338                         s += 32;
339                 }
340                 if (lseek(f, off, SEEK_SET) != off) {
341                         perr("Unable to lseek to %" PRId64, off);
342                         return FSFATAL;
343                 }
344                 if (write(f, delbuf, clsz) != clsz) {
345                         perr("Unable to write directory");
346                         return FSFATAL;
347                 }
348                 if (startcl == endcl)
349                         break;
350                 startcl = fat[startcl].next;
351                 s = delbuf;
352         }
353         return FSOK;
354 }
355
356 static int
357 removede(int f, struct bootblock *boot, struct fatEntry *fat, u_char *start,
358     u_char *end, cl_t startcl, cl_t endcl, cl_t curcl, char *path, int type)
359 {
360         switch (type) {
361         case 0:
362                 pwarn("Invalid long filename entry for %s\n", path);
363                 break;
364         case 1:
365                 pwarn("Invalid long filename entry at end of directory %s\n", path);
366                 break;
367         case 2:
368                 pwarn("Invalid long filename entry for volume label\n");
369                 break;
370         }
371         if (ask(0, "Remove")) {
372                 if (startcl != curcl) {
373                         if (delete(f, boot, fat,
374                                    startcl, start - buffer,
375                                    endcl, end - buffer,
376                                    endcl == curcl) == FSFATAL)
377                                 return FSFATAL;
378                         start = buffer;
379                 }
380                 /* startcl is < CLUST_FIRST for !fat32 root */
381                 if ((endcl == curcl) || (startcl < CLUST_FIRST))
382                         for (; start < end; start += 32)
383                                 *start = SLOT_DELETED;
384                 return FSDIRMOD;
385         }
386         return FSERROR;
387 }
388
389 /*
390  * Check an in-memory file entry
391  */
392 static int
393 checksize(struct bootblock *boot, struct fatEntry *fat, u_char *p,
394     struct dosDirEntry *dir)
395 {
396         /*
397          * Check size on ordinary files
398          */
399         u_int32_t physicalSize;
400
401         if (dir->head == CLUST_FREE)
402                 physicalSize = 0;
403         else {
404                 if (dir->head < CLUST_FIRST || dir->head >= boot->NumClusters)
405                         return FSERROR;
406                 physicalSize = fat[dir->head].length * boot->ClusterSize;
407         }
408         if (physicalSize < dir->size) {
409                 pwarn("size of %s is %u, should at most be %u\n",
410                       fullpath(dir), dir->size, physicalSize);
411                 if (ask(1, "Truncate")) {
412                         dir->size = physicalSize;
413                         p[28] = (u_char)physicalSize;
414                         p[29] = (u_char)(physicalSize >> 8);
415                         p[30] = (u_char)(physicalSize >> 16);
416                         p[31] = (u_char)(physicalSize >> 24);
417                         return FSDIRMOD;
418                 } else
419                         return FSERROR;
420         } else if (physicalSize - dir->size >= boot->ClusterSize) {
421                 pwarn("%s has too many clusters allocated\n",
422                       fullpath(dir));
423                 if (ask(1, "Drop superfluous clusters")) {
424                         cl_t cl;
425                         u_int32_t sz, len;
426
427                         for (cl = dir->head, len = sz = 0;
428                             (sz += boot->ClusterSize) < dir->size; len++)
429                                 cl = fat[cl].next;
430                         clearchain(boot, fat, fat[cl].next);
431                         fat[cl].next = CLUST_EOF;
432                         fat[dir->head].length = len;
433                         return FSFATMOD;
434                 } else
435                         return FSERROR;
436         }
437         return FSOK;
438 }
439
440 static const u_char dot_name[11]    = ".          ";
441 static const u_char dotdot_name[11] = "..         ";
442
443 /*
444  * Basic sanity check if the subdirectory have good '.' and '..' entries,
445  * and they are directory entries.  Further sanity checks are performed
446  * when we traverse into it.
447  */
448 static int
449 check_subdirectory(int f, struct bootblock *boot, struct dosDirEntry *dir)
450 {
451         u_char *buf, *cp;
452         off_t off;
453         cl_t cl;
454         int retval = FSOK;
455
456         cl = dir->head;
457         if (dir->parent && (cl < CLUST_FIRST || cl >= boot->NumClusters)) {
458                 return FSERROR;
459         }
460
461         if (!(boot->flags & FAT32) && !dir->parent) {
462                 off = boot->bpbResSectors + boot->bpbFATs *
463                         boot->FATsecs;
464         } else {
465                 off = cl * boot->bpbSecPerClust + boot->ClusterOffset;
466         }
467
468         /*
469          * We only need to check the first two entries of the directory,
470          * which is found in the first sector of the directory entry,
471          * so read in only the first sector.
472          */
473         buf = malloc(boot->bpbBytesPerSec);
474         if (buf == NULL) {
475                 perr("No space for directory buffer (%u)",
476                     boot->bpbBytesPerSec);
477                 return FSFATAL;
478         }
479
480         off *= boot->bpbBytesPerSec;
481         if (lseek(f, off, SEEK_SET) != off ||
482             read(f, buf, boot->bpbBytesPerSec) != boot->bpbBytesPerSec) {
483                 perr("Unable to read directory");
484                 free(buf);
485                 return FSFATAL;
486         }
487
488         /*
489          * Both `.' and `..' must be present and be the first two entries
490          * and be ATTR_DIRECTORY of a valid subdirectory.
491          */
492         cp = buf;
493         if (memcmp(cp, dot_name, sizeof(dot_name)) != 0 ||
494             (cp[11] & ATTR_DIRECTORY) != ATTR_DIRECTORY) {
495                 pwarn("%s: Incorrect `.' for %s.\n", __func__, dir->name);
496                 retval |= FSERROR;
497         }
498         cp += 32;
499         if (memcmp(cp, dotdot_name, sizeof(dotdot_name)) != 0 ||
500             (cp[11] & ATTR_DIRECTORY) != ATTR_DIRECTORY) {
501                 pwarn("%s: Incorrect `..' for %s. \n", __func__, dir->name);
502                 retval |= FSERROR;
503         }
504
505         free(buf);
506         return retval;
507 }
508
509 /*
510  * Read a directory and
511  *   - resolve long name records
512  *   - enter file and directory records into the parent's list
513  *   - push directories onto the todo-stack
514  */
515 static int
516 readDosDirSection(int f, struct bootblock *boot, struct fatEntry *fat,
517     struct dosDirEntry *dir)
518 {
519         struct dosDirEntry dirent, *d;
520         u_char *p, *vallfn, *invlfn, *empty;
521         off_t off;
522         int i, j, k, last;
523         cl_t cl, valcl = ~0, invcl = ~0, empcl = ~0;
524         char *t;
525         u_int lidx = 0;
526         int shortSum;
527         int mod = FSOK;
528 #define THISMOD 0x8000                  /* Only used within this routine */
529
530         cl = dir->head;
531         if (dir->parent && (cl < CLUST_FIRST || cl >= boot->NumClusters)) {
532                 /*
533                  * Already handled somewhere else.
534                  */
535                 return FSOK;
536         }
537         shortSum = -1;
538         vallfn = invlfn = empty = NULL;
539         do {
540                 if (!(boot->flags & FAT32) && !dir->parent) {
541                         last = boot->bpbRootDirEnts * 32;
542                         off = boot->bpbResSectors + boot->bpbFATs * boot->FATsecs;
543                 } else {
544                         last = boot->bpbSecPerClust * boot->bpbBytesPerSec;
545                         off = cl * boot->bpbSecPerClust + boot->ClusterOffset;
546                 }
547
548                 off *= boot->bpbBytesPerSec;
549                 if (lseek(f, off, SEEK_SET) != off
550                     || read(f, buffer, last) != last) {
551                         perr("Unable to read directory");
552                         return FSFATAL;
553                 }
554                 last /= 32;
555                 for (p = buffer, i = 0; i < last; i++, p += 32) {
556                         if (dir->fsckflags & DIREMPWARN) {
557                                 *p = SLOT_EMPTY;
558                                 continue;
559                         }
560
561                         if (*p == SLOT_EMPTY || *p == SLOT_DELETED) {
562                                 if (*p == SLOT_EMPTY) {
563                                         dir->fsckflags |= DIREMPTY;
564                                         empty = p;
565                                         empcl = cl;
566                                 }
567                                 continue;
568                         }
569
570                         if (dir->fsckflags & DIREMPTY) {
571                                 if (!(dir->fsckflags & DIREMPWARN)) {
572                                         pwarn("%s has entries after end of directory\n",
573                                               fullpath(dir));
574                                         if (ask(1, "Extend")) {
575                                                 u_char *q;
576
577                                                 dir->fsckflags &= ~DIREMPTY;
578                                                 if (delete(f, boot, fat,
579                                                            empcl, empty - buffer,
580                                                            cl, p - buffer, 1) == FSFATAL)
581                                                         return FSFATAL;
582                                                 q = ((empcl == cl) ? empty : buffer);
583                                                 assert(q != NULL);
584                                                 for (; q < p; q += 32)
585                                                         *q = SLOT_DELETED;
586                                                 mod |= THISMOD|FSDIRMOD;
587                                         } else if (ask(0, "Truncate"))
588                                                 dir->fsckflags |= DIREMPWARN;
589                                 }
590                                 if (dir->fsckflags & DIREMPWARN) {
591                                         *p = SLOT_DELETED;
592                                         mod |= THISMOD|FSDIRMOD;
593                                         continue;
594                                 } else if (dir->fsckflags & DIREMPTY)
595                                         mod |= FSERROR;
596                                 empty = NULL;
597                         }
598
599                         if (p[11] == ATTR_WIN95) {
600                                 if (*p & LRFIRST) {
601                                         if (shortSum != -1) {
602                                                 if (!invlfn) {
603                                                         invlfn = vallfn;
604                                                         invcl = valcl;
605                                                 }
606                                         }
607                                         memset(longName, 0, sizeof longName);
608                                         shortSum = p[13];
609                                         vallfn = p;
610                                         valcl = cl;
611                                 } else if (shortSum != p[13]
612                                            || lidx != (*p & LRNOMASK)) {
613                                         if (!invlfn) {
614                                                 invlfn = vallfn;
615                                                 invcl = valcl;
616                                         }
617                                         if (!invlfn) {
618                                                 invlfn = p;
619                                                 invcl = cl;
620                                         }
621                                         vallfn = NULL;
622                                 }
623                                 lidx = *p & LRNOMASK;
624                                 if (lidx == 0) {
625                                         pwarn("invalid long name\n");
626                                         if (!invlfn) {
627                                                 invlfn = vallfn;
628                                                 invcl = valcl;
629                                         }
630                                         vallfn = NULL;
631                                         continue;
632                                 }
633                                 t = longName + --lidx * 13;
634                                 for (k = 1; k < 11 && t < longName + sizeof(longName); k += 2) {
635                                         if (!p[k] && !p[k + 1])
636                                                 break;
637                                         *t++ = p[k];
638                                         /*
639                                          * Warn about those unusable chars in msdosfs here?     XXX
640                                          */
641                                         if (p[k + 1])
642                                                 t[-1] = '?';
643                                 }
644                                 if (k >= 11)
645                                         for (k = 14; k < 26 && t < longName + sizeof(longName); k += 2) {
646                                                 if (!p[k] && !p[k + 1])
647                                                         break;
648                                                 *t++ = p[k];
649                                                 if (p[k + 1])
650                                                         t[-1] = '?';
651                                         }
652                                 if (k >= 26)
653                                         for (k = 28; k < 32 && t < longName + sizeof(longName); k += 2) {
654                                                 if (!p[k] && !p[k + 1])
655                                                         break;
656                                                 *t++ = p[k];
657                                                 if (p[k + 1])
658                                                         t[-1] = '?';
659                                         }
660                                 if (t >= longName + sizeof(longName)) {
661                                         pwarn("long filename too long\n");
662                                         if (!invlfn) {
663                                                 invlfn = vallfn;
664                                                 invcl = valcl;
665                                         }
666                                         vallfn = NULL;
667                                 }
668                                 if (p[26] | (p[27] << 8)) {
669                                         pwarn("long filename record cluster start != 0\n");
670                                         if (!invlfn) {
671                                                 invlfn = vallfn;
672                                                 invcl = cl;
673                                         }
674                                         vallfn = NULL;
675                                 }
676                                 continue;       /* long records don't carry further
677                                                  * information */
678                         }
679
680                         /*
681                          * This is a standard msdosfs directory entry.
682                          */
683                         memset(&dirent, 0, sizeof dirent);
684
685                         /*
686                          * it's a short name record, but we need to know
687                          * more, so get the flags first.
688                          */
689                         dirent.flags = p[11];
690
691                         /*
692                          * Translate from 850 to ISO here               XXX
693                          */
694                         for (j = 0; j < 8; j++)
695                                 dirent.name[j] = p[j];
696                         dirent.name[8] = '\0';
697                         for (k = 7; k >= 0 && dirent.name[k] == ' '; k--)
698                                 dirent.name[k] = '\0';
699                         if (dirent.name[k] != '\0')
700                                 k++;
701                         if (dirent.name[0] == SLOT_E5)
702                                 dirent.name[0] = 0xe5;
703
704                         if (dirent.flags & ATTR_VOLUME) {
705                                 if (vallfn || invlfn) {
706                                         mod |= removede(f, boot, fat,
707                                                         invlfn ? invlfn : vallfn, p,
708                                                         invlfn ? invcl : valcl, -1, 0,
709                                                         fullpath(dir), 2);
710                                         vallfn = NULL;
711                                         invlfn = NULL;
712                                 }
713                                 continue;
714                         }
715
716                         if (p[8] != ' ')
717                                 dirent.name[k++] = '.';
718                         for (j = 0; j < 3; j++)
719                                 dirent.name[k++] = p[j+8];
720                         dirent.name[k] = '\0';
721                         for (k--; k >= 0 && dirent.name[k] == ' '; k--)
722                                 dirent.name[k] = '\0';
723
724                         if (vallfn && shortSum != calcShortSum(p)) {
725                                 if (!invlfn) {
726                                         invlfn = vallfn;
727                                         invcl = valcl;
728                                 }
729                                 vallfn = NULL;
730                         }
731                         dirent.head = p[26] | (p[27] << 8);
732                         if (boot->ClustMask == CLUST32_MASK)
733                                 dirent.head |= (p[20] << 16) | (p[21] << 24);
734                         dirent.size = p[28] | (p[29] << 8) | (p[30] << 16) | (p[31] << 24);
735                         if (vallfn) {
736                                 strlcpy(dirent.lname, longName,
737                                     sizeof(dirent.lname));
738                                 longName[0] = '\0';
739                                 shortSum = -1;
740                         }
741
742                         dirent.parent = dir;
743                         dirent.next = dir->child;
744
745                         if (invlfn) {
746                                 mod |= k = removede(f, boot, fat,
747                                                     invlfn, vallfn ? vallfn : p,
748                                                     invcl, vallfn ? valcl : cl, cl,
749                                                     fullpath(&dirent), 0);
750                                 if (mod & FSFATAL)
751                                         return FSFATAL;
752                                 if (vallfn
753                                     ? (valcl == cl && vallfn != buffer)
754                                     : p != buffer)
755                                         if (k & FSDIRMOD)
756                                                 mod |= THISMOD;
757                         }
758
759                         vallfn = NULL; /* not used any longer */
760                         invlfn = NULL;
761
762                         if (dirent.size == 0 && !(dirent.flags & ATTR_DIRECTORY)) {
763                                 if (dirent.head != 0) {
764                                         pwarn("%s has clusters, but size 0\n",
765                                               fullpath(&dirent));
766                                         if (ask(1, "Drop allocated clusters")) {
767                                                 p[26] = p[27] = 0;
768                                                 if (boot->ClustMask == CLUST32_MASK)
769                                                         p[20] = p[21] = 0;
770                                                 clearchain(boot, fat, dirent.head);
771                                                 dirent.head = 0;
772                                                 mod |= THISMOD|FSDIRMOD|FSFATMOD;
773                                         } else
774                                                 mod |= FSERROR;
775                                 }
776                         } else if (dirent.head == 0
777                                    && !strcmp(dirent.name, "..")
778                                    && dir->parent                       /* XXX */
779                                    && !dir->parent->parent) {
780                                 /*
781                                  *  Do nothing, the parent is the root
782                                  */
783                         } else if (dirent.head < CLUST_FIRST
784                                    || dirent.head >= boot->NumClusters
785                                    || fat[dirent.head].next == CLUST_FREE
786                                    || (fat[dirent.head].next >= CLUST_RSRVD
787                                        && fat[dirent.head].next < CLUST_EOFS)
788                                    || fat[dirent.head].head != dirent.head) {
789                                 if (dirent.head == 0)
790                                         pwarn("%s has no clusters\n",
791                                               fullpath(&dirent));
792                                 else if (dirent.head < CLUST_FIRST
793                                          || dirent.head >= boot->NumClusters)
794                                         pwarn("%s starts with cluster out of range(%u)\n",
795                                               fullpath(&dirent),
796                                               dirent.head);
797                                 else if (fat[dirent.head].next == CLUST_FREE)
798                                         pwarn("%s starts with free cluster\n",
799                                               fullpath(&dirent));
800                                 else if (fat[dirent.head].next >= CLUST_RSRVD)
801                                         pwarn("%s starts with cluster marked %s\n",
802                                               fullpath(&dirent),
803                                               rsrvdcltype(fat[dirent.head].next));
804                                 else
805                                         pwarn("%s doesn't start a cluster chain\n",
806                                               fullpath(&dirent));
807                                 if (dirent.flags & ATTR_DIRECTORY) {
808                                         if (ask(0, "Remove")) {
809                                                 *p = SLOT_DELETED;
810                                                 mod |= THISMOD|FSDIRMOD;
811                                         } else
812                                                 mod |= FSERROR;
813                                         continue;
814                                 } else {
815                                         if (ask(1, "Truncate")) {
816                                                 p[28] = p[29] = p[30] = p[31] = 0;
817                                                 p[26] = p[27] = 0;
818                                                 if (boot->ClustMask == CLUST32_MASK)
819                                                         p[20] = p[21] = 0;
820                                                 dirent.size = 0;
821                                                 mod |= THISMOD|FSDIRMOD;
822                                         } else
823                                                 mod |= FSERROR;
824                                 }
825                         }
826
827                         if (dirent.head >= CLUST_FIRST && dirent.head < boot->NumClusters)
828                                 fat[dirent.head].flags |= FAT_USED;
829
830                         if (dirent.flags & ATTR_DIRECTORY) {
831                                 /*
832                                  * gather more info for directories
833                                  */
834                                 struct dirTodoNode *n;
835
836                                 if (dirent.size) {
837                                         pwarn("Directory %s has size != 0\n",
838                                               fullpath(&dirent));
839                                         if (ask(1, "Correct")) {
840                                                 p[28] = p[29] = p[30] = p[31] = 0;
841                                                 dirent.size = 0;
842                                                 mod |= THISMOD|FSDIRMOD;
843                                         } else
844                                                 mod |= FSERROR;
845                                 }
846                                 /*
847                                  * handle `.' and `..' specially
848                                  */
849                                 if (strcmp(dirent.name, ".") == 0) {
850                                         if (dirent.head != dir->head) {
851                                                 pwarn("`.' entry in %s has incorrect start cluster\n",
852                                                       fullpath(dir));
853                                                 if (ask(1, "Correct")) {
854                                                         dirent.head = dir->head;
855                                                         p[26] = (u_char)dirent.head;
856                                                         p[27] = (u_char)(dirent.head >> 8);
857                                                         if (boot->ClustMask == CLUST32_MASK) {
858                                                                 p[20] = (u_char)(dirent.head >> 16);
859                                                                 p[21] = (u_char)(dirent.head >> 24);
860                                                         }
861                                                         mod |= THISMOD|FSDIRMOD;
862                                                 } else
863                                                         mod |= FSERROR;
864                                         }
865                                         continue;
866                                 }
867                                 if (strcmp(dirent.name, "..") == 0) {
868                                         if (dir->parent) {              /* XXX */
869                                                 if (!dir->parent->parent) {
870                                                         if (dirent.head) {
871                                                                 pwarn("`..' entry in %s has non-zero start cluster\n",
872                                                                       fullpath(dir));
873                                                                 if (ask(1, "Correct")) {
874                                                                         dirent.head = 0;
875                                                                         p[26] = p[27] = 0;
876                                                                         if (boot->ClustMask == CLUST32_MASK)
877                                                                                 p[20] = p[21] = 0;
878                                                                         mod |= THISMOD|FSDIRMOD;
879                                                                 } else
880                                                                         mod |= FSERROR;
881                                                         }
882                                                 } else if (dirent.head != dir->parent->head) {
883                                                         pwarn("`..' entry in %s has incorrect start cluster\n",
884                                                               fullpath(dir));
885                                                         if (ask(1, "Correct")) {
886                                                                 dirent.head = dir->parent->head;
887                                                                 p[26] = (u_char)dirent.head;
888                                                                 p[27] = (u_char)(dirent.head >> 8);
889                                                                 if (boot->ClustMask == CLUST32_MASK) {
890                                                                         p[20] = (u_char)(dirent.head >> 16);
891                                                                         p[21] = (u_char)(dirent.head >> 24);
892                                                                 }
893                                                                 mod |= THISMOD|FSDIRMOD;
894                                                         } else
895                                                                 mod |= FSERROR;
896                                                 }
897                                         }
898                                         continue;
899                                 } else {
900                                         /*
901                                          * Only one directory entry can point
902                                          * to dir->head, it's '.'.
903                                          */
904                                         if (dirent.head == dir->head) {
905                                                 pwarn("%s entry in %s has incorrect start cluster\n",
906                                                                 dirent.name, fullpath(dir));
907                                                 if (ask(1, "Remove")) {
908                                                         *p = SLOT_DELETED;
909                                                         mod |= THISMOD|FSDIRMOD;
910                                                 } else
911                                                         mod |= FSERROR;
912                                                 continue;
913                                         } else if ((check_subdirectory(f, boot,
914                                             &dirent) & FSERROR) == FSERROR) {
915                                                 /*
916                                                  * A subdirectory should have
917                                                  * a dot (.) entry and a dot-dot
918                                                  * (..) entry of ATTR_DIRECTORY,
919                                                  * we will inspect further when
920                                                  * traversing into it.
921                                                  */
922                                                 if (ask(1, "Remove")) {
923                                                         *p = SLOT_DELETED;
924                                                         mod |= THISMOD|FSDIRMOD;
925                                                 } else
926                                                         mod |= FSERROR;
927                                                 continue;
928                                         }
929                                 }
930
931                                 /* create directory tree node */
932                                 if (!(d = newDosDirEntry())) {
933                                         perr("No space for directory");
934                                         return FSFATAL;
935                                 }
936                                 memcpy(d, &dirent, sizeof(struct dosDirEntry));
937                                 /* link it into the tree */
938                                 dir->child = d;
939
940                                 /* Enter this directory into the todo list */
941                                 if (!(n = newDirTodo())) {
942                                         perr("No space for todo list");
943                                         return FSFATAL;
944                                 }
945                                 n->next = pendingDirectories;
946                                 n->dir = d;
947                                 pendingDirectories = n;
948                         } else {
949                                 mod |= k = checksize(boot, fat, p, &dirent);
950                                 if (k & FSDIRMOD)
951                                         mod |= THISMOD;
952                         }
953                         boot->NumFiles++;
954                 }
955
956                 if (!(boot->flags & FAT32) && !dir->parent)
957                         break;
958
959                 if (mod & THISMOD) {
960                         last *= 32;
961                         if (lseek(f, off, SEEK_SET) != off
962                             || write(f, buffer, last) != last) {
963                                 perr("Unable to write directory");
964                                 return FSFATAL;
965                         }
966                         mod &= ~THISMOD;
967                 }
968         } while ((cl = fat[cl].next) >= CLUST_FIRST && cl < boot->NumClusters);
969         if (invlfn || vallfn)
970                 mod |= removede(f, boot, fat,
971                                 invlfn ? invlfn : vallfn, p,
972                                 invlfn ? invcl : valcl, -1, 0,
973                                 fullpath(dir), 1);
974
975         /* The root directory of non fat32 filesystems is in a special
976          * area and may have been modified above without being written out.
977          */
978         if ((mod & FSDIRMOD) && !(boot->flags & FAT32) && !dir->parent) {
979                 last *= 32;
980                 if (lseek(f, off, SEEK_SET) != off
981                     || write(f, buffer, last) != last) {
982                         perr("Unable to write directory");
983                         return FSFATAL;
984                 }
985                 mod &= ~THISMOD;
986         }
987         return mod & ~THISMOD;
988 }
989
990 int
991 handleDirTree(int dosfs, struct bootblock *boot, struct fatEntry *fat)
992 {
993         int mod;
994
995         mod = readDosDirSection(dosfs, boot, fat, rootDir);
996         if (mod & FSFATAL)
997                 return FSFATAL;
998
999         /*
1000          * process the directory todo list
1001          */
1002         while (pendingDirectories) {
1003                 struct dosDirEntry *dir = pendingDirectories->dir;
1004                 struct dirTodoNode *n = pendingDirectories->next;
1005
1006                 /*
1007                  * remove TODO entry now, the list might change during
1008                  * directory reads
1009                  */
1010                 freeDirTodo(pendingDirectories);
1011                 pendingDirectories = n;
1012
1013                 /*
1014                  * handle subdirectory
1015                  */
1016                 mod |= readDosDirSection(dosfs, boot, fat, dir);
1017                 if (mod & FSFATAL)
1018                         return FSFATAL;
1019         }
1020
1021         return mod;
1022 }
1023
1024 /*
1025  * Try to reconnect a FAT chain into dir
1026  */
1027 static u_char *lfbuf;
1028 static cl_t lfcl;
1029 static off_t lfoff;
1030
1031 int
1032 reconnect(int dosfs, struct bootblock *boot, struct fatEntry *fat, cl_t head)
1033 {
1034         struct dosDirEntry d;
1035         u_char *p;
1036
1037         if (!ask(1, "Reconnect"))
1038                 return FSERROR;
1039
1040         if (!lostDir) {
1041                 for (lostDir = rootDir->child; lostDir; lostDir = lostDir->next) {
1042                         if (!strcmp(lostDir->name, LOSTDIR))
1043                                 break;
1044                 }
1045                 if (!lostDir) {         /* Create LOSTDIR?              XXX */
1046                         pwarn("No %s directory\n", LOSTDIR);
1047                         return FSERROR;
1048                 }
1049         }
1050         if (!lfbuf) {
1051                 lfbuf = malloc(boot->ClusterSize);
1052                 if (!lfbuf) {
1053                         perr("No space for buffer");
1054                         return FSFATAL;
1055                 }
1056                 p = NULL;
1057         } else
1058                 p = lfbuf;
1059         while (1) {
1060                 if (p)
1061                         for (; p < lfbuf + boot->ClusterSize; p += 32)
1062                                 if (*p == SLOT_EMPTY
1063                                     || *p == SLOT_DELETED)
1064                                         break;
1065                 if (p && p < lfbuf + boot->ClusterSize)
1066                         break;
1067                 lfcl = p ? fat[lfcl].next : lostDir->head;
1068                 if (lfcl < CLUST_FIRST || lfcl >= boot->NumClusters) {
1069                         /* Extend LOSTDIR?                              XXX */
1070                         pwarn("No space in %s\n", LOSTDIR);
1071                         lfcl = (lostDir->head < boot->NumClusters) ? lostDir->head : 0;
1072                         return FSERROR;
1073                 }
1074                 lfoff = lfcl * boot->ClusterSize
1075                     + boot->ClusterOffset * boot->bpbBytesPerSec;
1076                 if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
1077                     || (size_t)read(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
1078                         perr("could not read LOST.DIR");
1079                         return FSFATAL;
1080                 }
1081                 p = lfbuf;
1082         }
1083
1084         boot->NumFiles++;
1085         /* Ensure uniqueness of entry here!                             XXX */
1086         memset(&d, 0, sizeof d);
1087         snprintf(d.name, sizeof(d.name), "%u", head);
1088         d.flags = 0;
1089         d.head = head;
1090         d.size = fat[head].length * boot->ClusterSize;
1091
1092         memset(p, 0, 32);
1093         memset(p, ' ', 11);
1094         memcpy(p, d.name, strlen(d.name));
1095         p[26] = (u_char)d.head;
1096         p[27] = (u_char)(d.head >> 8);
1097         if (boot->ClustMask == CLUST32_MASK) {
1098                 p[20] = (u_char)(d.head >> 16);
1099                 p[21] = (u_char)(d.head >> 24);
1100         }
1101         p[28] = (u_char)d.size;
1102         p[29] = (u_char)(d.size >> 8);
1103         p[30] = (u_char)(d.size >> 16);
1104         p[31] = (u_char)(d.size >> 24);
1105         fat[head].flags |= FAT_USED;
1106         if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
1107             || (size_t)write(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
1108                 perr("could not write LOST.DIR");
1109                 return FSFATAL;
1110         }
1111         return FSDIRMOD;
1112 }
1113
1114 void
1115 finishlf(void)
1116 {
1117         if (lfbuf)
1118                 free(lfbuf);
1119         lfbuf = NULL;
1120 }