Home | History | Annotate | Line # | Download | only in fsck_msdos
dir.c revision 1.10
      1 /*	$NetBSD: dir.c,v 1.10 1997/09/14 14:40:12 lukem Exp $	*/
      2 
      3 /*
      4  * Copyright (C) 1995, 1996 Wolfgang Solfrank
      5  * Copyright (c) 1995 Martin Husemann
      6  * Some structure declaration borrowed from Paul Popelka
      7  * (paulp (at) 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  * 3. All advertising materials mentioning features or use of this software
     18  *    must display the following acknowledgement:
     19  *	This product includes software developed by Martin Husemann
     20  *	and Wolfgang Solfrank.
     21  * 4. Neither the name of the University nor the names of its contributors
     22  *    may be used to endorse or promote products derived from this software
     23  *    without specific prior written permission.
     24  *
     25  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
     26  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     27  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     28  * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
     29  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     30  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     31  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     32  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     33  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     34  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     35  */
     36 
     37 
     38 #include <sys/cdefs.h>
     39 #ifndef lint
     40 __RCSID("$NetBSD: dir.c,v 1.10 1997/09/14 14:40:12 lukem Exp $");
     41 #endif /* not lint */
     42 
     43 #include <stdio.h>
     44 #include <stdlib.h>
     45 #include <string.h>
     46 #include <ctype.h>
     47 #include <stdio.h>
     48 #include <unistd.h>
     49 #include <time.h>
     50 
     51 #include <sys/param.h>
     52 
     53 #include "ext.h"
     54 #include "fsutil.h"
     55 
     56 #define	SLOT_EMPTY	0x00		/* slot has never been used */
     57 #define	SLOT_E5		0x05		/* the real value is 0xe5 */
     58 #define	SLOT_DELETED	0xe5		/* file in this slot deleted */
     59 
     60 #define	ATTR_NORMAL	0x00		/* normal file */
     61 #define	ATTR_READONLY	0x01		/* file is readonly */
     62 #define	ATTR_HIDDEN	0x02		/* file is hidden */
     63 #define	ATTR_SYSTEM	0x04		/* file is a system file */
     64 #define	ATTR_VOLUME	0x08		/* entry is a volume label */
     65 #define	ATTR_DIRECTORY	0x10		/* entry is a directory name */
     66 #define	ATTR_ARCHIVE	0x20		/* file is new or modified */
     67 
     68 #define	ATTR_WIN95	0x0f		/* long name record */
     69 
     70 /*
     71  * This is the format of the contents of the deTime field in the direntry
     72  * structure.
     73  * We don't use bitfields because we don't know how compilers for
     74  * arbitrary machines will lay them out.
     75  */
     76 #define DT_2SECONDS_MASK	0x1F	/* seconds divided by 2 */
     77 #define DT_2SECONDS_SHIFT	0
     78 #define DT_MINUTES_MASK		0x7E0	/* minutes */
     79 #define DT_MINUTES_SHIFT	5
     80 #define DT_HOURS_MASK		0xF800	/* hours */
     81 #define DT_HOURS_SHIFT		11
     82 
     83 /*
     84  * This is the format of the contents of the deDate field in the direntry
     85  * structure.
     86  */
     87 #define DD_DAY_MASK		0x1F	/* day of month */
     88 #define DD_DAY_SHIFT		0
     89 #define DD_MONTH_MASK		0x1E0	/* month */
     90 #define DD_MONTH_SHIFT		5
     91 #define DD_YEAR_MASK		0xFE00	/* year - 1980 */
     92 #define DD_YEAR_SHIFT		9
     93 
     94 
     95 /* dir.c */
     96 static struct dosDirEntry *newDosDirEntry __P((void));
     97 static void freeDosDirEntry __P((struct dosDirEntry *));
     98 static struct dirTodoNode *newDirTodo __P((void));
     99 static void freeDirTodo __P((struct dirTodoNode *));
    100 static char *fullpath __P((struct dosDirEntry *));
    101 static u_char calcShortSum __P((u_char *));
    102 static int delete __P((int, struct bootblock *, struct fatEntry *, cl_t, int,
    103     cl_t, int, int));
    104 static int removede __P((int, struct bootblock *, struct fatEntry *, u_char *,
    105     u_char *, cl_t, cl_t, cl_t, char *, int));
    106 static int checksize __P((struct bootblock *, struct fatEntry *, u_char *,
    107     struct dosDirEntry *));
    108 static int readDosDirSection __P((int, struct bootblock *, struct fatEntry *,
    109     struct dosDirEntry *));
    110 
    111 /*
    112  * Manage free dosDirEntry structures.
    113  */
    114 static struct dosDirEntry *freede;
    115 
    116 static struct dosDirEntry *
    117 newDosDirEntry()
    118 {
    119 	struct dosDirEntry *de;
    120 
    121 	if (!(de = freede)) {
    122 		if (!(de = (struct dosDirEntry *)malloc(sizeof *de)))
    123 			return 0;
    124 	} else
    125 		freede = de->next;
    126 	return de;
    127 }
    128 
    129 static void
    130 freeDosDirEntry(de)
    131 	struct dosDirEntry *de;
    132 {
    133 	de->next = freede;
    134 	freede = de;
    135 }
    136 
    137 /*
    138  * The same for dirTodoNode structures.
    139  */
    140 static struct dirTodoNode *freedt;
    141 
    142 static struct dirTodoNode *
    143 newDirTodo()
    144 {
    145 	struct dirTodoNode *dt;
    146 
    147 	if (!(dt = freedt)) {
    148 		if (!(dt = (struct dirTodoNode *)malloc(sizeof *dt)))
    149 			return 0;
    150 	} else
    151 		freedt = dt->next;
    152 	return dt;
    153 }
    154 
    155 static void
    156 freeDirTodo(dt)
    157 	struct dirTodoNode *dt;
    158 {
    159 	dt->next = freedt;
    160 	freedt = dt;
    161 }
    162 
    163 /*
    164  * The stack of unread directories
    165  */
    166 struct dirTodoNode *pendingDirectories = NULL;
    167 
    168 /*
    169  * Return the full pathname for a directory entry.
    170  */
    171 static char *
    172 fullpath(dir)
    173 	struct dosDirEntry *dir;
    174 {
    175 	static char namebuf[MAXPATHLEN + 1];
    176 	char *cp, *np;
    177 	int nl;
    178 
    179 	cp = namebuf + sizeof namebuf - 1;
    180 	*cp = '\0';
    181 	do {
    182 		np = dir->lname[0] ? dir->lname : dir->name;
    183 		nl = strlen(np);
    184 		if ((cp -= nl) <= namebuf + 1)
    185 			break;
    186 		memcpy(cp, np, nl);
    187 		*--cp = '/';
    188 	} while ((dir = dir->parent) != NULL);
    189 	if (dir)
    190 		*--cp = '?';
    191 	else
    192 		cp++;
    193 	return cp;
    194 }
    195 
    196 /*
    197  * Calculate a checksum over an 8.3 alias name
    198  */
    199 static u_char
    200 calcShortSum(p)
    201 	u_char *p;
    202 {
    203 	u_char sum = 0;
    204 	int i;
    205 
    206 	for (i = 0; i < 11; i++) {
    207 		sum = (sum << 7)|(sum >> 1);	/* rotate right */
    208 		sum += p[i];
    209 	}
    210 
    211 	return sum;
    212 }
    213 
    214 /*
    215  * Global variables temporarily used during a directory scan
    216  */
    217 static char longName[DOSLONGNAMELEN] = "";
    218 static u_char *buffer = NULL;
    219 static u_char *delbuf = NULL;
    220 
    221 struct dosDirEntry *rootDir;
    222 static struct dosDirEntry *lostDir;
    223 
    224 /*
    225  * Init internal state for a new directory scan.
    226  */
    227 int
    228 resetDosDirSection(boot)
    229 	struct bootblock *boot;
    230 {
    231 	int b1, b2;
    232 
    233 	b1 = boot->RootDirEnts * 32;
    234 	b2 = boot->SecPerClust * boot->BytesPerSec;
    235 
    236 	if (!(buffer = malloc(b1 > b2 ? b1 : b2))
    237 	    || !(delbuf = malloc(b2))
    238 	    || !(rootDir = newDosDirEntry())) {
    239 		perror("No space for directory");
    240 		return FSFATAL;
    241 	}
    242 	memset(rootDir, 0, sizeof *rootDir);
    243 	return FSOK;
    244 }
    245 
    246 /*
    247  * Cleanup after a directory scan
    248  */
    249 void
    250 finishDosDirSection()
    251 {
    252 	struct dirTodoNode *p, *np;
    253 	struct dosDirEntry *d, *nd;
    254 
    255 	for (p = pendingDirectories; p; p = np) {
    256 		np = p->next;
    257 		freeDirTodo(p);
    258 	}
    259 	pendingDirectories = 0;
    260 	for (d = rootDir; d; d = nd) {
    261 		if ((nd = d->child) != NULL) {
    262 			d->child = 0;
    263 			continue;
    264 		}
    265 		if (!(nd = d->next))
    266 			nd = d->parent;
    267 		freeDosDirEntry(d);
    268 	}
    269 	rootDir = lostDir = NULL;
    270 	free(buffer);
    271 	free(delbuf);
    272 	buffer = NULL;
    273 	delbuf = NULL;
    274 }
    275 
    276 /*
    277  * Delete directory entries between startcl, startoff and endcl, endoff.
    278  */
    279 static int
    280 delete(f, boot, fat, startcl, startoff, endcl, endoff, notlast)
    281 	int f;
    282 	struct bootblock *boot;
    283 	struct fatEntry *fat;
    284 	cl_t startcl;
    285 	int startoff;
    286 	cl_t endcl;
    287 	int endoff;
    288 	int notlast;
    289 {
    290 	u_char *s, *e;
    291 	off_t off;
    292 	int clsz = boot->SecPerClust * boot->BytesPerSec;
    293 
    294 	s = delbuf + startoff;
    295 	e = delbuf + clsz;
    296 	while (startcl >= CLUST_FIRST && startcl < boot->NumClusters) {
    297 		if (startcl == endcl) {
    298 			if (notlast)
    299 				break;
    300 			e = delbuf + endoff;
    301 		}
    302 		off = startcl * boot->SecPerClust + boot->ClusterOffset;
    303 		off *= boot->BytesPerSec;
    304 		if (lseek(f, off, SEEK_SET) != off
    305 		    || read(f, delbuf, clsz) != clsz) {
    306 			perror("Unable to read directory");
    307 			return FSFATAL;
    308 		}
    309 		while (s < e) {
    310 			*s = SLOT_DELETED;
    311 			s += 32;
    312 		}
    313 		if (lseek(f, off, SEEK_SET) != off
    314 		    || write(f, delbuf, clsz) != clsz) {
    315 			perror("Unable to write directory");
    316 			return FSFATAL;
    317 		}
    318 		if (startcl == endcl)
    319 			break;
    320 		startcl = fat[startcl].next;
    321 		s = delbuf;
    322 	}
    323 	return FSOK;
    324 }
    325 
    326 static int
    327 removede(f, boot, fat, start, end, startcl, endcl, curcl, path, type)
    328 	int f;
    329 	struct bootblock *boot;
    330 	struct fatEntry *fat;
    331 	u_char *start;
    332 	u_char *end;
    333 	cl_t startcl;
    334 	cl_t endcl;
    335 	cl_t curcl;
    336 	char *path;
    337 	int type;
    338 {
    339 	switch (type) {
    340 	case 0:
    341 		pwarn("Invalid long filename entry for %s\n", path);
    342 		break;
    343 	case 1:
    344 		pwarn("Invalid long filename entry at end of directory %s\n", path);
    345 		break;
    346 	case 2:
    347 		pwarn("Invalid long filename entry for volume label\n");
    348 		break;
    349 	}
    350 	if (ask(0, "Remove")) {
    351 		if (startcl != curcl) {
    352 			if (delete(f, boot, fat,
    353 				   startcl, start - buffer,
    354 				   endcl, end - buffer,
    355 				   endcl == curcl) == FSFATAL)
    356 				return FSFATAL;
    357 			start = buffer;
    358 		}
    359 		if (endcl == curcl)
    360 			for (; start < end; start += 32)
    361 				*start = SLOT_DELETED;
    362 		return FSDIRMOD;
    363 	}
    364 	return FSERROR;
    365 }
    366 
    367 /*
    368  * Check an in-memory file entry
    369  */
    370 static int
    371 checksize(boot, fat, p, dir)
    372 	struct bootblock *boot;
    373 	struct fatEntry *fat;
    374 	u_char *p;
    375 	struct dosDirEntry *dir;
    376 {
    377 	/*
    378 	 * Check size on ordinary files
    379 	 */
    380 	int32_t physicalSize;
    381 
    382 	if (dir->head == CLUST_FREE)
    383 		physicalSize = 0;
    384 	else {
    385 		if (dir->head < CLUST_FIRST || dir->head >= boot->NumClusters)
    386 			return FSERROR;
    387 		physicalSize = fat[dir->head].length * boot->ClusterSize;
    388 	}
    389 	if (physicalSize < dir->size) {
    390 		pwarn("size of %s is %u, should at most be %u\n",
    391 		      fullpath(dir), dir->size, physicalSize);
    392 		if (ask(1, "Truncate")) {
    393 			dir->size = physicalSize;
    394 			p[28] = (u_char)physicalSize;
    395 			p[29] = (u_char)(physicalSize >> 8);
    396 			p[30] = (u_char)(physicalSize >> 16);
    397 			p[31] = (u_char)(physicalSize >> 24);
    398 			return FSDIRMOD;
    399 		} else
    400 			return FSERROR;
    401 	} else if (physicalSize - dir->size >= boot->ClusterSize) {
    402 		pwarn("%s has too many clusters allocated\n",
    403 		      fullpath(dir));
    404 		if (ask(1, "Drop superfluous clusters")) {
    405 			cl_t cl;
    406 			u_int32_t sz = 0;
    407 
    408 			for (cl = dir->head; (sz += boot->ClusterSize) < dir->size;)
    409 				cl = fat[cl].next;
    410 			clearchain(boot, fat, fat[cl].next);
    411 			fat[cl].next = CLUST_EOF;
    412 			return FSFATMOD;
    413 		} else
    414 			return FSERROR;
    415 	}
    416 	return FSOK;
    417 }
    418 
    419 /*
    420  * Read a directory and
    421  *   - resolve long name records
    422  *   - enter file and directory records into the parent's list
    423  *   - push directories onto the todo-stack
    424  */
    425 static int
    426 readDosDirSection(f, boot, fat, dir)
    427 	int f;
    428 	struct bootblock *boot;
    429 	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 (!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 			dirent.size = p[28] | (p[29] << 8) | (p[30] << 16) | (p[31] << 24);
    639 			if (vallfn) {
    640 				strcpy(dirent.lname, longName);
    641 				longName[0] = '\0';
    642 				shortSum = -1;
    643 			}
    644 
    645 			if (invlfn) {
    646 				mod |= k = removede(f, boot, fat,
    647 						    invlfn, vallfn ? vallfn : p,
    648 						    invcl, vallfn ? valcl : cl, cl,
    649 						    fullpath(&dirent), 0);
    650 				if (mod & FSFATAL)
    651 					return FSFATAL;
    652 				if (vallfn
    653 				    ? (valcl == cl && vallfn != buffer)
    654 				    : p != buffer)
    655 					if (k & FSDIRMOD)
    656 						mod |= THISMOD;
    657 			}
    658 			vallfn = NULL; /* not used any longer */
    659 			invlfn = NULL;
    660 			dirent.parent = dir;
    661 			dirent.next = dir->child;
    662 
    663 			if (dirent.size == 0 && !(dirent.flags & ATTR_DIRECTORY)) {
    664 				if (dirent.head != 0) {
    665 					pwarn("%s has clusters, but size 0\n",
    666 					      fullpath(&dirent));
    667 					if (ask(1, "Drop allocated clusters")) {
    668 						p[26] = p[27] = 0;
    669 						clearchain(boot, fat, dirent.head);
    670 						dirent.head = 0;
    671 						mod |= THISMOD|FSDIRMOD|FSFATMOD;
    672 					} else
    673 						mod |= FSERROR;
    674 				}
    675 			} else if (dirent.head == 0
    676 				   && !strcmp(dirent.name, "..")
    677 				   && dir->parent			/* XXX */
    678 				   && !dir->parent->parent) {
    679 				/*
    680 				 *  Do nothing, the parent is the root
    681 				 */
    682 			} else if (dirent.head < CLUST_FIRST
    683 				   || dirent.head >= boot->NumClusters
    684 				   || fat[dirent.head].next == CLUST_FREE
    685 				   || (fat[dirent.head].next >= CLUST_RSRVD
    686 				       && fat[dirent.head].next < CLUST_EOFS)
    687 				   || fat[dirent.head].head != dirent.head) {
    688 				if (dirent.head == 0)
    689 					pwarn("%s has no clusters\n",
    690 					      fullpath(&dirent));
    691 				else if (dirent.head < CLUST_FIRST
    692 					 || dirent.head >= boot->NumClusters)
    693 					pwarn("%s starts with cluster out of range(%d)\n",
    694 					      fullpath(&dirent),
    695 					      dirent.head);
    696 				else if (fat[dirent.head].next == CLUST_FREE)
    697 					pwarn("%s starts with free cluster\n",
    698 					      fullpath(&dirent));
    699 				else if (fat[dirent.head].next >= CLUST_RSRVD)
    700 					pwarn("%s starts with %s cluster\n",
    701 					      fullpath(&dirent),
    702 					      rsrvdcltype(fat[dirent.head].next));
    703 				else
    704 					pwarn("%s doesn't start a cluster chain\n",
    705 					      fullpath(&dirent));
    706 				if (dirent.flags & ATTR_DIRECTORY) {
    707 					if (ask(0, "Remove")) {
    708 						*p = SLOT_DELETED;
    709 						mod |= THISMOD|FSDIRMOD;
    710 					} else
    711 						mod |= FSERROR;
    712 					continue;
    713 				} else {
    714 					if (ask(1, "Truncate")) {
    715 						p[28] = p[29] = p[30] = p[31] = 0;
    716 						dirent.size = 0;
    717 						mod |= THISMOD|FSDIRMOD;
    718 					} else
    719 						mod |= FSERROR;
    720 				}
    721 			}
    722 
    723 			if (dirent.head >= CLUST_FIRST && dirent.head < boot->NumClusters)
    724 				fat[dirent.head].flags |= FAT_USED;
    725 
    726 			if (dirent.flags & ATTR_DIRECTORY) {
    727 				/*
    728 				 * gather more info for directories
    729 				 */
    730 				struct dirTodoNode * n;
    731 
    732 				if (dirent.size) {
    733 					pwarn("Directory %s has size != 0\n",
    734 					      fullpath(&dirent));
    735 					if (ask(1, "Correct")) {
    736 						p[28] = p[29] = p[30] = p[31] = 0;
    737 						dirent.size = 0;
    738 						mod |= THISMOD|FSDIRMOD;
    739 					} else
    740 						mod |= FSERROR;
    741 				}
    742 				/*
    743 				 * handle `.' and `..' specially
    744 				 */
    745 				if (strcmp(dirent.name, ".") == 0) {
    746 					if (dirent.head != dir->head) {
    747 						pwarn("`.' entry in %s has incorrect start cluster\n",
    748 						      fullpath(dir));
    749 						if (ask(1, "Correct")) {
    750 							dirent.head = dir->head;
    751 							p[26] = (u_char)dirent.head;
    752 							p[27] = (u_char)(dirent.head >> 8);
    753 							mod |= THISMOD|FSDIRMOD;
    754 						} else
    755 							mod |= FSERROR;
    756 					}
    757 					continue;
    758 				}
    759 				if (strcmp(dirent.name, "..") == 0) {
    760 					if (dir->parent			/* XXX */
    761 					    && dirent.head != dir->parent->head) {
    762 						pwarn("`..' entry in %s has incorrect start cluster\n",
    763 						      fullpath(dir));
    764 						if (ask(1, "Correct")) {
    765 							dirent.head = dir->parent->head;
    766 							p[26] = (u_char)dirent.head;
    767 							p[27] = (u_char)(dirent.head >> 8);
    768 							mod |= THISMOD|FSDIRMOD;
    769 						} else
    770 							mod |= FSERROR;
    771 					}
    772 					continue;
    773 				}
    774 
    775 				/* create directory tree node */
    776 				if (!(d = newDosDirEntry())) {
    777 					perror("No space for directory");
    778 					return FSFATAL;
    779 				}
    780 				memcpy(d, &dirent, sizeof(struct dosDirEntry));
    781 				/* link it into the tree */
    782 				dir->child = d;
    783 
    784 				/* Enter this directory into the todo list */
    785 				if (!(n = newDirTodo())) {
    786 					perror("No space for todo list");
    787 					return FSFATAL;
    788 				}
    789 				n->next = pendingDirectories;
    790 				n->dir = d;
    791 				pendingDirectories = n;
    792 			} else {
    793 				mod |= k = checksize(boot, fat, p, &dirent);
    794 				if (k & FSDIRMOD)
    795 					mod |= THISMOD;
    796 			}
    797 			boot->NumFiles++;
    798 		}
    799 		if (mod & THISMOD) {
    800 			last *= 32;
    801 			if (lseek(f, off, SEEK_SET) != off
    802 			    || write(f, buffer, last) != last) {
    803 				perror("Unable to write directory");
    804 				return FSFATAL;
    805 			}
    806 			mod &= ~THISMOD;
    807 		}
    808 	} while ((cl = fat[cl].next) >= CLUST_FIRST && cl < boot->NumClusters);
    809 	if (invlfn || vallfn)
    810 		mod |= removede(f, boot, fat,
    811 				invlfn ? invlfn : vallfn, p,
    812 				invlfn ? invcl : valcl, -1, 0,
    813 				fullpath(dir), 1);
    814 	return mod & ~THISMOD;
    815 }
    816 
    817 int
    818 handleDirTree(dosfs, boot, fat)
    819 	int dosfs;
    820 	struct bootblock *boot;
    821 	struct fatEntry *fat;
    822 {
    823 	int mod;
    824 
    825 	mod = readDosDirSection(dosfs, boot, fat, rootDir);
    826 	if (mod & FSFATAL)
    827 		return FSFATAL;
    828 
    829 	if (mod & FSFATMOD) {
    830 		mod &= ~FSFATMOD;
    831 		mod |= writefat(dosfs, boot, fat); /* delay writing fats?	XXX */
    832 	}
    833 
    834 	if (mod & FSFATAL)
    835 		return FSFATAL;
    836 
    837 	/*
    838 	 * process the directory todo list
    839 	 */
    840 	while (pendingDirectories) {
    841 		struct dosDirEntry *dir = pendingDirectories->dir;
    842 		struct dirTodoNode *n = pendingDirectories->next;
    843 
    844 		/*
    845 		 * remove TODO entry now, the list might change during
    846 		 * directory reads
    847 		 */
    848 		freeDirTodo(pendingDirectories);
    849 		pendingDirectories = n;
    850 
    851 		/*
    852 		 * handle subdirectory
    853 		 */
    854 		mod |= readDosDirSection(dosfs, boot, fat, dir);
    855 		if (mod & FSFATAL)
    856 			return FSFATAL;
    857 		if (mod & FSFATMOD) {
    858 			mod &= ~FSFATMOD;
    859 			mod |= writefat(dosfs, boot, fat); /* delay writing fats? XXX */
    860 		}
    861 		if (mod & FSFATAL)
    862 			return FSFATAL;
    863 	}
    864 	return mod;
    865 }
    866 
    867 /*
    868  * Try to reconnect a FAT chain into dir
    869  */
    870 static u_char *lfbuf;
    871 static cl_t lfcl;
    872 static off_t lfoff;
    873 
    874 int
    875 reconnect(dosfs, boot, fat, head)
    876 	int dosfs;
    877 	struct bootblock *boot;
    878 	struct fatEntry *fat;
    879 	cl_t head;
    880 {
    881 	struct dosDirEntry d;
    882 	u_char *p;
    883 
    884 	if (!lostDir) {
    885 		for (lostDir = rootDir->child; lostDir; lostDir = lostDir->next) {
    886 			if (!strcmp(lostDir->name, LOSTDIR))
    887 				break;
    888 		}
    889 		if (!lostDir) {		/* Create LOSTDIR?		XXX */
    890 			pwarn("No %s directory\n", LOSTDIR);
    891 			return FSERROR;
    892 		}
    893 	}
    894 	if (!lfbuf) {
    895 		lfbuf = malloc(boot->ClusterSize);
    896 		if (!lfbuf) {
    897 			perror("No space for buffer");
    898 			return FSFATAL;
    899 		}
    900 		p = NULL;
    901 	} else
    902 		p = lfbuf;
    903 	while (1) {
    904 		if (p)
    905 			for (; p < lfbuf + boot->ClusterSize; p += 32)
    906 				if (*p == SLOT_EMPTY
    907 				    || *p == SLOT_DELETED)
    908 					break;
    909 		if (p && p < lfbuf + boot->ClusterSize)
    910 			break;
    911 		lfcl = p ? fat[lfcl].next : lostDir->head;
    912 		if (lfcl < CLUST_FIRST || lfcl >= boot->NumClusters) {
    913 			/* Extend LOSTDIR?				XXX */
    914 			pwarn("No space in %s\n", LOSTDIR);
    915 			return FSERROR;
    916 		}
    917 		lfoff = lfcl * boot->ClusterSize
    918 		    + boot->ClusterOffset * boot->BytesPerSec;
    919 		if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
    920 		    || read(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
    921 			perror("could not read LOST.DIR");
    922 			return FSFATAL;
    923 		}
    924 		p = lfbuf;
    925 	}
    926 
    927 	if (!ask(0, "Reconnect"))
    928 		return FSERROR;
    929 
    930 	boot->NumFiles++;
    931 	/* Ensure uniqueness of entry here!				XXX */
    932 	memset(&d, 0, sizeof d);
    933 	sprintf(d.name, "%d", head);
    934 	d.flags = 0;
    935 	d.head = head;
    936 	d.size = fat[head].length * boot->ClusterSize;
    937 
    938 	memset(p, 0, 32);
    939 	memset(p, ' ', 11);
    940 	memcpy(p, d.name, strlen(d.name));
    941 	p[26] = (u_char)d.head;
    942 	p[27] = (u_char)(d.head >> 8);
    943 	p[28] = (u_char)d.size;
    944 	p[29] = (u_char)(d.size >> 8);
    945 	p[30] = (u_char)(d.size >> 16);
    946 	p[31] = (u_char)(d.size >> 24);
    947 	fat[head].flags |= FAT_USED;
    948 	if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
    949 	    || write(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
    950 		perror("could not write LOST.DIR");
    951 		return FSFATAL;
    952 	}
    953 	return FSDIRMOD;
    954 }
    955 
    956 void
    957 finishlf()
    958 {
    959 	if (lfbuf)
    960 		free(lfbuf);
    961 	lfbuf = NULL;
    962 }
    963