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