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