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