dir.c revision 1.17
1/*	$NetBSD: dir.c,v 1.17 2005/01/19 20:00:45 xtraeme Exp $	*/
2
3/*
4 * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank
5 * Copyright (c) 1995 Martin Husemann
6 * Some structure declaration borrowed from Paul Popelka
7 * (paulp@uts.amdahl.com), see /sys/msdosfs/ for reference.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 * 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.17 2005/01/19 20:00:45 xtraeme 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 */
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,
349	 int type)
350{
351	switch (type) {
352	case 0:
353		pwarn("Invalid long filename entry for %s\n", path);
354		break;
355	case 1:
356		pwarn("Invalid long filename entry at end of directory %s\n", path);
357		break;
358	case 2:
359		pwarn("Invalid long filename entry for volume label\n");
360		break;
361	}
362	if (ask(0, "Remove")) {
363		if (startcl != curcl) {
364			if (delete(f, boot, fat,
365				   startcl, start - buffer,
366				   endcl, end - buffer,
367				   endcl == curcl) == FSFATAL)
368				return FSFATAL;
369			start = buffer;
370		}
371		if (endcl == curcl)
372			for (; start < end; start += 32)
373				*start = SLOT_DELETED;
374		return FSDIRMOD;
375	}
376	return FSERROR;
377}
378
379/*
380 * Check an in-memory file entry
381 */
382static int
383checksize(struct bootblock *boot, struct fatEntry *fat, u_char *p,
384	  struct dosDirEntry *dir)
385{
386	/*
387	 * Check size on ordinary files
388	 */
389	int32_t physicalSize;
390
391	if (dir->head == CLUST_FREE)
392		physicalSize = 0;
393	else {
394		if (dir->head < CLUST_FIRST || dir->head >= boot->NumClusters)
395			return FSERROR;
396		physicalSize = fat[dir->head].length * boot->ClusterSize;
397	}
398	if (physicalSize < dir->size) {
399		pwarn("size of %s is %u, should at most be %u\n",
400		      fullpath(dir), dir->size, physicalSize);
401		if (ask(1, "Truncate")) {
402			dir->size = physicalSize;
403			p[28] = (u_char)physicalSize;
404			p[29] = (u_char)(physicalSize >> 8);
405			p[30] = (u_char)(physicalSize >> 16);
406			p[31] = (u_char)(physicalSize >> 24);
407			return FSDIRMOD;
408		} else
409			return FSERROR;
410	} else if (physicalSize - dir->size >= boot->ClusterSize) {
411		pwarn("%s has too many clusters allocated\n",
412		      fullpath(dir));
413		if (ask(1, "Drop superfluous clusters")) {
414			cl_t cl;
415			u_int32_t sz = 0;
416
417			for (cl = dir->head; (sz += boot->ClusterSize) < dir->size;)
418				cl = fat[cl].next;
419			clearchain(boot, fat, fat[cl].next);
420			fat[cl].next = CLUST_EOF;
421			return FSFATMOD;
422		} else
423			return FSERROR;
424	}
425	return FSOK;
426}
427
428/*
429 * Read a directory and
430 *   - resolve long name records
431 *   - enter file and directory records into the parent's list
432 *   - push directories onto the todo-stack
433 */
434static int
435readDosDirSection(int f, struct bootblock *boot, struct fatEntry *fat,
436		  struct dosDirEntry *dir)
437{
438	struct dosDirEntry dirent, *d;
439	u_char *p, *vallfn, *invlfn, *empty;
440	off_t off;
441	int i, j, k, last;
442	cl_t cl, valcl = ~0, invcl = ~0, empcl = ~0;
443	char *t;
444	u_int lidx = 0;
445	int shortSum;
446	int mod = FSOK;
447#define	THISMOD	0x8000			/* Only used within this routine */
448
449	cl = dir->head;
450	if (dir->parent && (cl < CLUST_FIRST || cl >= boot->NumClusters)) {
451		/*
452		 * Already handled somewhere else.
453		 */
454		return FSOK;
455	}
456	shortSum = -1;
457	vallfn = invlfn = empty = NULL;
458	do {
459		if (!(boot->flags & FAT32) && !dir->parent) {
460			last = boot->RootDirEnts * 32;
461			off = boot->ResSectors + boot->FATs * boot->FATsecs;
462		} else {
463			last = boot->SecPerClust * boot->BytesPerSec;
464			off = cl * boot->SecPerClust + boot->ClusterOffset;
465		}
466
467		off *= boot->BytesPerSec;
468		if (lseek(f, off, SEEK_SET) != off
469		    || read(f, buffer, last) != last) {
470			perror("Unable to read directory");
471			return FSFATAL;
472		}
473		last /= 32;
474		/*
475		 * Check `.' and `..' entries here?			XXX
476		 */
477		for (p = buffer, i = 0; i < last; i++, p += 32) {
478			if (dir->fsckflags & DIREMPWARN) {
479				*p = SLOT_EMPTY;
480				continue;
481			}
482
483			if (*p == SLOT_EMPTY || *p == SLOT_DELETED) {
484				if (*p == SLOT_EMPTY) {
485					dir->fsckflags |= DIREMPTY;
486					empty = p;
487					empcl = cl;
488				}
489				continue;
490			}
491
492			if (dir->fsckflags & DIREMPTY) {
493				if (!(dir->fsckflags & DIREMPWARN)) {
494					pwarn("%s has entries after end of directory\n",
495					      fullpath(dir));
496					if (ask(1, "Extend")) {
497						u_char *q;
498
499						dir->fsckflags &= ~DIREMPTY;
500						if (delete(f, boot, fat,
501							   empcl, empty - buffer,
502							   cl, p - buffer, 1) == FSFATAL)
503							return FSFATAL;
504						q = empcl == cl ? empty : buffer;
505						for (; q < p; q += 32)
506							*q = SLOT_DELETED;
507						mod |= THISMOD|FSDIRMOD;
508					} else if (ask(0, "Truncate"))
509						dir->fsckflags |= DIREMPWARN;
510				}
511				if (dir->fsckflags & DIREMPWARN) {
512					*p = SLOT_DELETED;
513					mod |= THISMOD|FSDIRMOD;
514					continue;
515				} else if (dir->fsckflags & DIREMPTY)
516					mod |= FSERROR;
517				empty = NULL;
518			}
519
520			if (p[11] == ATTR_WIN95) {
521				if (*p & LRFIRST) {
522					if (shortSum != -1) {
523						if (!invlfn) {
524							invlfn = vallfn;
525							invcl = valcl;
526						}
527					}
528					memset(longName, 0, sizeof longName);
529					shortSum = p[13];
530					vallfn = p;
531					valcl = cl;
532				} else if (shortSum != p[13]
533					   || lidx != (*p & LRNOMASK)) {
534					if (!invlfn) {
535						invlfn = vallfn;
536						invcl = valcl;
537					}
538					if (!invlfn) {
539						invlfn = p;
540						invcl = cl;
541					}
542					vallfn = NULL;
543				}
544				lidx = *p & LRNOMASK;
545				t = longName + --lidx * 13;
546				for (k = 1; k < 11 && t < longName + sizeof(longName); k += 2) {
547					if (!p[k] && !p[k + 1])
548						break;
549					*t++ = p[k];
550					/*
551					 * Warn about those unusable chars in msdosfs here?	XXX
552					 */
553					if (p[k + 1])
554						t[-1] = '?';
555				}
556				if (k >= 11)
557					for (k = 14; k < 26 && t < longName + sizeof(longName); k += 2) {
558						if (!p[k] && !p[k + 1])
559							break;
560						*t++ = p[k];
561						if (p[k + 1])
562							t[-1] = '?';
563					}
564				if (k >= 26)
565					for (k = 28; k < 32 && t < longName + sizeof(longName); k += 2) {
566						if (!p[k] && !p[k + 1])
567							break;
568						*t++ = p[k];
569						if (p[k + 1])
570							t[-1] = '?';
571					}
572				if (t >= longName + sizeof(longName)) {
573					pwarn("long filename too long\n");
574					if (!invlfn) {
575						invlfn = vallfn;
576						invcl = valcl;
577					}
578					vallfn = NULL;
579				}
580				if (p[26] | (p[27] << 8)) {
581					pwarn("long filename record cluster start != 0\n");
582					if (!invlfn) {
583						invlfn = vallfn;
584						invcl = cl;
585					}
586					vallfn = NULL;
587				}
588				continue;	/* long records don't carry further
589						 * information */
590			}
591
592			/*
593			 * This is a standard msdosfs directory entry.
594			 */
595			memset(&dirent, 0, sizeof dirent);
596
597			/*
598			 * it's a short name record, but we need to know
599			 * more, so get the flags first.
600			 */
601			dirent.flags = p[11];
602
603			/*
604			 * Translate from 850 to ISO here		XXX
605			 */
606			for (j = 0; j < 8; j++)
607				dirent.name[j] = p[j];
608			dirent.name[8] = '\0';
609			for (k = 7; k >= 0 && dirent.name[k] == ' '; k--)
610				dirent.name[k] = '\0';
611			if (dirent.name[k] != '\0')
612				k++;
613			if (dirent.name[0] == SLOT_E5)
614				dirent.name[0] = 0xe5;
615
616			if (dirent.flags & ATTR_VOLUME) {
617				if (vallfn || invlfn) {
618					mod |= removede(f, boot, fat,
619							invlfn ? invlfn : vallfn, p,
620							invlfn ? invcl : valcl, -1, 0,
621							fullpath(dir), 2);
622					vallfn = NULL;
623					invlfn = NULL;
624				}
625				continue;
626			}
627
628			if (p[8] != ' ')
629				dirent.name[k++] = '.';
630			for (j = 0; j < 3; j++)
631				dirent.name[k++] = p[j+8];
632			dirent.name[k] = '\0';
633			for (k--; k >= 0 && dirent.name[k] == ' '; k--)
634				dirent.name[k] = '\0';
635
636			if (vallfn && shortSum != calcShortSum(p)) {
637				if (!invlfn) {
638					invlfn = vallfn;
639					invcl = valcl;
640				}
641				vallfn = NULL;
642			}
643			dirent.head = p[26] | (p[27] << 8);
644			if (boot->ClustMask == CLUST32_MASK)
645				dirent.head |= (p[20] << 16) | (p[21] << 24);
646			dirent.size = p[28] | (p[29] << 8) | (p[30] << 16) | (p[31] << 24);
647			if (vallfn) {
648				strlcpy(dirent.lname, longName,
649				    sizeof(dirent.lname));
650				longName[0] = '\0';
651				shortSum = -1;
652			}
653
654			dirent.parent = dir;
655			dirent.next = dir->child;
656
657			if (invlfn) {
658				mod |= k = removede(f, boot, fat,
659						    invlfn, vallfn ? vallfn : p,
660						    invcl, vallfn ? valcl : cl, cl,
661						    fullpath(&dirent), 0);
662				if (mod & FSFATAL)
663					return FSFATAL;
664				if (vallfn
665				    ? (valcl == cl && vallfn != buffer)
666				    : p != buffer)
667					if (k & FSDIRMOD)
668						mod |= THISMOD;
669			}
670
671			vallfn = NULL; /* not used any longer */
672			invlfn = NULL;
673
674			if (dirent.size == 0 && !(dirent.flags & ATTR_DIRECTORY)) {
675				if (dirent.head != 0) {
676					pwarn("%s has clusters, but size 0\n",
677					      fullpath(&dirent));
678					if (ask(1, "Drop allocated clusters")) {
679						p[26] = p[27] = 0;
680						if (boot->ClustMask == CLUST32_MASK)
681							p[20] = p[21] = 0;
682						clearchain(boot, fat, dirent.head);
683						dirent.head = 0;
684						mod |= THISMOD|FSDIRMOD|FSFATMOD;
685					} else
686						mod |= FSERROR;
687				}
688			} else if (dirent.head == 0
689				   && !strcmp(dirent.name, "..")
690				   && dir->parent			/* XXX */
691				   && !dir->parent->parent) {
692				/*
693				 *  Do nothing, the parent is the root
694				 */
695			} else if (dirent.head < CLUST_FIRST
696				   || dirent.head >= boot->NumClusters
697				   || fat[dirent.head].next == CLUST_FREE
698				   || (fat[dirent.head].next >= CLUST_RSRVD
699				       && fat[dirent.head].next < CLUST_EOFS)
700				   || fat[dirent.head].head != dirent.head) {
701				if (dirent.head == 0)
702					pwarn("%s has no clusters\n",
703					      fullpath(&dirent));
704				else if (dirent.head < CLUST_FIRST
705					 || dirent.head >= boot->NumClusters)
706					pwarn("%s starts with cluster out of range(%u)\n",
707					      fullpath(&dirent),
708					      dirent.head);
709				else if (fat[dirent.head].next == CLUST_FREE)
710					pwarn("%s starts with free cluster\n",
711					      fullpath(&dirent));
712				else if (fat[dirent.head].next >= CLUST_RSRVD)
713					pwarn("%s starts with cluster marked %s\n",
714					      fullpath(&dirent),
715					      rsrvdcltype(fat[dirent.head].next));
716				else
717					pwarn("%s doesn't start a cluster chain\n",
718					      fullpath(&dirent));
719				if (dirent.flags & ATTR_DIRECTORY) {
720					if (ask(0, "Remove")) {
721						*p = SLOT_DELETED;
722						mod |= THISMOD|FSDIRMOD;
723					} else
724						mod |= FSERROR;
725					continue;
726				} else {
727					if (ask(1, "Truncate")) {
728						p[28] = p[29] = p[30] = p[31] = 0;
729						p[26] = p[27] = 0;
730						if (boot->ClustMask == CLUST32_MASK)
731							p[20] = p[21] = 0;
732						dirent.size = 0;
733						mod |= THISMOD|FSDIRMOD;
734					} else
735						mod |= FSERROR;
736				}
737			}
738
739			if (dirent.head >= CLUST_FIRST && dirent.head < boot->NumClusters)
740				fat[dirent.head].flags |= FAT_USED;
741
742			if (dirent.flags & ATTR_DIRECTORY) {
743				/*
744				 * gather more info for directories
745				 */
746				struct dirTodoNode *n;
747
748				if (dirent.size) {
749					pwarn("Directory %s has size != 0\n",
750					      fullpath(&dirent));
751					if (ask(1, "Correct")) {
752						p[28] = p[29] = p[30] = p[31] = 0;
753						dirent.size = 0;
754						mod |= THISMOD|FSDIRMOD;
755					} else
756						mod |= FSERROR;
757				}
758				/*
759				 * handle `.' and `..' specially
760				 */
761				if (strcmp(dirent.name, ".") == 0) {
762					if (dirent.head != dir->head) {
763						pwarn("`.' entry in %s has incorrect start cluster\n",
764						      fullpath(dir));
765						if (ask(1, "Correct")) {
766							dirent.head = dir->head;
767							p[26] = (u_char)dirent.head;
768							p[27] = (u_char)(dirent.head >> 8);
769							if (boot->ClustMask == CLUST32_MASK) {
770								p[20] = (u_char)(dirent.head >> 16);
771								p[21] = (u_char)(dirent.head >> 24);
772							}
773							mod |= THISMOD|FSDIRMOD;
774						} else
775							mod |= FSERROR;
776					}
777					continue;
778				}
779				if (strcmp(dirent.name, "..") == 0) {
780					if (dir->parent) {		/* XXX */
781						if (!dir->parent->parent) {
782							if (dirent.head) {
783								pwarn("`..' entry in %s has non-zero start cluster\n",
784								      fullpath(dir));
785								if (ask(1, "Correct")) {
786									dirent.head = 0;
787									p[26] = p[27] = 0;
788									if (boot->ClustMask == CLUST32_MASK)
789										p[20] = p[21] = 0;
790									mod |= THISMOD|FSDIRMOD;
791								} else
792									mod |= FSERROR;
793							}
794						} else if (dirent.head != dir->parent->head) {
795							pwarn("`..' entry in %s has incorrect start cluster\n",
796							      fullpath(dir));
797							if (ask(1, "Correct")) {
798								dirent.head = dir->parent->head;
799								p[26] = (u_char)dirent.head;
800								p[27] = (u_char)(dirent.head >> 8);
801								if (boot->ClustMask == CLUST32_MASK) {
802									p[20] = (u_char)(dirent.head >> 16);
803									p[21] = (u_char)(dirent.head >> 24);
804								}
805								mod |= THISMOD|FSDIRMOD;
806							} else
807								mod |= FSERROR;
808						}
809					}
810					continue;
811				}
812
813				/* create directory tree node */
814				if (!(d = newDosDirEntry())) {
815					perror("No space for directory");
816					return FSFATAL;
817				}
818				memcpy(d, &dirent, sizeof(struct dosDirEntry));
819				/* link it into the tree */
820				dir->child = d;
821
822				/* Enter this directory into the todo list */
823				if (!(n = newDirTodo())) {
824					perror("No space for todo list");
825					return FSFATAL;
826				}
827				n->next = pendingDirectories;
828				n->dir = d;
829				pendingDirectories = n;
830			} else {
831				mod |= k = checksize(boot, fat, p, &dirent);
832				if (k & FSDIRMOD)
833					mod |= THISMOD;
834			}
835			boot->NumFiles++;
836		}
837		if (mod & THISMOD) {
838			last *= 32;
839			if (lseek(f, off, SEEK_SET) != off
840			    || write(f, buffer, last) != last) {
841				perror("Unable to write directory");
842				return FSFATAL;
843			}
844			mod &= ~THISMOD;
845		}
846	} while ((cl = fat[cl].next) >= CLUST_FIRST && cl < boot->NumClusters);
847	if (invlfn || vallfn)
848		mod |= removede(f, boot, fat,
849				invlfn ? invlfn : vallfn, p,
850				invlfn ? invcl : valcl, -1, 0,
851				fullpath(dir), 1);
852	return mod & ~THISMOD;
853}
854
855int
856handleDirTree(int dosfs, struct bootblock *boot, struct fatEntry *fat)
857{
858	int mod;
859
860	mod = readDosDirSection(dosfs, boot, fat, rootDir);
861	if (mod & FSFATAL)
862		return FSFATAL;
863
864	/*
865	 * process the directory todo list
866	 */
867	while (pendingDirectories) {
868		struct dosDirEntry *dir = pendingDirectories->dir;
869		struct dirTodoNode *n = pendingDirectories->next;
870
871		/*
872		 * remove TODO entry now, the list might change during
873		 * directory reads
874		 */
875		freeDirTodo(pendingDirectories);
876		pendingDirectories = n;
877
878		/*
879		 * handle subdirectory
880		 */
881		mod |= readDosDirSection(dosfs, boot, fat, dir);
882		if (mod & FSFATAL)
883			return FSFATAL;
884	}
885
886	return mod;
887}
888
889/*
890 * Try to reconnect a FAT chain into dir
891 */
892static u_char *lfbuf;
893static cl_t lfcl;
894static off_t lfoff;
895
896int
897reconnect(int dosfs, struct bootblock *boot, struct fatEntry *fat, cl_t head)
898{
899	struct dosDirEntry d;
900	u_char *p;
901
902	if (!ask(1, "Reconnect"))
903		return FSERROR;
904
905	if (!lostDir) {
906		for (lostDir = rootDir->child; lostDir; lostDir = lostDir->next) {
907			if (!strcmp(lostDir->name, LOSTDIR))
908				break;
909		}
910		if (!lostDir) {		/* Create LOSTDIR?		XXX */
911			pwarn("No %s directory\n", LOSTDIR);
912			return FSERROR;
913		}
914	}
915	if (!lfbuf) {
916		lfbuf = malloc(boot->ClusterSize);
917		if (!lfbuf) {
918			perror("No space for buffer");
919			return FSFATAL;
920		}
921		p = NULL;
922	} else
923		p = lfbuf;
924	while (1) {
925		if (p)
926			for (; p < lfbuf + boot->ClusterSize; p += 32)
927				if (*p == SLOT_EMPTY
928				    || *p == SLOT_DELETED)
929					break;
930		if (p && p < lfbuf + boot->ClusterSize)
931			break;
932		lfcl = p ? fat[lfcl].next : lostDir->head;
933		if (lfcl < CLUST_FIRST || lfcl >= boot->NumClusters) {
934			/* Extend LOSTDIR?				XXX */
935			pwarn("No space in %s\n", LOSTDIR);
936			return FSERROR;
937		}
938		lfoff = lfcl * boot->ClusterSize
939		    + boot->ClusterOffset * boot->BytesPerSec;
940		if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
941		    || read(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
942			perror("could not read LOST.DIR");
943			return FSFATAL;
944		}
945		p = lfbuf;
946	}
947
948	boot->NumFiles++;
949	/* Ensure uniqueness of entry here!				XXX */
950	memset(&d, 0, sizeof d);
951	(void)snprintf(d.name, sizeof(d.name), "%u", head);
952	d.flags = 0;
953	d.head = head;
954	d.size = fat[head].length * boot->ClusterSize;
955
956	memset(p, 0, 32);
957	memset(p, ' ', 11);
958	memcpy(p, d.name, strlen(d.name));
959	p[26] = (u_char)d.head;
960	p[27] = (u_char)(d.head >> 8);
961	if (boot->ClustMask == CLUST32_MASK) {
962		p[20] = (u_char)(d.head >> 16);
963		p[21] = (u_char)(d.head >> 24);
964	}
965	p[28] = (u_char)d.size;
966	p[29] = (u_char)(d.size >> 8);
967	p[30] = (u_char)(d.size >> 16);
968	p[31] = (u_char)(d.size >> 24);
969	fat[head].flags |= FAT_USED;
970	if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
971	    || write(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
972		perror("could not write LOST.DIR");
973		return FSFATAL;
974	}
975	return FSDIRMOD;
976}
977
978void
979finishlf(void)
980{
981	if (lfbuf)
982		free(lfbuf);
983	lfbuf = NULL;
984}
985