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