01024ab69ef8a874af82447de7f78552b2abfcd4
[games.git] / sbin / fsck_msdosfs / dir.c
1 /*
2  * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank
3  * Copyright (c) 1995 Martin Husemann
4  * Some structure declaration borrowed from Paul Popelka
5  * (paulp@uts.amdahl.com), see /sys/msdosfs/ for reference.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgement:
17  *      This product includes software developed by Martin Husemann
18  *      and Wolfgang Solfrank.
19  * 4. Neither the name of the University nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
24  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
25  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
26  * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
28  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
32  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33  *
34  * $NetBSD: dir.c,v 1.14 1998/08/25 19:18:15 ross Exp $
35  * $FreeBSD: src/sbin/fsck_msdosfs/dir.c,v 1.1.2.1 2001/08/01 05:47:55 obrien Exp $
36  */
37
38
39 #include <sys/cdefs.h>
40
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(int, struct bootblock *, struct fatEntry *, cl_t, int,
100     cl_t, int, int);
101 static int removede(int, struct bootblock *, struct fatEntry *, u_char *,
102     u_char *, cl_t, cl_t, cl_t, char *, int);
103 static int checksize(struct bootblock *, struct fatEntry *, u_char *,
104     struct dosDirEntry *);
105 static int readDosDirSection(int, struct bootblock *, struct fatEntry *,
106     struct dosDirEntry *);
107
108 /*
109  * Manage free dosDirEntry structures.
110  */
111 static struct dosDirEntry *freede;
112
113 static struct dosDirEntry *
114 newDosDirEntry(void)
115 {
116         struct dosDirEntry *de;
117
118         if (!(de = freede)) {
119                 if (!(de = (struct dosDirEntry *)malloc(sizeof *de)))
120                         return 0;
121         } else
122                 freede = de->next;
123         return de;
124 }
125
126 static void
127 freeDosDirEntry(struct dosDirEntry *de)
128 {
129         de->next = freede;
130         freede = de;
131 }
132
133 /*
134  * The same for dirTodoNode structures.
135  */
136 static struct dirTodoNode *freedt;
137
138 static struct dirTodoNode *
139 newDirTodo(void)
140 {
141         struct dirTodoNode *dt;
142
143         if (!(dt = freedt)) {
144                 if (!(dt = (struct dirTodoNode *)malloc(sizeof *dt)))
145                         return 0;
146         } else
147                 freedt = dt->next;
148         return dt;
149 }
150
151 static void
152 freeDirTodo(struct dirTodoNode *dt)
153 {
154         dt->next = freedt;
155         freedt = dt;
156 }
157
158 /*
159  * The stack of unread directories
160  */
161 struct dirTodoNode *pendingDirectories = NULL;
162
163 /*
164  * Return the full pathname for a directory entry.
165  */
166 static char *
167 fullpath(struct dosDirEntry *dir)
168 {
169         static char namebuf[MAXPATHLEN + 1];
170         char *cp, *np;
171         int nl;
172
173         cp = namebuf + sizeof namebuf - 1;
174         *cp = '\0';
175         do {
176                 np = dir->lname[0] ? dir->lname : dir->name;
177                 nl = strlen(np);
178                 if ((cp -= nl) <= namebuf + 1)
179                         break;
180                 memcpy(cp, np, nl);
181                 *--cp = '/';
182         } while ((dir = dir->parent) != NULL);
183         if (dir)
184                 *--cp = '?';
185         else
186                 cp++;
187         return cp;
188 }
189
190 /*
191  * Calculate a checksum over an 8.3 alias name
192  */
193 static u_char
194 calcShortSum(u_char *p)
195 {
196         u_char sum = 0;
197         int i;
198
199         for (i = 0; i < 11; i++) {
200                 sum = (sum << 7)|(sum >> 1);    /* rotate right */
201                 sum += p[i];
202         }
203
204         return sum;
205 }
206
207 /*
208  * Global variables temporarily used during a directory scan
209  */
210 static char longName[DOSLONGNAMELEN] = "";
211 static u_char *buffer = NULL;
212 static u_char *delbuf = NULL;
213
214 struct dosDirEntry *rootDir;
215 static struct dosDirEntry *lostDir;
216
217 /*
218  * Init internal state for a new directory scan.
219  */
220 int
221 resetDosDirSection(struct bootblock *boot, struct fatEntry *fat)
222 {
223         int b1, b2;
224         cl_t cl;
225         int ret = FSOK;
226
227         b1 = boot->RootDirEnts * 32;
228         b2 = boot->SecPerClust * boot->BytesPerSec;
229
230         if (!(buffer = malloc(b1 > b2 ? b1 : b2))
231             || !(delbuf = malloc(b2))
232             || !(rootDir = newDosDirEntry())) {
233                 perror("No space for directory");
234                 return FSFATAL;
235         }
236         memset(rootDir, 0, sizeof *rootDir);
237         if (boot->flags & FAT32) {
238                 if (boot->RootCl < CLUST_FIRST || boot->RootCl >= boot->NumClusters) {
239                         pfatal("Root directory starts with cluster out of range(%u)",
240                                boot->RootCl);
241                         return FSFATAL;
242                 }
243                 cl = fat[boot->RootCl].next;
244                 if (cl < CLUST_FIRST
245                     || (cl >= CLUST_RSRVD && cl< CLUST_EOFS)
246                     || fat[boot->RootCl].head != boot->RootCl) {
247                         if (cl == CLUST_FREE)
248                                 pwarn("Root directory starts with free cluster\n");
249                         else if (cl >= CLUST_RSRVD)
250                                 pwarn("Root directory starts with cluster marked %s\n",
251                                       rsrvdcltype(cl));
252                         else {
253                                 pfatal("Root directory doesn't start a cluster chain");
254                                 return FSFATAL;
255                         }
256                         if (ask(1, "Fix")) {
257                                 fat[boot->RootCl].next = CLUST_FREE;
258                                 ret = FSFATMOD;
259                         } else
260                                 ret = FSFATAL;
261                 }
262
263                 fat[boot->RootCl].flags |= FAT_USED;
264                 rootDir->head = boot->RootCl;
265         }
266
267         return ret;
268 }
269
270 /*
271  * Cleanup after a directory scan
272  */
273 void
274 finishDosDirSection(void)
275 {
276         struct dirTodoNode *p, *np;
277         struct dosDirEntry *d, *nd;
278
279         for (p = pendingDirectories; p; p = np) {
280                 np = p->next;
281                 freeDirTodo(p);
282         }
283         pendingDirectories = NULL;
284         for (d = rootDir; d; d = nd) {
285                 if ((nd = d->child) != NULL) {
286                         d->child = 0;
287                         continue;
288                 }
289                 if (!(nd = d->next))
290                         nd = d->parent;
291                 freeDosDirEntry(d);
292         }
293         rootDir = lostDir = NULL;
294         free(buffer);
295         free(delbuf);
296         buffer = NULL;
297         delbuf = NULL;
298 }
299
300 /*
301  * Delete directory entries between startcl, startoff and endcl, endoff.
302  */
303 static int
304 delete(int f, struct bootblock *boot, struct fatEntry *fat, cl_t startcl,
305        int startoff, cl_t endcl, int endoff, int notlast)
306 {
307         u_char *s, *e;
308         off_t off;
309         int clsz = boot->SecPerClust * boot->BytesPerSec;
310
311         s = delbuf + startoff;
312         e = delbuf + clsz;
313         while (startcl >= CLUST_FIRST && startcl < boot->NumClusters) {
314                 if (startcl == endcl) {
315                         if (notlast)
316                                 break;
317                         e = delbuf + endoff;
318                 }
319                 off = startcl * boot->SecPerClust + boot->ClusterOffset;
320                 off *= boot->BytesPerSec;
321                 if (lseek(f, off, SEEK_SET) != off
322                     || read(f, delbuf, clsz) != clsz) {
323                         perror("Unable to read directory");
324                         return FSFATAL;
325                 }
326                 while (s < e) {
327                         *s = SLOT_DELETED;
328                         s += 32;
329                 }
330                 if (lseek(f, off, SEEK_SET) != off
331                     || write(f, delbuf, clsz) != clsz) {
332                         perror("Unable to write directory");
333                         return FSFATAL;
334                 }
335                 if (startcl == endcl)
336                         break;
337                 startcl = fat[startcl].next;
338                 s = delbuf;
339         }
340         return FSOK;
341 }
342
343 static int
344 removede(int f, struct bootblock *boot, struct fatEntry *fat, u_char *start,
345          u_char *end, cl_t startcl, cl_t endcl, cl_t curcl, char *path,
346          int type)
347 {
348         switch (type) {
349         case 0:
350                 pwarn("Invalid long filename entry for %s\n", path);
351                 break;
352         case 1:
353                 pwarn("Invalid long filename entry at end of directory %s\n", path);
354                 break;
355         case 2:
356                 pwarn("Invalid long filename entry for volume label\n");
357                 break;
358         }
359         if (ask(0, "Remove")) {
360                 if (startcl != curcl) {
361                         if (delete(f, boot, fat,
362                                    startcl, start - buffer,
363                                    endcl, end - buffer,
364                                    endcl == curcl) == FSFATAL)
365                                 return FSFATAL;
366                         start = buffer;
367                 }
368                 if (endcl == curcl)
369                         for (; start < end; start += 32)
370                                 *start = SLOT_DELETED;
371                 return FSDIRMOD;
372         }
373         return FSERROR;
374 }
375
376 /*
377  * Check an in-memory file entry
378  */
379 static int
380 checksize(struct bootblock *boot, struct fatEntry *fat, u_char *p,
381           struct dosDirEntry *dir)
382 {
383         /*
384          * Check size on ordinary files
385          */
386         int32_t physicalSize;
387
388         if (dir->head == CLUST_FREE)
389                 physicalSize = 0;
390         else {
391                 if (dir->head < CLUST_FIRST || dir->head >= boot->NumClusters)
392                         return FSERROR;
393                 physicalSize = fat[dir->head].length * boot->ClusterSize;
394         }
395         if (physicalSize < dir->size) {
396                 pwarn("size of %s is %u, should at most be %u\n",
397                       fullpath(dir), dir->size, physicalSize);
398                 if (ask(1, "Truncate")) {
399                         dir->size = physicalSize;
400                         p[28] = (u_char)physicalSize;
401                         p[29] = (u_char)(physicalSize >> 8);
402                         p[30] = (u_char)(physicalSize >> 16);
403                         p[31] = (u_char)(physicalSize >> 24);
404                         return FSDIRMOD;
405                 } else
406                         return FSERROR;
407         } else if (physicalSize - dir->size >= boot->ClusterSize) {
408                 pwarn("%s has too many clusters allocated\n",
409                       fullpath(dir));
410                 if (ask(1, "Drop superfluous clusters")) {
411                         cl_t cl;
412                         u_int32_t sz = 0;
413
414                         for (cl = dir->head; (sz += boot->ClusterSize) < dir->size;)
415                                 cl = fat[cl].next;
416                         clearchain(boot, fat, fat[cl].next);
417                         fat[cl].next = CLUST_EOF;
418                         return FSFATMOD;
419                 } else
420                         return FSERROR;
421         }
422         return FSOK;
423 }
424
425 /*
426  * Read a directory and
427  *   - resolve long name records
428  *   - enter file and directory records into the parent's list
429  *   - push directories onto the todo-stack
430  */
431 static int
432 readDosDirSection(int f, struct bootblock *boot, struct fatEntry *fat,
433                   struct dosDirEntry *dir)
434 {
435         struct dosDirEntry dirent, *d;
436         u_char *p, *vallfn, *invlfn, *empty;
437         off_t off;
438         int i, j, k, last;
439         cl_t cl, valcl = ~0, invcl = ~0, empcl = ~0;
440         char *t;
441         u_int lidx = 0;
442         int shortSum;
443         int mod = FSOK;
444 #define THISMOD 0x8000                  /* Only used within this routine */
445
446         cl = dir->head;
447         if (dir->parent && (cl < CLUST_FIRST || cl >= boot->NumClusters)) {
448                 /*
449                  * Already handled somewhere else.
450                  */
451                 return FSOK;
452         }
453         shortSum = -1;
454         vallfn = invlfn = empty = NULL;
455         do {
456                 if (!(boot->flags & FAT32) && !dir->parent) {
457                         last = boot->RootDirEnts * 32;
458                         off = boot->ResSectors + boot->FATs * boot->FATsecs;
459                 } else {
460                         last = boot->SecPerClust * boot->BytesPerSec;
461                         off = cl * boot->SecPerClust + boot->ClusterOffset;
462                 }
463
464                 off *= boot->BytesPerSec;
465                 if (lseek(f, off, SEEK_SET) != off
466                     || read(f, buffer, last) != last) {
467                         perror("Unable to read directory");
468                         return FSFATAL;
469                 }
470                 last /= 32;
471                 /*
472                  * Check `.' and `..' entries here?                     XXX
473                  */
474                 for (p = buffer, i = 0; i < last; i++, p += 32) {
475                         if (dir->fsckflags & DIREMPWARN) {
476                                 *p = SLOT_EMPTY;
477                                 continue;
478                         }
479
480                         if (*p == SLOT_EMPTY || *p == SLOT_DELETED) {
481                                 if (*p == SLOT_EMPTY) {
482                                         dir->fsckflags |= DIREMPTY;
483                                         empty = p;
484                                         empcl = cl;
485                                 }
486                                 continue;
487                         }
488
489                         if (dir->fsckflags & DIREMPTY) {
490                                 if (!(dir->fsckflags & DIREMPWARN)) {
491                                         pwarn("%s has entries after end of directory\n",
492                                               fullpath(dir));
493                                         if (ask(1, "Extend")) {
494                                                 u_char *q;
495
496                                                 dir->fsckflags &= ~DIREMPTY;
497                                                 if (delete(f, boot, fat,
498                                                            empcl, empty - buffer,
499                                                            cl, p - buffer, 1) == FSFATAL)
500                                                         return FSFATAL;
501                                                 q = empcl == cl ? empty : buffer;
502                                                 for (; q < p; q += 32)
503                                                         *q = SLOT_DELETED;
504                                                 mod |= THISMOD|FSDIRMOD;
505                                         } else if (ask(0, "Truncate"))
506                                                 dir->fsckflags |= DIREMPWARN;
507                                 }
508                                 if (dir->fsckflags & DIREMPWARN) {
509                                         *p = SLOT_DELETED;
510                                         mod |= THISMOD|FSDIRMOD;
511                                         continue;
512                                 } else if (dir->fsckflags & DIREMPTY)
513                                         mod |= FSERROR;
514                                 empty = NULL;
515                         }
516
517                         if (p[11] == ATTR_WIN95) {
518                                 if (*p & LRFIRST) {
519                                         if (shortSum != -1) {
520                                                 if (!invlfn) {
521                                                         invlfn = vallfn;
522                                                         invcl = valcl;
523                                                 }
524                                         }
525                                         memset(longName, 0, sizeof longName);
526                                         shortSum = p[13];
527                                         vallfn = p;
528                                         valcl = cl;
529                                 } else if (shortSum != p[13]
530                                            || lidx != (*p & LRNOMASK)) {
531                                         if (!invlfn) {
532                                                 invlfn = vallfn;
533                                                 invcl = valcl;
534                                         }
535                                         if (!invlfn) {
536                                                 invlfn = p;
537                                                 invcl = cl;
538                                         }
539                                         vallfn = NULL;
540                                 }
541                                 lidx = *p & LRNOMASK;
542                                 t = longName + --lidx * 13;
543                                 for (k = 1; k < 11 && t < longName + sizeof(longName); k += 2) {
544                                         if (!p[k] && !p[k + 1])
545                                                 break;
546                                         *t++ = p[k];
547                                         /*
548                                          * Warn about those unusable chars in msdosfs here?     XXX
549                                          */
550                                         if (p[k + 1])
551                                                 t[-1] = '?';
552                                 }
553                                 if (k >= 11)
554                                         for (k = 14; k < 26 && t < longName + sizeof(longName); k += 2) {
555                                                 if (!p[k] && !p[k + 1])
556                                                         break;
557                                                 *t++ = p[k];
558                                                 if (p[k + 1])
559                                                         t[-1] = '?';
560                                         }
561                                 if (k >= 26)
562                                         for (k = 28; k < 32 && t < longName + sizeof(longName); k += 2) {
563                                                 if (!p[k] && !p[k + 1])
564                                                         break;
565                                                 *t++ = p[k];
566                                                 if (p[k + 1])
567                                                         t[-1] = '?';
568                                         }
569                                 if (t >= longName + sizeof(longName)) {
570                                         pwarn("long filename too long\n");
571                                         if (!invlfn) {
572                                                 invlfn = vallfn;
573                                                 invcl = valcl;
574                                         }
575                                         vallfn = NULL;
576                                 }
577                                 if (p[26] | (p[27] << 8)) {
578                                         pwarn("long filename record cluster start != 0\n");
579                                         if (!invlfn) {
580                                                 invlfn = vallfn;
581                                                 invcl = cl;
582                                         }
583                                         vallfn = NULL;
584                                 }
585                                 continue;       /* long records don't carry further
586                                                  * information */
587                         }
588
589                         /*
590                          * This is a standard msdosfs directory entry.
591                          */
592                         memset(&dirent, 0, sizeof dirent);
593
594                         /*
595                          * it's a short name record, but we need to know
596                          * more, so get the flags first.
597                          */
598                         dirent.flags = p[11];
599
600                         /*
601                          * Translate from 850 to ISO here               XXX
602                          */
603                         for (j = 0; j < 8; j++)
604                                 dirent.name[j] = p[j];
605                         dirent.name[8] = '\0';
606                         for (k = 7; k >= 0 && dirent.name[k] == ' '; k--)
607                                 dirent.name[k] = '\0';
608                         if (dirent.name[k] != '\0')
609                                 k++;
610                         if (dirent.name[0] == SLOT_E5)
611                                 dirent.name[0] = 0xe5;
612
613                         if (dirent.flags & ATTR_VOLUME) {
614                                 if (vallfn || invlfn) {
615                                         mod |= removede(f, boot, fat,
616                                                         invlfn ? invlfn : vallfn, p,
617                                                         invlfn ? invcl : valcl, -1, 0,
618                                                         fullpath(dir), 2);
619                                         vallfn = NULL;
620                                         invlfn = NULL;
621                                 }
622                                 continue;
623                         }
624
625                         if (p[8] != ' ')
626                                 dirent.name[k++] = '.';
627                         for (j = 0; j < 3; j++)
628                                 dirent.name[k++] = p[j+8];
629                         dirent.name[k] = '\0';
630                         for (k--; k >= 0 && dirent.name[k] == ' '; k--)
631                                 dirent.name[k] = '\0';
632
633                         if (vallfn && shortSum != calcShortSum(p)) {
634                                 if (!invlfn) {
635                                         invlfn = vallfn;
636                                         invcl = valcl;
637                                 }
638                                 vallfn = NULL;
639                         }
640                         dirent.head = p[26] | (p[27] << 8);
641                         if (boot->ClustMask == CLUST32_MASK)
642                                 dirent.head |= (p[20] << 16) | (p[21] << 24);
643                         dirent.size = p[28] | (p[29] << 8) | (p[30] << 16) | (p[31] << 24);
644                         if (vallfn) {
645                                 strcpy(dirent.lname, longName);
646                                 longName[0] = '\0';
647                                 shortSum = -1;
648                         }
649
650                         dirent.parent = dir;
651                         dirent.next = dir->child;
652
653                         if (invlfn) {
654                                 mod |= k = removede(f, boot, fat,
655                                                     invlfn, vallfn ? vallfn : p,
656                                                     invcl, vallfn ? valcl : cl, cl,
657                                                     fullpath(&dirent), 0);
658                                 if (mod & FSFATAL)
659                                         return FSFATAL;
660                                 if (vallfn
661                                     ? (valcl == cl && vallfn != buffer)
662                                     : p != buffer)
663                                         if (k & FSDIRMOD)
664                                                 mod |= THISMOD;
665                         }
666
667                         vallfn = NULL; /* not used any longer */
668                         invlfn = NULL;
669
670                         if (dirent.size == 0 && !(dirent.flags & ATTR_DIRECTORY)) {
671                                 if (dirent.head != 0) {
672                                         pwarn("%s has clusters, but size 0\n",
673                                               fullpath(&dirent));
674                                         if (ask(1, "Drop allocated clusters")) {
675                                                 p[26] = p[27] = 0;
676                                                 if (boot->ClustMask == CLUST32_MASK)
677                                                         p[20] = p[21] = 0;
678                                                 clearchain(boot, fat, dirent.head);
679                                                 dirent.head = 0;
680                                                 mod |= THISMOD|FSDIRMOD|FSFATMOD;
681                                         } else
682                                                 mod |= FSERROR;
683                                 }
684                         } else if (dirent.head == 0
685                                    && !strcmp(dirent.name, "..")
686                                    && dir->parent                       /* XXX */
687                                    && !dir->parent->parent) {
688                                 /*
689                                  *  Do nothing, the parent is the root
690                                  */
691                         } else if (dirent.head < CLUST_FIRST
692                                    || dirent.head >= boot->NumClusters
693                                    || fat[dirent.head].next == CLUST_FREE
694                                    || (fat[dirent.head].next >= CLUST_RSRVD
695                                        && fat[dirent.head].next < CLUST_EOFS)
696                                    || fat[dirent.head].head != dirent.head) {
697                                 if (dirent.head == 0)
698                                         pwarn("%s has no clusters\n",
699                                               fullpath(&dirent));
700                                 else if (dirent.head < CLUST_FIRST
701                                          || dirent.head >= boot->NumClusters)
702                                         pwarn("%s starts with cluster out of range(%u)\n",
703                                               fullpath(&dirent),
704                                               dirent.head);
705                                 else if (fat[dirent.head].next == CLUST_FREE)
706                                         pwarn("%s starts with free cluster\n",
707                                               fullpath(&dirent));
708                                 else if (fat[dirent.head].next >= CLUST_RSRVD)
709                                         pwarn("%s starts with cluster marked %s\n",
710                                               fullpath(&dirent),
711                                               rsrvdcltype(fat[dirent.head].next));
712                                 else
713                                         pwarn("%s doesn't start a cluster chain\n",
714                                               fullpath(&dirent));
715                                 if (dirent.flags & ATTR_DIRECTORY) {
716                                         if (ask(0, "Remove")) {
717                                                 *p = SLOT_DELETED;
718                                                 mod |= THISMOD|FSDIRMOD;
719                                         } else
720                                                 mod |= FSERROR;
721                                         continue;
722                                 } else {
723                                         if (ask(1, "Truncate")) {
724                                                 p[28] = p[29] = p[30] = p[31] = 0;
725                                                 p[26] = p[27] = 0;
726                                                 if (boot->ClustMask == CLUST32_MASK)
727                                                         p[20] = p[21] = 0;
728                                                 dirent.size = 0;
729                                                 mod |= THISMOD|FSDIRMOD;
730                                         } else
731                                                 mod |= FSERROR;
732                                 }
733                         }
734
735                         if (dirent.head >= CLUST_FIRST && dirent.head < boot->NumClusters)
736                                 fat[dirent.head].flags |= FAT_USED;
737
738                         if (dirent.flags & ATTR_DIRECTORY) {
739                                 /*
740                                  * gather more info for directories
741                                  */
742                                 struct dirTodoNode *n;
743
744                                 if (dirent.size) {
745                                         pwarn("Directory %s has size != 0\n",
746                                               fullpath(&dirent));
747                                         if (ask(1, "Correct")) {
748                                                 p[28] = p[29] = p[30] = p[31] = 0;
749                                                 dirent.size = 0;
750                                                 mod |= THISMOD|FSDIRMOD;
751                                         } else
752                                                 mod |= FSERROR;
753                                 }
754                                 /*
755                                  * handle `.' and `..' specially
756                                  */
757                                 if (strcmp(dirent.name, ".") == 0) {
758                                         if (dirent.head != dir->head) {
759                                                 pwarn("`.' entry in %s has incorrect start cluster\n",
760                                                       fullpath(dir));
761                                                 if (ask(1, "Correct")) {
762                                                         dirent.head = dir->head;
763                                                         p[26] = (u_char)dirent.head;
764                                                         p[27] = (u_char)(dirent.head >> 8);
765                                                         if (boot->ClustMask == CLUST32_MASK) {
766                                                                 p[20] = (u_char)(dirent.head >> 16);
767                                                                 p[21] = (u_char)(dirent.head >> 24);
768                                                         }
769                                                         mod |= THISMOD|FSDIRMOD;
770                                                 } else
771                                                         mod |= FSERROR;
772                                         }
773                                         continue;
774                                 }
775                                 if (strcmp(dirent.name, "..") == 0) {
776                                         if (dir->parent) {              /* XXX */
777                                                 if (!dir->parent->parent) {
778                                                         if (dirent.head) {
779                                                                 pwarn("`..' entry in %s has non-zero start cluster\n",
780                                                                       fullpath(dir));
781                                                                 if (ask(1, "Correct")) {
782                                                                         dirent.head = 0;
783                                                                         p[26] = p[27] = 0;
784                                                                         if (boot->ClustMask == CLUST32_MASK)
785                                                                                 p[20] = p[21] = 0;
786                                                                         mod |= THISMOD|FSDIRMOD;
787                                                                 } else
788                                                                         mod |= FSERROR;
789                                                         }
790                                                 } else if (dirent.head != dir->parent->head) {
791                                                         pwarn("`..' entry in %s has incorrect start cluster\n",
792                                                               fullpath(dir));
793                                                         if (ask(1, "Correct")) {
794                                                                 dirent.head = dir->parent->head;
795                                                                 p[26] = (u_char)dirent.head;
796                                                                 p[27] = (u_char)(dirent.head >> 8);
797                                                                 if (boot->ClustMask == CLUST32_MASK) {
798                                                                         p[20] = (u_char)(dirent.head >> 16);
799                                                                         p[21] = (u_char)(dirent.head >> 24);
800                                                                 }
801                                                                 mod |= THISMOD|FSDIRMOD;
802                                                         } else
803                                                                 mod |= FSERROR;
804                                                 }
805                                         }
806                                         continue;
807                                 }
808
809                                 /* create directory tree node */
810                                 if (!(d = newDosDirEntry())) {
811                                         perror("No space for directory");
812                                         return FSFATAL;
813                                 }
814                                 memcpy(d, &dirent, sizeof(struct dosDirEntry));
815                                 /* link it into the tree */
816                                 dir->child = d;
817
818                                 /* Enter this directory into the todo list */
819                                 if (!(n = newDirTodo())) {
820                                         perror("No space for todo list");
821                                         return FSFATAL;
822                                 }
823                                 n->next = pendingDirectories;
824                                 n->dir = d;
825                                 pendingDirectories = n;
826                         } else {
827                                 mod |= k = checksize(boot, fat, p, &dirent);
828                                 if (k & FSDIRMOD)
829                                         mod |= THISMOD;
830                         }
831                         boot->NumFiles++;
832                 }
833                 if (mod & THISMOD) {
834                         last *= 32;
835                         if (lseek(f, off, SEEK_SET) != off
836                             || write(f, buffer, last) != last) {
837                                 perror("Unable to write directory");
838                                 return FSFATAL;
839                         }
840                         mod &= ~THISMOD;
841                 }
842         } while ((cl = fat[cl].next) >= CLUST_FIRST && cl < boot->NumClusters);
843         if (invlfn || vallfn)
844                 mod |= removede(f, boot, fat,
845                                 invlfn ? invlfn : vallfn, p,
846                                 invlfn ? invcl : valcl, -1, 0,
847                                 fullpath(dir), 1);
848         return mod & ~THISMOD;
849 }
850
851 int
852 handleDirTree(int dosfs, struct bootblock *boot, struct fatEntry *fat)
853 {
854         int mod;
855
856         mod = readDosDirSection(dosfs, boot, fat, rootDir);
857         if (mod & FSFATAL)
858                 return FSFATAL;
859
860         /*
861          * process the directory todo list
862          */
863         while (pendingDirectories) {
864                 struct dosDirEntry *dir = pendingDirectories->dir;
865                 struct dirTodoNode *n = pendingDirectories->next;
866
867                 /*
868                  * remove TODO entry now, the list might change during
869                  * directory reads
870                  */
871                 freeDirTodo(pendingDirectories);
872                 pendingDirectories = n;
873
874                 /*
875                  * handle subdirectory
876                  */
877                 mod |= readDosDirSection(dosfs, boot, fat, dir);
878                 if (mod & FSFATAL)
879                         return FSFATAL;
880         }
881
882         return mod;
883 }
884
885 /*
886  * Try to reconnect a FAT chain into dir
887  */
888 static u_char *lfbuf;
889 static cl_t lfcl;
890 static off_t lfoff;
891
892 int
893 reconnect(int dosfs, struct bootblock *boot, struct fatEntry *fat, cl_t head)
894 {
895         struct dosDirEntry d;
896         u_char *p;
897
898         if (!ask(1, "Reconnect"))
899                 return FSERROR;
900
901         if (!lostDir) {
902                 for (lostDir = rootDir->child; lostDir; lostDir = lostDir->next) {
903                         if (!strcmp(lostDir->name, LOSTDIR))
904                                 break;
905                 }
906                 if (!lostDir) {         /* Create LOSTDIR?              XXX */
907                         pwarn("No %s directory\n", LOSTDIR);
908                         return FSERROR;
909                 }
910         }
911         if (!lfbuf) {
912                 lfbuf = malloc(boot->ClusterSize);
913                 if (!lfbuf) {
914                         perror("No space for buffer");
915                         return FSFATAL;
916                 }
917                 p = NULL;
918         } else
919                 p = lfbuf;
920         while (1) {
921                 if (p)
922                         for (; p < lfbuf + boot->ClusterSize; p += 32)
923                                 if (*p == SLOT_EMPTY
924                                     || *p == SLOT_DELETED)
925                                         break;
926                 if (p && p < lfbuf + boot->ClusterSize)
927                         break;
928                 lfcl = p ? fat[lfcl].next : lostDir->head;
929                 if (lfcl < CLUST_FIRST || lfcl >= boot->NumClusters) {
930                         /* Extend LOSTDIR?                              XXX */
931                         pwarn("No space in %s\n", LOSTDIR);
932                         return FSERROR;
933                 }
934                 lfoff = lfcl * boot->ClusterSize
935                     + boot->ClusterOffset * boot->BytesPerSec;
936                 if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
937                     || read(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
938                         perror("could not read LOST.DIR");
939                         return FSFATAL;
940                 }
941                 p = lfbuf;
942         }
943
944         boot->NumFiles++;
945         /* Ensure uniqueness of entry here!                             XXX */
946         memset(&d, 0, sizeof d);
947         snprintf(d.name, sizeof(d.name), "%u", head);
948         d.flags = 0;
949         d.head = head;
950         d.size = fat[head].length * boot->ClusterSize;
951
952         memset(p, 0, 32);
953         memset(p, ' ', 11);
954         memcpy(p, d.name, strlen(d.name));
955         p[26] = (u_char)d.head;
956         p[27] = (u_char)(d.head >> 8);
957         if (boot->ClustMask == CLUST32_MASK) {
958                 p[20] = (u_char)(d.head >> 16);
959                 p[21] = (u_char)(d.head >> 24);
960         }
961         p[28] = (u_char)d.size;
962         p[29] = (u_char)(d.size >> 8);
963         p[30] = (u_char)(d.size >> 16);
964         p[31] = (u_char)(d.size >> 24);
965         fat[head].flags |= FAT_USED;
966         if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
967             || write(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
968                 perror("could not write LOST.DIR");
969                 return FSFATAL;
970         }
971         return FSDIRMOD;
972 }
973
974 void
975 finishlf(void)
976 {
977         if (lfbuf)
978                 free(lfbuf);
979         lfbuf = NULL;
980 }