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