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