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