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