dir.c revision 1.18 1 /* $NetBSD: dir.c,v 1.18 2006/03/20 01:25:14 christos 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.18 2006/03/20 01:25:14 christos 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 if (endcl == curcl)
373 for (; start < end; start += 32)
374 *start = SLOT_DELETED;
375 return FSDIRMOD;
376 }
377 return FSERROR;
378 }
379
380 /*
381 * Check an in-memory file entry
382 */
383 static int
384 checksize(struct bootblock *boot, struct fatEntry *fat, u_char *p,
385 struct dosDirEntry *dir)
386 {
387 /*
388 * Check size on ordinary files
389 */
390 int32_t physicalSize;
391
392 if (dir->head == CLUST_FREE)
393 physicalSize = 0;
394 else {
395 if (dir->head < CLUST_FIRST || dir->head >= boot->NumClusters)
396 return FSERROR;
397 physicalSize = fat[dir->head].length * boot->ClusterSize;
398 }
399 if (physicalSize < dir->size) {
400 pwarn("size of %s is %u, should at most be %u\n",
401 fullpath(dir), dir->size, physicalSize);
402 if (ask(1, "Truncate")) {
403 dir->size = physicalSize;
404 p[28] = (u_char)physicalSize;
405 p[29] = (u_char)(physicalSize >> 8);
406 p[30] = (u_char)(physicalSize >> 16);
407 p[31] = (u_char)(physicalSize >> 24);
408 return FSDIRMOD;
409 } else
410 return FSERROR;
411 } else if (physicalSize - dir->size >= boot->ClusterSize) {
412 pwarn("%s has too many clusters allocated\n",
413 fullpath(dir));
414 if (ask(1, "Drop superfluous clusters")) {
415 cl_t cl;
416 u_int32_t sz = 0;
417
418 for (cl = dir->head; (sz += boot->ClusterSize) < dir->size;)
419 cl = fat[cl].next;
420 clearchain(boot, fat, fat[cl].next);
421 fat[cl].next = CLUST_EOF;
422 return FSFATMOD;
423 } else
424 return FSERROR;
425 }
426 return FSOK;
427 }
428
429 /*
430 * Read a directory and
431 * - resolve long name records
432 * - enter file and directory records into the parent's list
433 * - push directories onto the todo-stack
434 */
435 static int
436 readDosDirSection(int f, struct bootblock *boot, struct fatEntry *fat,
437 struct dosDirEntry *dir)
438 {
439 struct dosDirEntry dirent, *d;
440 u_char *p, *vallfn, *invlfn, *empty;
441 off_t off;
442 int i, j, k, last;
443 cl_t cl, valcl = ~0, invcl = ~0, empcl = ~0;
444 char *t;
445 u_int lidx = 0;
446 int shortSum;
447 int mod = FSOK;
448 #define THISMOD 0x8000 /* Only used within this routine */
449
450 cl = dir->head;
451 if (dir->parent && (cl < CLUST_FIRST || cl >= boot->NumClusters)) {
452 /*
453 * Already handled somewhere else.
454 */
455 return FSOK;
456 }
457 shortSum = -1;
458 vallfn = invlfn = empty = NULL;
459 do {
460 if (!(boot->flags & FAT32) && !dir->parent) {
461 last = boot->RootDirEnts * 32;
462 off = boot->ResSectors + boot->FATs * boot->FATsecs;
463 } else {
464 last = boot->SecPerClust * boot->BytesPerSec;
465 off = cl * boot->SecPerClust + boot->ClusterOffset;
466 }
467
468 off *= boot->BytesPerSec;
469 if (lseek(f, off, SEEK_SET) != off
470 || read(f, buffer, last) != last) {
471 perror("Unable to read directory");
472 return FSFATAL;
473 }
474 last /= 32;
475 /*
476 * Check `.' and `..' entries here? XXX
477 */
478 for (p = buffer, i = 0; i < last; i++, p += 32) {
479 if (dir->fsckflags & DIREMPWARN) {
480 *p = SLOT_EMPTY;
481 continue;
482 }
483
484 if (*p == SLOT_EMPTY || *p == SLOT_DELETED) {
485 if (*p == SLOT_EMPTY) {
486 dir->fsckflags |= DIREMPTY;
487 empty = p;
488 empcl = cl;
489 }
490 continue;
491 }
492
493 if (dir->fsckflags & DIREMPTY) {
494 if (!(dir->fsckflags & DIREMPWARN)) {
495 pwarn("%s has entries after end of directory\n",
496 fullpath(dir));
497 if (ask(1, "Extend")) {
498 u_char *q;
499
500 dir->fsckflags &= ~DIREMPTY;
501 if (delete(f, boot, fat,
502 empcl, empty - buffer,
503 cl, p - buffer, 1) == FSFATAL)
504 return FSFATAL;
505 q = empcl == cl ? empty : buffer;
506 assert(q != NULL);
507 for (; q < p; q += 32)
508 *q = SLOT_DELETED;
509 mod |= THISMOD|FSDIRMOD;
510 } else if (ask(0, "Truncate"))
511 dir->fsckflags |= DIREMPWARN;
512 }
513 if (dir->fsckflags & DIREMPWARN) {
514 *p = SLOT_DELETED;
515 mod |= THISMOD|FSDIRMOD;
516 continue;
517 } else if (dir->fsckflags & DIREMPTY)
518 mod |= FSERROR;
519 empty = NULL;
520 }
521
522 if (p[11] == ATTR_WIN95) {
523 if (*p & LRFIRST) {
524 if (shortSum != -1) {
525 if (!invlfn) {
526 invlfn = vallfn;
527 invcl = valcl;
528 }
529 }
530 memset(longName, 0, sizeof longName);
531 shortSum = p[13];
532 vallfn = p;
533 valcl = cl;
534 } else if (shortSum != p[13]
535 || lidx != (*p & LRNOMASK)) {
536 if (!invlfn) {
537 invlfn = vallfn;
538 invcl = valcl;
539 }
540 if (!invlfn) {
541 invlfn = p;
542 invcl = cl;
543 }
544 vallfn = NULL;
545 }
546 lidx = *p & LRNOMASK;
547 t = longName + --lidx * 13;
548 for (k = 1; k < 11 && t < longName + sizeof(longName); k += 2) {
549 if (!p[k] && !p[k + 1])
550 break;
551 *t++ = p[k];
552 /*
553 * Warn about those unusable chars in msdosfs here? XXX
554 */
555 if (p[k + 1])
556 t[-1] = '?';
557 }
558 if (k >= 11)
559 for (k = 14; k < 26 && t < longName + sizeof(longName); k += 2) {
560 if (!p[k] && !p[k + 1])
561 break;
562 *t++ = p[k];
563 if (p[k + 1])
564 t[-1] = '?';
565 }
566 if (k >= 26)
567 for (k = 28; k < 32 && t < longName + sizeof(longName); k += 2) {
568 if (!p[k] && !p[k + 1])
569 break;
570 *t++ = p[k];
571 if (p[k + 1])
572 t[-1] = '?';
573 }
574 if (t >= longName + sizeof(longName)) {
575 pwarn("long filename too long\n");
576 if (!invlfn) {
577 invlfn = vallfn;
578 invcl = valcl;
579 }
580 vallfn = NULL;
581 }
582 if (p[26] | (p[27] << 8)) {
583 pwarn("long filename record cluster start != 0\n");
584 if (!invlfn) {
585 invlfn = vallfn;
586 invcl = cl;
587 }
588 vallfn = NULL;
589 }
590 continue; /* long records don't carry further
591 * information */
592 }
593
594 /*
595 * This is a standard msdosfs directory entry.
596 */
597 memset(&dirent, 0, sizeof dirent);
598
599 /*
600 * it's a short name record, but we need to know
601 * more, so get the flags first.
602 */
603 dirent.flags = p[11];
604
605 /*
606 * Translate from 850 to ISO here XXX
607 */
608 for (j = 0; j < 8; j++)
609 dirent.name[j] = p[j];
610 dirent.name[8] = '\0';
611 for (k = 7; k >= 0 && dirent.name[k] == ' '; k--)
612 dirent.name[k] = '\0';
613 if (dirent.name[k] != '\0')
614 k++;
615 if (dirent.name[0] == SLOT_E5)
616 dirent.name[0] = 0xe5;
617
618 if (dirent.flags & ATTR_VOLUME) {
619 if (vallfn || invlfn) {
620 mod |= removede(f, boot, fat,
621 invlfn ? invlfn : vallfn, p,
622 invlfn ? invcl : valcl, -1, 0,
623 fullpath(dir), 2);
624 vallfn = NULL;
625 invlfn = NULL;
626 }
627 continue;
628 }
629
630 if (p[8] != ' ')
631 dirent.name[k++] = '.';
632 for (j = 0; j < 3; j++)
633 dirent.name[k++] = p[j+8];
634 dirent.name[k] = '\0';
635 for (k--; k >= 0 && dirent.name[k] == ' '; k--)
636 dirent.name[k] = '\0';
637
638 if (vallfn && shortSum != calcShortSum(p)) {
639 if (!invlfn) {
640 invlfn = vallfn;
641 invcl = valcl;
642 }
643 vallfn = NULL;
644 }
645 dirent.head = p[26] | (p[27] << 8);
646 if (boot->ClustMask == CLUST32_MASK)
647 dirent.head |= (p[20] << 16) | (p[21] << 24);
648 dirent.size = p[28] | (p[29] << 8) | (p[30] << 16) | (p[31] << 24);
649 if (vallfn) {
650 strlcpy(dirent.lname, longName,
651 sizeof(dirent.lname));
652 longName[0] = '\0';
653 shortSum = -1;
654 }
655
656 dirent.parent = dir;
657 dirent.next = dir->child;
658
659 if (invlfn) {
660 mod |= k = removede(f, boot, fat,
661 invlfn, vallfn ? vallfn : p,
662 invcl, vallfn ? valcl : cl, cl,
663 fullpath(&dirent), 0);
664 if (mod & FSFATAL)
665 return FSFATAL;
666 if (vallfn
667 ? (valcl == cl && vallfn != buffer)
668 : p != buffer)
669 if (k & FSDIRMOD)
670 mod |= THISMOD;
671 }
672
673 vallfn = NULL; /* not used any longer */
674 invlfn = NULL;
675
676 if (dirent.size == 0 && !(dirent.flags & ATTR_DIRECTORY)) {
677 if (dirent.head != 0) {
678 pwarn("%s has clusters, but size 0\n",
679 fullpath(&dirent));
680 if (ask(1, "Drop allocated clusters")) {
681 p[26] = p[27] = 0;
682 if (boot->ClustMask == CLUST32_MASK)
683 p[20] = p[21] = 0;
684 clearchain(boot, fat, dirent.head);
685 dirent.head = 0;
686 mod |= THISMOD|FSDIRMOD|FSFATMOD;
687 } else
688 mod |= FSERROR;
689 }
690 } else if (dirent.head == 0
691 && !strcmp(dirent.name, "..")
692 && dir->parent /* XXX */
693 && !dir->parent->parent) {
694 /*
695 * Do nothing, the parent is the root
696 */
697 } else if (dirent.head < CLUST_FIRST
698 || dirent.head >= boot->NumClusters
699 || fat[dirent.head].next == CLUST_FREE
700 || (fat[dirent.head].next >= CLUST_RSRVD
701 && fat[dirent.head].next < CLUST_EOFS)
702 || fat[dirent.head].head != dirent.head) {
703 if (dirent.head == 0)
704 pwarn("%s has no clusters\n",
705 fullpath(&dirent));
706 else if (dirent.head < CLUST_FIRST
707 || dirent.head >= boot->NumClusters)
708 pwarn("%s starts with cluster out of range(%u)\n",
709 fullpath(&dirent),
710 dirent.head);
711 else if (fat[dirent.head].next == CLUST_FREE)
712 pwarn("%s starts with free cluster\n",
713 fullpath(&dirent));
714 else if (fat[dirent.head].next >= CLUST_RSRVD)
715 pwarn("%s starts with cluster marked %s\n",
716 fullpath(&dirent),
717 rsrvdcltype(fat[dirent.head].next));
718 else
719 pwarn("%s doesn't start a cluster chain\n",
720 fullpath(&dirent));
721 if (dirent.flags & ATTR_DIRECTORY) {
722 if (ask(0, "Remove")) {
723 *p = SLOT_DELETED;
724 mod |= THISMOD|FSDIRMOD;
725 } else
726 mod |= FSERROR;
727 continue;
728 } else {
729 if (ask(1, "Truncate")) {
730 p[28] = p[29] = p[30] = p[31] = 0;
731 p[26] = p[27] = 0;
732 if (boot->ClustMask == CLUST32_MASK)
733 p[20] = p[21] = 0;
734 dirent.size = 0;
735 mod |= THISMOD|FSDIRMOD;
736 } else
737 mod |= FSERROR;
738 }
739 }
740
741 if (dirent.head >= CLUST_FIRST && dirent.head < boot->NumClusters)
742 fat[dirent.head].flags |= FAT_USED;
743
744 if (dirent.flags & ATTR_DIRECTORY) {
745 /*
746 * gather more info for directories
747 */
748 struct dirTodoNode *n;
749
750 if (dirent.size) {
751 pwarn("Directory %s has size != 0\n",
752 fullpath(&dirent));
753 if (ask(1, "Correct")) {
754 p[28] = p[29] = p[30] = p[31] = 0;
755 dirent.size = 0;
756 mod |= THISMOD|FSDIRMOD;
757 } else
758 mod |= FSERROR;
759 }
760 /*
761 * handle `.' and `..' specially
762 */
763 if (strcmp(dirent.name, ".") == 0) {
764 if (dirent.head != dir->head) {
765 pwarn("`.' entry in %s has incorrect start cluster\n",
766 fullpath(dir));
767 if (ask(1, "Correct")) {
768 dirent.head = dir->head;
769 p[26] = (u_char)dirent.head;
770 p[27] = (u_char)(dirent.head >> 8);
771 if (boot->ClustMask == CLUST32_MASK) {
772 p[20] = (u_char)(dirent.head >> 16);
773 p[21] = (u_char)(dirent.head >> 24);
774 }
775 mod |= THISMOD|FSDIRMOD;
776 } else
777 mod |= FSERROR;
778 }
779 continue;
780 }
781 if (strcmp(dirent.name, "..") == 0) {
782 if (dir->parent) { /* XXX */
783 if (!dir->parent->parent) {
784 if (dirent.head) {
785 pwarn("`..' entry in %s has non-zero start cluster\n",
786 fullpath(dir));
787 if (ask(1, "Correct")) {
788 dirent.head = 0;
789 p[26] = p[27] = 0;
790 if (boot->ClustMask == CLUST32_MASK)
791 p[20] = p[21] = 0;
792 mod |= THISMOD|FSDIRMOD;
793 } else
794 mod |= FSERROR;
795 }
796 } else if (dirent.head != dir->parent->head) {
797 pwarn("`..' entry in %s has incorrect start cluster\n",
798 fullpath(dir));
799 if (ask(1, "Correct")) {
800 dirent.head = dir->parent->head;
801 p[26] = (u_char)dirent.head;
802 p[27] = (u_char)(dirent.head >> 8);
803 if (boot->ClustMask == CLUST32_MASK) {
804 p[20] = (u_char)(dirent.head >> 16);
805 p[21] = (u_char)(dirent.head >> 24);
806 }
807 mod |= THISMOD|FSDIRMOD;
808 } else
809 mod |= FSERROR;
810 }
811 }
812 continue;
813 }
814
815 /* create directory tree node */
816 if (!(d = newDosDirEntry())) {
817 perror("No space for directory");
818 return FSFATAL;
819 }
820 memcpy(d, &dirent, sizeof(struct dosDirEntry));
821 /* link it into the tree */
822 dir->child = d;
823
824 /* Enter this directory into the todo list */
825 if (!(n = newDirTodo())) {
826 perror("No space for todo list");
827 return FSFATAL;
828 }
829 n->next = pendingDirectories;
830 n->dir = d;
831 pendingDirectories = n;
832 } else {
833 mod |= k = checksize(boot, fat, p, &dirent);
834 if (k & FSDIRMOD)
835 mod |= THISMOD;
836 }
837 boot->NumFiles++;
838 }
839 if (mod & THISMOD) {
840 last *= 32;
841 if (lseek(f, off, SEEK_SET) != off
842 || write(f, buffer, last) != last) {
843 perror("Unable to write directory");
844 return FSFATAL;
845 }
846 mod &= ~THISMOD;
847 }
848 } while ((cl = fat[cl].next) >= CLUST_FIRST && cl < boot->NumClusters);
849 if (invlfn || vallfn)
850 mod |= removede(f, boot, fat,
851 invlfn ? invlfn : vallfn, p,
852 invlfn ? invcl : valcl, -1, 0,
853 fullpath(dir), 1);
854 return mod & ~THISMOD;
855 }
856
857 int
858 handleDirTree(int dosfs, struct bootblock *boot, struct fatEntry *fat)
859 {
860 int mod;
861
862 mod = readDosDirSection(dosfs, boot, fat, rootDir);
863 if (mod & FSFATAL)
864 return FSFATAL;
865
866 /*
867 * process the directory todo list
868 */
869 while (pendingDirectories) {
870 struct dosDirEntry *dir = pendingDirectories->dir;
871 struct dirTodoNode *n = pendingDirectories->next;
872
873 /*
874 * remove TODO entry now, the list might change during
875 * directory reads
876 */
877 freeDirTodo(pendingDirectories);
878 pendingDirectories = n;
879
880 /*
881 * handle subdirectory
882 */
883 mod |= readDosDirSection(dosfs, boot, fat, dir);
884 if (mod & FSFATAL)
885 return FSFATAL;
886 }
887
888 return mod;
889 }
890
891 /*
892 * Try to reconnect a FAT chain into dir
893 */
894 static u_char *lfbuf;
895 static cl_t lfcl;
896 static off_t lfoff;
897
898 int
899 reconnect(int dosfs, struct bootblock *boot, struct fatEntry *fat, cl_t head)
900 {
901 struct dosDirEntry d;
902 u_char *p;
903
904 if (!ask(1, "Reconnect"))
905 return FSERROR;
906
907 if (!lostDir) {
908 for (lostDir = rootDir->child; lostDir; lostDir = lostDir->next) {
909 if (!strcmp(lostDir->name, LOSTDIR))
910 break;
911 }
912 if (!lostDir) { /* Create LOSTDIR? XXX */
913 pwarn("No %s directory\n", LOSTDIR);
914 return FSERROR;
915 }
916 }
917 if (!lfbuf) {
918 lfbuf = malloc(boot->ClusterSize);
919 if (!lfbuf) {
920 perror("No space for buffer");
921 return FSFATAL;
922 }
923 p = NULL;
924 } else
925 p = lfbuf;
926 while (1) {
927 if (p)
928 for (; p < lfbuf + boot->ClusterSize; p += 32)
929 if (*p == SLOT_EMPTY
930 || *p == SLOT_DELETED)
931 break;
932 if (p && p < lfbuf + boot->ClusterSize)
933 break;
934 lfcl = p ? fat[lfcl].next : lostDir->head;
935 if (lfcl < CLUST_FIRST || lfcl >= boot->NumClusters) {
936 /* Extend LOSTDIR? XXX */
937 pwarn("No space in %s\n", LOSTDIR);
938 return FSERROR;
939 }
940 lfoff = lfcl * boot->ClusterSize
941 + boot->ClusterOffset * boot->BytesPerSec;
942 if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
943 || read(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
944 perror("could not read LOST.DIR");
945 return FSFATAL;
946 }
947 p = lfbuf;
948 }
949
950 boot->NumFiles++;
951 /* Ensure uniqueness of entry here! XXX */
952 memset(&d, 0, sizeof d);
953 (void)snprintf(d.name, sizeof(d.name), "%u", head);
954 d.flags = 0;
955 d.head = head;
956 d.size = fat[head].length * boot->ClusterSize;
957
958 memset(p, 0, 32);
959 memset(p, ' ', 11);
960 memcpy(p, d.name, strlen(d.name));
961 p[26] = (u_char)d.head;
962 p[27] = (u_char)(d.head >> 8);
963 if (boot->ClustMask == CLUST32_MASK) {
964 p[20] = (u_char)(d.head >> 16);
965 p[21] = (u_char)(d.head >> 24);
966 }
967 p[28] = (u_char)d.size;
968 p[29] = (u_char)(d.size >> 8);
969 p[30] = (u_char)(d.size >> 16);
970 p[31] = (u_char)(d.size >> 24);
971 fat[head].flags |= FAT_USED;
972 if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
973 || write(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
974 perror("could not write LOST.DIR");
975 return FSFATAL;
976 }
977 return FSDIRMOD;
978 }
979
980 void
981 finishlf(void)
982 {
983 if (lfbuf)
984 free(lfbuf);
985 lfbuf = NULL;
986 }
987