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