xref: /freebsd/sbin/fsck_msdosfs/dir.c (revision 72df847a94bccee245a3316e4f848482b9ac2ac2)
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) || !fat_is_cl_head(fat, dir->head)) {
404 			pwarn("Directory entry %s of size %u referencing invalid cluster %u\n",
405 			    fullpath(dir), dir->size, dir->head);
406 			if (ask(1, "Truncate")) {
407 				p[28] = p[29] = p[30] = p[31] = 0;
408 				p[26] = p[27] = 0;
409 				if (boot->ClustMask == CLUST32_MASK)
410 					p[20] = p[21] = 0;
411 				dir->size = 0;
412 				dir->head = CLUST_FREE;
413 				return FSDIRMOD;
414 			} else {
415 				return FSERROR;
416 			}
417 		}
418 		ret = checkchain(fat, dir->head, &chainsize);
419 		/*
420 		 * Upon return, chainsize would hold the chain length
421 		 * that checkchain() was able to validate, but if the user
422 		 * refused the proposed repair, it would be unsafe to
423 		 * proceed with directory entry fix, so bail out in that
424 		 * case.
425 		 */
426 		if (ret == FSERROR) {
427 			return (FSERROR);
428 		}
429 		/*
430 		 * The maximum file size on FAT32 is 4GiB - 1, which
431 		 * will occupy a cluster chain of exactly 4GiB in
432 		 * size.  On 32-bit platforms, since size_t is 32-bit,
433 		 * it would wrap back to 0.
434 		 */
435 		physicalSize = (u_int64_t)chainsize * boot->ClusterSize;
436 	}
437 	if (physicalSize < dir->size) {
438 		pwarn("size of %s is %u, should at most be %ju\n",
439 		      fullpath(dir), dir->size, (uintmax_t)physicalSize);
440 		if (ask(1, "Truncate")) {
441 			dir->size = physicalSize;
442 			p[28] = (u_char)physicalSize;
443 			p[29] = (u_char)(physicalSize >> 8);
444 			p[30] = (u_char)(physicalSize >> 16);
445 			p[31] = (u_char)(physicalSize >> 24);
446 			return FSDIRMOD;
447 		} else
448 			return FSERROR;
449 	} else if (physicalSize - dir->size >= boot->ClusterSize) {
450 		pwarn("%s has too many clusters allocated\n",
451 		      fullpath(dir));
452 		if (ask(1, "Drop superfluous clusters")) {
453 			cl_t cl;
454 			u_int32_t sz, len;
455 
456 			for (cl = dir->head, len = sz = 0;
457 			    (sz += boot->ClusterSize) < dir->size; len++)
458 				cl = fat_get_cl_next(fat, cl);
459 			clearchain(fat, fat_get_cl_next(fat, cl));
460 			ret = fat_set_cl_next(fat, cl, CLUST_EOF);
461 			return (FSFATMOD | ret);
462 		} else
463 			return FSERROR;
464 	}
465 	return FSOK;
466 }
467 
468 static const u_char dot_name[11]    = ".          ";
469 static const u_char dotdot_name[11] = "..         ";
470 
471 /*
472  * Basic sanity check if the subdirectory have good '.' and '..' entries,
473  * and they are directory entries.  Further sanity checks are performed
474  * when we traverse into it.
475  */
476 static int
477 check_subdirectory(struct fat_descriptor *fat, struct dosDirEntry *dir)
478 {
479 	u_char *buf, *cp;
480 	off_t off;
481 	cl_t cl;
482 	int retval = FSOK;
483 	int fd;
484 	struct bootblock *boot;
485 
486 	boot = fat_get_boot(fat);
487 	fd = fat_get_fd(fat);
488 
489 	cl = dir->head;
490 	if (dir->parent && !fat_is_valid_cl(fat, cl)) {
491 		return FSERROR;
492 	}
493 
494 	if (!(boot->flags & FAT32) && !dir->parent) {
495 		off = boot->bpbResSectors + boot->bpbFATs *
496 			boot->FATsecs;
497 	} else {
498 		off = (cl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster;
499 	}
500 
501 	/*
502 	 * We only need to check the first two entries of the directory,
503 	 * which is found in the first sector of the directory entry,
504 	 * so read in only the first sector.
505 	 */
506 	buf = malloc(boot->bpbBytesPerSec);
507 	if (buf == NULL) {
508 		perr("No space for directory buffer (%u)",
509 		    boot->bpbBytesPerSec);
510 		return FSFATAL;
511 	}
512 
513 	off *= boot->bpbBytesPerSec;
514 	if (lseek(fd, off, SEEK_SET) != off ||
515 	    read(fd, buf, boot->bpbBytesPerSec) != (ssize_t)boot->bpbBytesPerSec) {
516 		perr("Unable to read directory");
517 		free(buf);
518 		return FSFATAL;
519 	}
520 
521 	/*
522 	 * Both `.' and `..' must be present and be the first two entries
523 	 * and be ATTR_DIRECTORY of a valid subdirectory.
524 	 */
525 	cp = buf;
526 	if (memcmp(cp, dot_name, sizeof(dot_name)) != 0 ||
527 	    (cp[11] & ATTR_DIRECTORY) != ATTR_DIRECTORY) {
528 		pwarn("%s: Incorrect `.' for %s.\n", __func__, dir->name);
529 		retval |= FSERROR;
530 	}
531 	cp += 32;
532 	if (memcmp(cp, dotdot_name, sizeof(dotdot_name)) != 0 ||
533 	    (cp[11] & ATTR_DIRECTORY) != ATTR_DIRECTORY) {
534 		pwarn("%s: Incorrect `..' for %s. \n", __func__, dir->name);
535 		retval |= FSERROR;
536 	}
537 
538 	free(buf);
539 	return retval;
540 }
541 
542 /*
543  * Read a directory and
544  *   - resolve long name records
545  *   - enter file and directory records into the parent's list
546  *   - push directories onto the todo-stack
547  */
548 static int
549 readDosDirSection(struct fat_descriptor *fat, struct dosDirEntry *dir)
550 {
551 	struct bootblock *boot;
552 	struct dosDirEntry dirent, *d;
553 	u_char *p, *vallfn, *invlfn, *empty;
554 	off_t off;
555 	int fd, i, j, k, iosize, entries;
556 	bool is_legacyroot;
557 	cl_t cl, valcl = ~0, invcl = ~0, empcl = ~0;
558 	char *t;
559 	u_int lidx = 0;
560 	int shortSum;
561 	int mod = FSOK;
562 	size_t dirclusters;
563 #define	THISMOD	0x8000			/* Only used within this routine */
564 
565 	boot = fat_get_boot(fat);
566 	fd = fat_get_fd(fat);
567 
568 	cl = dir->head;
569 	if (dir->parent && (!fat_is_valid_cl(fat, cl))) {
570 		/*
571 		 * Already handled somewhere else.
572 		 */
573 		return FSOK;
574 	}
575 	shortSum = -1;
576 	vallfn = invlfn = empty = NULL;
577 
578 	/*
579 	 * If we are checking the legacy root (for FAT12/FAT16),
580 	 * we will operate on the whole directory; otherwise, we
581 	 * will operate on one cluster at a time, and also take
582 	 * this opportunity to examine the chain.
583 	 *
584 	 * Derive how many entries we are going to encounter from
585 	 * the I/O size.
586 	 */
587 	is_legacyroot = (dir->parent == NULL && !(boot->flags & FAT32));
588 	if (is_legacyroot) {
589 		iosize = boot->bpbRootDirEnts * 32;
590 		entries = boot->bpbRootDirEnts;
591 	} else {
592 		iosize = boot->bpbSecPerClust * boot->bpbBytesPerSec;
593 		entries = iosize / 32;
594 		mod |= checkchain(fat, dir->head, &dirclusters);
595 	}
596 
597 	do {
598 		if (is_legacyroot) {
599 			/*
600 			 * Special case for FAT12/FAT16 root -- read
601 			 * in the whole root directory.
602 			 */
603 			off = boot->bpbResSectors + boot->bpbFATs *
604 			    boot->FATsecs;
605 		} else {
606 			/*
607 			 * Otherwise, read in a cluster of the
608 			 * directory.
609 			 */
610 			off = (cl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster;
611 		}
612 
613 		off *= boot->bpbBytesPerSec;
614 		if (lseek(fd, off, SEEK_SET) != off ||
615 		    read(fd, buffer, iosize) != iosize) {
616 			perr("Unable to read directory");
617 			return FSFATAL;
618 		}
619 
620 		for (p = buffer, i = 0; i < entries; i++, p += 32) {
621 			if (dir->fsckflags & DIREMPWARN) {
622 				*p = SLOT_EMPTY;
623 				continue;
624 			}
625 
626 			if (*p == SLOT_EMPTY || *p == SLOT_DELETED) {
627 				if (*p == SLOT_EMPTY) {
628 					dir->fsckflags |= DIREMPTY;
629 					empty = p;
630 					empcl = cl;
631 				}
632 				continue;
633 			}
634 
635 			if (dir->fsckflags & DIREMPTY) {
636 				if (!(dir->fsckflags & DIREMPWARN)) {
637 					pwarn("%s has entries after end of directory\n",
638 					      fullpath(dir));
639 					if (ask(1, "Extend")) {
640 						u_char *q;
641 
642 						dir->fsckflags &= ~DIREMPTY;
643 						if (delete(fat,
644 							   empcl, empty - buffer,
645 							   cl, p - buffer, 1) == FSFATAL)
646 							return FSFATAL;
647 						q = ((empcl == cl) ? empty : buffer);
648 						assert(q != NULL);
649 						for (; q < p; q += 32)
650 							*q = SLOT_DELETED;
651 						mod |= THISMOD|FSDIRMOD;
652 					} else if (ask(0, "Truncate"))
653 						dir->fsckflags |= DIREMPWARN;
654 				}
655 				if (dir->fsckflags & DIREMPWARN) {
656 					*p = SLOT_DELETED;
657 					mod |= THISMOD|FSDIRMOD;
658 					continue;
659 				} else if (dir->fsckflags & DIREMPTY)
660 					mod |= FSERROR;
661 				empty = NULL;
662 			}
663 
664 			if (p[11] == ATTR_WIN95) {
665 				if (*p & LRFIRST) {
666 					if (shortSum != -1) {
667 						if (!invlfn) {
668 							invlfn = vallfn;
669 							invcl = valcl;
670 						}
671 					}
672 					memset(longName, 0, sizeof longName);
673 					shortSum = p[13];
674 					vallfn = p;
675 					valcl = cl;
676 				} else if (shortSum != p[13]
677 					   || lidx != (*p & LRNOMASK)) {
678 					if (!invlfn) {
679 						invlfn = vallfn;
680 						invcl = valcl;
681 					}
682 					if (!invlfn) {
683 						invlfn = p;
684 						invcl = cl;
685 					}
686 					vallfn = NULL;
687 				}
688 				lidx = *p & LRNOMASK;
689 				if (lidx == 0) {
690 					pwarn("invalid long name\n");
691 					if (!invlfn) {
692 						invlfn = vallfn;
693 						invcl = valcl;
694 					}
695 					vallfn = NULL;
696 					continue;
697 				}
698 				t = longName + --lidx * 13;
699 				for (k = 1; k < 11 && t < longName +
700 				    sizeof(longName); k += 2) {
701 					if (!p[k] && !p[k + 1])
702 						break;
703 					*t++ = p[k];
704 					/*
705 					 * Warn about those unusable chars in msdosfs here?	XXX
706 					 */
707 					if (p[k + 1])
708 						t[-1] = '?';
709 				}
710 				if (k >= 11)
711 					for (k = 14; k < 26 && t < longName + sizeof(longName); k += 2) {
712 						if (!p[k] && !p[k + 1])
713 							break;
714 						*t++ = p[k];
715 						if (p[k + 1])
716 							t[-1] = '?';
717 					}
718 				if (k >= 26)
719 					for (k = 28; k < 32 && t < longName + sizeof(longName); k += 2) {
720 						if (!p[k] && !p[k + 1])
721 							break;
722 						*t++ = p[k];
723 						if (p[k + 1])
724 							t[-1] = '?';
725 					}
726 				if (t >= longName + sizeof(longName)) {
727 					pwarn("long filename too long\n");
728 					if (!invlfn) {
729 						invlfn = vallfn;
730 						invcl = valcl;
731 					}
732 					vallfn = NULL;
733 				}
734 				if (p[26] | (p[27] << 8)) {
735 					pwarn("long filename record cluster start != 0\n");
736 					if (!invlfn) {
737 						invlfn = vallfn;
738 						invcl = cl;
739 					}
740 					vallfn = NULL;
741 				}
742 				continue;	/* long records don't carry further
743 						 * information */
744 			}
745 
746 			/*
747 			 * This is a standard msdosfs directory entry.
748 			 */
749 			memset(&dirent, 0, sizeof dirent);
750 
751 			/*
752 			 * it's a short name record, but we need to know
753 			 * more, so get the flags first.
754 			 */
755 			dirent.flags = p[11];
756 
757 			/*
758 			 * Translate from 850 to ISO here		XXX
759 			 */
760 			for (j = 0; j < 8; j++)
761 				dirent.name[j] = p[j];
762 			dirent.name[8] = '\0';
763 			for (k = 7; k >= 0 && dirent.name[k] == ' '; k--)
764 				dirent.name[k] = '\0';
765 			if (k < 0 || dirent.name[k] != '\0')
766 				k++;
767 			if (dirent.name[0] == SLOT_E5)
768 				dirent.name[0] = 0xe5;
769 
770 			if (dirent.flags & ATTR_VOLUME) {
771 				if (vallfn || invlfn) {
772 					mod |= removede(fat,
773 							invlfn ? invlfn : vallfn, p,
774 							invlfn ? invcl : valcl, -1, 0,
775 							fullpath(dir), 2);
776 					vallfn = NULL;
777 					invlfn = NULL;
778 				}
779 				continue;
780 			}
781 
782 			if (p[8] != ' ')
783 				dirent.name[k++] = '.';
784 			for (j = 0; j < 3; j++)
785 				dirent.name[k++] = p[j+8];
786 			dirent.name[k] = '\0';
787 			for (k--; k >= 0 && dirent.name[k] == ' '; k--)
788 				dirent.name[k] = '\0';
789 
790 			if (vallfn && shortSum != calcShortSum(p)) {
791 				if (!invlfn) {
792 					invlfn = vallfn;
793 					invcl = valcl;
794 				}
795 				vallfn = NULL;
796 			}
797 			dirent.head = p[26] | (p[27] << 8);
798 			if (boot->ClustMask == CLUST32_MASK)
799 				dirent.head |= (p[20] << 16) | (p[21] << 24);
800 			dirent.size = p[28] | (p[29] << 8) | (p[30] << 16) | (p[31] << 24);
801 			if (vallfn) {
802 				strlcpy(dirent.lname, longName,
803 				    sizeof(dirent.lname));
804 				longName[0] = '\0';
805 				shortSum = -1;
806 			}
807 
808 			dirent.parent = dir;
809 			dirent.next = dir->child;
810 
811 			if (invlfn) {
812 				mod |= k = removede(fat,
813 						    invlfn, vallfn ? vallfn : p,
814 						    invcl, vallfn ? valcl : cl, cl,
815 						    fullpath(&dirent), 0);
816 				if (mod & FSFATAL)
817 					return FSFATAL;
818 				if (vallfn
819 				    ? (valcl == cl && vallfn != buffer)
820 				    : p != buffer)
821 					if (k & FSDIRMOD)
822 						mod |= THISMOD;
823 			}
824 
825 			vallfn = NULL; /* not used any longer */
826 			invlfn = NULL;
827 
828 			/*
829 			 * Check if the directory entry is sane.
830 			 *
831 			 * '.' and '..' are skipped, their sanity is
832 			 * checked somewhere else.
833 			 *
834 			 * For everything else, check if we have a new,
835 			 * valid cluster chain (beginning of a file or
836 			 * directory that was never previously claimed
837 			 * by another file) when it's a non-empty file
838 			 * or a directory. The sanity of the cluster
839 			 * chain is checked at a later time when we
840 			 * traverse into the directory, or examine the
841 			 * file's directory entry.
842 			 *
843 			 * The only possible fix is to delete the entry
844 			 * if it's a directory; for file, we have to
845 			 * truncate the size to 0.
846 			 */
847 			if (!(dirent.flags & ATTR_DIRECTORY) ||
848 			    (strcmp(dirent.name, ".") != 0 &&
849 			    strcmp(dirent.name, "..") != 0)) {
850 				if ((dirent.size != 0 || (dirent.flags & ATTR_DIRECTORY)) &&
851 				    ((!fat_is_valid_cl(fat, dirent.head) ||
852 				    !fat_is_cl_head(fat, dirent.head)))) {
853 					if (!fat_is_valid_cl(fat, dirent.head)) {
854 						pwarn("%s starts with cluster out of range(%u)\n",
855 						    fullpath(&dirent),
856 						    dirent.head);
857 					} else {
858 						pwarn("%s doesn't start a new cluster chain\n",
859 						    fullpath(&dirent));
860 					}
861 
862 					if (dirent.flags & ATTR_DIRECTORY) {
863 						if (ask(0, "Remove")) {
864 							*p = SLOT_DELETED;
865 							mod |= THISMOD|FSDIRMOD;
866 						} else
867 							mod |= FSERROR;
868 						continue;
869 					} else {
870 						if (ask(1, "Truncate")) {
871 							p[28] = p[29] = p[30] = p[31] = 0;
872 							p[26] = p[27] = 0;
873 							if (boot->ClustMask == CLUST32_MASK)
874 								p[20] = p[21] = 0;
875 							dirent.size = 0;
876 							dirent.head = 0;
877 							mod |= THISMOD|FSDIRMOD;
878 						} else
879 							mod |= FSERROR;
880 					}
881 				}
882 			}
883 			if (dirent.flags & ATTR_DIRECTORY) {
884 				/*
885 				 * gather more info for directories
886 				 */
887 				struct dirTodoNode *n;
888 
889 				if (dirent.size) {
890 					pwarn("Directory %s has size != 0\n",
891 					      fullpath(&dirent));
892 					if (ask(1, "Correct")) {
893 						p[28] = p[29] = p[30] = p[31] = 0;
894 						dirent.size = 0;
895 						mod |= THISMOD|FSDIRMOD;
896 					} else
897 						mod |= FSERROR;
898 				}
899 				/*
900 				 * handle `.' and `..' specially
901 				 */
902 				if (strcmp(dirent.name, ".") == 0) {
903 					if (dirent.head != dir->head) {
904 						pwarn("`.' entry in %s has incorrect start cluster\n",
905 						      fullpath(dir));
906 						if (ask(1, "Correct")) {
907 							dirent.head = dir->head;
908 							p[26] = (u_char)dirent.head;
909 							p[27] = (u_char)(dirent.head >> 8);
910 							if (boot->ClustMask == CLUST32_MASK) {
911 								p[20] = (u_char)(dirent.head >> 16);
912 								p[21] = (u_char)(dirent.head >> 24);
913 							}
914 							mod |= THISMOD|FSDIRMOD;
915 						} else
916 							mod |= FSERROR;
917 					}
918 					continue;
919 				} else if (strcmp(dirent.name, "..") == 0) {
920 					if (dir->parent) {		/* XXX */
921 						if (!dir->parent->parent) {
922 							if (dirent.head) {
923 								pwarn("`..' entry in %s has non-zero start cluster\n",
924 								      fullpath(dir));
925 								if (ask(1, "Correct")) {
926 									dirent.head = 0;
927 									p[26] = p[27] = 0;
928 									if (boot->ClustMask == CLUST32_MASK)
929 										p[20] = p[21] = 0;
930 									mod |= THISMOD|FSDIRMOD;
931 								} else
932 									mod |= FSERROR;
933 							}
934 						} else if (dirent.head != dir->parent->head) {
935 							pwarn("`..' entry in %s has incorrect start cluster\n",
936 							      fullpath(dir));
937 							if (ask(1, "Correct")) {
938 								dirent.head = dir->parent->head;
939 								p[26] = (u_char)dirent.head;
940 								p[27] = (u_char)(dirent.head >> 8);
941 								if (boot->ClustMask == CLUST32_MASK) {
942 									p[20] = (u_char)(dirent.head >> 16);
943 									p[21] = (u_char)(dirent.head >> 24);
944 								}
945 								mod |= THISMOD|FSDIRMOD;
946 							} else
947 								mod |= FSERROR;
948 						}
949 					}
950 					continue;
951 				} else {
952 					/*
953 					 * Only one directory entry can point
954 					 * to dir->head, it's '.'.
955 					 */
956 					if (dirent.head == dir->head) {
957 						pwarn("%s entry in %s has incorrect start cluster\n",
958 								dirent.name, fullpath(dir));
959 						if (ask(1, "Remove")) {
960 							*p = SLOT_DELETED;
961 							mod |= THISMOD|FSDIRMOD;
962 						} else
963 							mod |= FSERROR;
964 						continue;
965 					} else if ((check_subdirectory(fat,
966 					    &dirent) & FSERROR) == FSERROR) {
967 						/*
968 						 * A subdirectory should have
969 						 * a dot (.) entry and a dot-dot
970 						 * (..) entry of ATTR_DIRECTORY,
971 						 * we will inspect further when
972 						 * traversing into it.
973 						 */
974 						if (ask(1, "Remove")) {
975 							*p = SLOT_DELETED;
976 							mod |= THISMOD|FSDIRMOD;
977 						} else
978 							mod |= FSERROR;
979 						continue;
980 					}
981 				}
982 
983 				/* create directory tree node */
984 				if (!(d = newDosDirEntry())) {
985 					perr("No space for directory");
986 					return FSFATAL;
987 				}
988 				memcpy(d, &dirent, sizeof(struct dosDirEntry));
989 				/* link it into the tree */
990 				dir->child = d;
991 
992 				/* Enter this directory into the todo list */
993 				if (!(n = newDirTodo())) {
994 					perr("No space for todo list");
995 					return FSFATAL;
996 				}
997 				n->next = pendingDirectories;
998 				n->dir = d;
999 				pendingDirectories = n;
1000 			} else {
1001 				mod |= k = checksize(fat, p, &dirent);
1002 				if (k & FSDIRMOD)
1003 					mod |= THISMOD;
1004 			}
1005 			boot->NumFiles++;
1006 		}
1007 
1008 		if (is_legacyroot) {
1009 			/*
1010 			 * Don't bother to write back right now because
1011 			 * we may continue to make modification to the
1012 			 * non-FAT32 root directory below.
1013 			 */
1014 			break;
1015 		} else if (mod & THISMOD) {
1016 			if (lseek(fd, off, SEEK_SET) != off
1017 			    || write(fd, buffer, iosize) != iosize) {
1018 				perr("Unable to write directory");
1019 				return FSFATAL;
1020 			}
1021 			mod &= ~THISMOD;
1022 		}
1023 	} while (fat_is_valid_cl(fat, (cl = fat_get_cl_next(fat, cl))));
1024 	if (invlfn || vallfn)
1025 		mod |= removede(fat,
1026 				invlfn ? invlfn : vallfn, p,
1027 				invlfn ? invcl : valcl, -1, 0,
1028 				fullpath(dir), 1);
1029 
1030 	/*
1031 	 * The root directory of non-FAT32 filesystems is in a special
1032 	 * area and may have been modified above removede() without
1033 	 * being written out.
1034 	 */
1035 	if ((mod & FSDIRMOD) && is_legacyroot) {
1036 		if (lseek(fd, off, SEEK_SET) != off
1037 		    || write(fd, buffer, iosize) != iosize) {
1038 			perr("Unable to write directory");
1039 			return FSFATAL;
1040 		}
1041 		mod &= ~THISMOD;
1042 	}
1043 	return mod & ~THISMOD;
1044 }
1045 
1046 int
1047 handleDirTree(struct fat_descriptor *fat)
1048 {
1049 	int mod;
1050 
1051 	mod = readDosDirSection(fat, rootDir);
1052 	if (mod & FSFATAL)
1053 		return FSFATAL;
1054 
1055 	/*
1056 	 * process the directory todo list
1057 	 */
1058 	while (pendingDirectories) {
1059 		struct dosDirEntry *dir = pendingDirectories->dir;
1060 		struct dirTodoNode *n = pendingDirectories->next;
1061 
1062 		/*
1063 		 * remove TODO entry now, the list might change during
1064 		 * directory reads
1065 		 */
1066 		freeDirTodo(pendingDirectories);
1067 		pendingDirectories = n;
1068 
1069 		/*
1070 		 * handle subdirectory
1071 		 */
1072 		mod |= readDosDirSection(fat, dir);
1073 		if (mod & FSFATAL)
1074 			return FSFATAL;
1075 	}
1076 
1077 	return mod;
1078 }
1079 
1080 /*
1081  * Try to reconnect a FAT chain into dir
1082  */
1083 static u_char *lfbuf;
1084 static cl_t lfcl;
1085 static off_t lfoff;
1086 
1087 int
1088 reconnect(struct fat_descriptor *fat, cl_t head, size_t length)
1089 {
1090 	struct bootblock *boot = fat_get_boot(fat);
1091 	struct dosDirEntry d;
1092 	int len, dosfs;
1093 	u_char *p;
1094 
1095 	dosfs = fat_get_fd(fat);
1096 
1097 	if (!ask(1, "Reconnect"))
1098 		return FSERROR;
1099 
1100 	if (!lostDir) {
1101 		for (lostDir = rootDir->child; lostDir; lostDir = lostDir->next) {
1102 			if (!strcmp(lostDir->name, LOSTDIR))
1103 				break;
1104 		}
1105 		if (!lostDir) {		/* Create LOSTDIR?		XXX */
1106 			pwarn("No %s directory\n", LOSTDIR);
1107 			return FSERROR;
1108 		}
1109 	}
1110 	if (!lfbuf) {
1111 		lfbuf = malloc(boot->ClusterSize);
1112 		if (!lfbuf) {
1113 			perr("No space for buffer");
1114 			return FSFATAL;
1115 		}
1116 		p = NULL;
1117 	} else
1118 		p = lfbuf;
1119 	while (1) {
1120 		if (p)
1121 			for (; p < lfbuf + boot->ClusterSize; p += 32)
1122 				if (*p == SLOT_EMPTY
1123 				    || *p == SLOT_DELETED)
1124 					break;
1125 		if (p && p < lfbuf + boot->ClusterSize)
1126 			break;
1127 		lfcl = p ? fat_get_cl_next(fat, lfcl) : lostDir->head;
1128 		if (lfcl < CLUST_FIRST || lfcl >= boot->NumClusters) {
1129 			/* Extend LOSTDIR?				XXX */
1130 			pwarn("No space in %s\n", LOSTDIR);
1131 			lfcl = (lostDir->head < boot->NumClusters) ? lostDir->head : 0;
1132 			return FSERROR;
1133 		}
1134 		lfoff = (lfcl - CLUST_FIRST) * boot->ClusterSize
1135 		    + boot->FirstCluster * boot->bpbBytesPerSec;
1136 
1137 		if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
1138 		    || (size_t)read(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
1139 			perr("could not read LOST.DIR");
1140 			return FSFATAL;
1141 		}
1142 		p = lfbuf;
1143 	}
1144 
1145 	boot->NumFiles++;
1146 	/* Ensure uniqueness of entry here!				XXX */
1147 	memset(&d, 0, sizeof d);
1148 	/* worst case -1 = 4294967295, 10 digits */
1149 	len = snprintf(d.name, sizeof(d.name), "%u", head);
1150 	d.flags = 0;
1151 	d.head = head;
1152 	d.size = length * boot->ClusterSize;
1153 
1154 	memcpy(p, d.name, len);
1155 	memset(p + len, ' ', 11 - len);
1156 	memset(p + 11, 0, 32 - 11);
1157 	p[26] = (u_char)d.head;
1158 	p[27] = (u_char)(d.head >> 8);
1159 	if (boot->ClustMask == CLUST32_MASK) {
1160 		p[20] = (u_char)(d.head >> 16);
1161 		p[21] = (u_char)(d.head >> 24);
1162 	}
1163 	p[28] = (u_char)d.size;
1164 	p[29] = (u_char)(d.size >> 8);
1165 	p[30] = (u_char)(d.size >> 16);
1166 	p[31] = (u_char)(d.size >> 24);
1167 	if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
1168 	    || (size_t)write(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
1169 		perr("could not write LOST.DIR");
1170 		return FSFATAL;
1171 	}
1172 	return FSDIRMOD;
1173 }
1174 
1175 void
1176 finishlf(void)
1177 {
1178 	if (lfbuf)
1179 		free(lfbuf);
1180 	lfbuf = NULL;
1181 }
1182