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