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