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