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