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