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