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