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