1/*	$NetBSD: dir.c,v 1.24 2011/02/07 17:36:42 christos 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 *
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.24 2011/02/07 17:36:42 christos Exp $");
34#endif /* not lint */
35
36#include <stdio.h>
37#include <stdlib.h>
38#include <string.h>
39#include <ctype.h>
40#include <unistd.h>
41#include <assert.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 char *eLongName = longName + sizeof(longName);
208static u_char *buffer = NULL;
209static u_char *delbuf = NULL;
210
211struct 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->RootDirEnts * 32;
226	b2 = boot->SecPerClust * boot->BytesPerSec;
227
228	if ((buffer = malloc(len = b1 > b2 ? 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->RootCl < CLUST_FIRST || boot->RootCl >= boot->NumClusters) {
249			pfatal("Root directory starts with cluster out of range(%u)",
250			       boot->RootCl);
251			return FSFATAL;
252		}
253		cl = fat[boot->RootCl].next;
254		if (cl < CLUST_FIRST
255		    || (cl >= CLUST_RSRVD && cl< CLUST_EOFS)
256		    || fat[boot->RootCl].head != boot->RootCl) {
257			if (cl == CLUST_FREE)
258				pwarn("Root directory starts with free cluster\n");
259			else if (cl >= CLUST_RSRVD)
260				pwarn("Root directory starts with cluster marked %s\n",
261				      rsrvdcltype(cl));
262			else {
263				pfatal("Root directory doesn't start a cluster chain");
264				return FSFATAL;
265			}
266			if (ask(1, "Fix")) {
267				fat[boot->RootCl].next = CLUST_FREE;
268				ret = FSFATMOD;
269			} else
270				ret = FSFATAL;
271		}
272
273		fat[boot->RootCl].flags |= FAT_USED;
274		rootDir->head = boot->RootCl;
275	}
276
277	return ret;
278}
279
280/*
281 * Cleanup after a directory scan
282 */
283void
284finishDosDirSection(void)
285{
286	struct dirTodoNode *p, *np;
287	struct dosDirEntry *d, *nd;
288
289	for (p = pendingDirectories; p; p = np) {
290		np = p->next;
291		freeDirTodo(p);
292	}
293	pendingDirectories = 0;
294	for (d = rootDir; d; d = nd) {
295		if ((nd = d->child) != NULL) {
296			d->child = 0;
297			continue;
298		}
299		if (!(nd = d->next))
300			nd = d->parent;
301		freeDosDirEntry(d);
302	}
303	rootDir = lostDir = NULL;
304	free(buffer);
305	free(delbuf);
306	buffer = NULL;
307	delbuf = NULL;
308}
309
310/*
311 * Delete directory entries between startcl, startoff and endcl, endoff.
312 */
313static int
314delete(int f, struct bootblock *boot, struct fatEntry *fat, cl_t startcl,
315       int startoff, cl_t endcl, int endoff, int notlast)
316{
317	u_char *s, *e;
318	off_t off;
319	int clsz = boot->SecPerClust * boot->BytesPerSec;
320
321	s = delbuf + startoff;
322	e = delbuf + clsz;
323	while (startcl >= CLUST_FIRST && startcl < boot->NumClusters) {
324		if (startcl == endcl) {
325			if (notlast)
326				break;
327			e = delbuf + endoff;
328		}
329		off = startcl * boot->SecPerClust + boot->ClusterOffset;
330		off *= boot->BytesPerSec;
331		if (lseek(f, off, SEEK_SET) != off
332		    || read(f, delbuf, clsz) != clsz) {
333			perr("Unable to read directory");
334			return FSFATAL;
335		}
336		while (s < e) {
337			*s = SLOT_DELETED;
338			s += 32;
339		}
340		if (lseek(f, off, SEEK_SET) != off
341		    || write(f, delbuf, clsz) != clsz) {
342			perr("Unable to write directory");
343			return FSFATAL;
344		}
345		if (startcl == endcl)
346			break;
347		startcl = fat[startcl].next;
348		s = delbuf;
349	}
350	return FSOK;
351}
352
353static int
354removede(int f, struct bootblock *boot, struct fatEntry *fat, u_char *start,
355         u_char *end, cl_t startcl, cl_t endcl, cl_t curcl, char *path,
356	 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", path);
364		break;
365	case 2:
366		pwarn("Invalid long filename entry for volume label\n");
367		break;
368	}
369	if (ask(0, "Remove")) {
370		if (startcl != curcl) {
371			if (delete(f, boot, fat,
372				   startcl, start - buffer,
373				   endcl, end - buffer,
374				   endcl == curcl) == FSFATAL)
375				return FSFATAL;
376			start = buffer;
377		}
378		/* startcl is < CLUST_FIRST for !fat32 root */
379		if ((endcl == curcl) || (startcl < CLUST_FIRST))
380			for (; start < end; start += 32)
381				*start = SLOT_DELETED;
382		return FSDIRMOD;
383	}
384	return FSERROR;
385}
386
387/*
388 * Check an in-memory file entry
389 */
390static int
391checksize(struct bootblock *boot, struct fatEntry *fat, u_char *p,
392	  struct dosDirEntry *dir)
393{
394	/*
395	 * Check size on ordinary files
396	 */
397	u_int32_t physicalSize;
398
399	if (dir->head == CLUST_FREE)
400		physicalSize = 0;
401	else {
402		if (dir->head < CLUST_FIRST || dir->head >= boot->NumClusters)
403			return FSERROR;
404		physicalSize = fat[dir->head].length * boot->ClusterSize;
405	}
406	if (physicalSize < dir->size) {
407		pwarn("size of %s is %u, should at most be %u\n",
408		      fullpath(dir), dir->size, physicalSize);
409		if (ask(1, "Truncate")) {
410			dir->size = physicalSize;
411			p[28] = (u_char)physicalSize;
412			p[29] = (u_char)(physicalSize >> 8);
413			p[30] = (u_char)(physicalSize >> 16);
414			p[31] = (u_char)(physicalSize >> 24);
415			return FSDIRMOD;
416		} else
417			return FSERROR;
418	} else if (physicalSize - dir->size >= boot->ClusterSize) {
419		pwarn("%s has too many clusters allocated\n",
420		      fullpath(dir));
421		if (ask(1, "Drop superfluous clusters")) {
422			cl_t cl;
423			u_int32_t sz = 0;
424
425			for (cl = dir->head; (sz += boot->ClusterSize) < dir->size;)
426				cl = fat[cl].next;
427			clearchain(boot, fat, fat[cl].next);
428			fat[cl].next = CLUST_EOF;
429			return FSFATMOD;
430		} else
431			return FSERROR;
432	}
433	return FSOK;
434}
435
436static int
437procName(int from, int to, char **dst, const u_char *src)
438{
439	int k;
440	char *t = *dst;
441
442	for (k = from; k < to && t < eLongName; k += 2) {
443		if (!src[k] && !src[k + 1])
444			break;
445		*t++ = src[k];
446		/*
447		 * Warn about those unusable chars in msdosfs here?	XXX
448		 */
449		if (src[k + 1])
450		    t[-1] = '?';
451	}
452	*dst = t;
453	return k;
454}
455
456/*
457 * Read a directory and
458 *   - resolve long name records
459 *   - enter file and directory records into the parent's list
460 *   - push directories onto the todo-stack
461 */
462static int
463readDosDirSection(int f, struct bootblock *boot, struct fatEntry *fat,
464		  struct dosDirEntry *dir)
465{
466	struct dosDirEntry dirent, *d;
467	u_char *p, *vallfn, *invlfn, *empty;
468	off_t off;
469	int i, j, k, last;
470	cl_t cl, valcl = ~0, invcl = ~0, empcl = ~0;
471	char *t;
472	u_int lidx = 0;
473	int shortSum;
474	int mod = FSOK;
475#define	THISMOD	0x8000			/* Only used within this routine */
476
477	cl = dir->head;
478	if (dir->parent && (cl < CLUST_FIRST || cl >= boot->NumClusters)) {
479		/*
480		 * Already handled somewhere else.
481		 */
482		return FSOK;
483	}
484	shortSum = -1;
485	vallfn = invlfn = empty = NULL;
486	do {
487		if (!(boot->flags & FAT32) && !dir->parent) {
488			last = boot->RootDirEnts * 32;
489			off = boot->ResSectors + boot->FATs * boot->FATsecs;
490		} else {
491			last = boot->SecPerClust * boot->BytesPerSec;
492			off = cl * boot->SecPerClust + boot->ClusterOffset;
493		}
494
495		off *= boot->BytesPerSec;
496		if (lseek(f, off, SEEK_SET) != off
497		    || read(f, buffer, last) != last) {
498			perr("Unable to read directory");
499			return FSFATAL;
500		}
501		last /= 32;
502		/*
503		 * Check `.' and `..' entries here?			XXX
504		 */
505		for (p = buffer, i = 0; i < last; i++, p += 32) {
506			if (dir->fsckflags & DIREMPWARN) {
507				*p = SLOT_EMPTY;
508				continue;
509			}
510
511			if (*p == SLOT_EMPTY || *p == SLOT_DELETED) {
512				if (*p == SLOT_EMPTY) {
513					dir->fsckflags |= DIREMPTY;
514					empty = p;
515					empcl = cl;
516				}
517				continue;
518			}
519
520			if (dir->fsckflags & DIREMPTY) {
521				if (!(dir->fsckflags & DIREMPWARN)) {
522					pwarn("%s has entries after end of directory\n",
523					      fullpath(dir));
524					if (ask(1, "Extend")) {
525						u_char *q;
526
527						dir->fsckflags &= ~DIREMPTY;
528						if (delete(f, boot, fat,
529							   empcl, empty - buffer,
530							   cl, p - buffer, 1) == FSFATAL)
531							return FSFATAL;
532						q = empcl == cl ? empty : buffer;
533						assert(q != NULL);
534						for (; q < p; q += 32)
535							*q = SLOT_DELETED;
536						mod |= THISMOD|FSDIRMOD;
537					} else if (ask(0, "Truncate"))
538						dir->fsckflags |= DIREMPWARN;
539				}
540				if (dir->fsckflags & DIREMPWARN) {
541					*p = SLOT_DELETED;
542					mod |= THISMOD|FSDIRMOD;
543					continue;
544				} else if (dir->fsckflags & DIREMPTY)
545					mod |= FSERROR;
546				empty = NULL;
547			}
548
549			if (p[11] == ATTR_WIN95) {
550				u_int lrnomask = *p & LRNOMASK;
551				if (*p & LRFIRST) {
552					if (shortSum != -1) {
553						if (!invlfn) {
554							invlfn = vallfn;
555							invcl = valcl;
556						}
557					}
558					memset(longName, 0, sizeof longName);
559					shortSum = p[13];
560					vallfn = p;
561					valcl = cl;
562				} else if (shortSum != p[13]
563				   || lidx != lrnomask) {
564					if (!invlfn) {
565						invlfn = vallfn;
566						invcl = valcl;
567					}
568					if (!invlfn) {
569						invlfn = p;
570						invcl = cl;
571					}
572					vallfn = NULL;
573				}
574				lidx = lrnomask;
575				if (lidx != 0) {
576					t = longName + --lidx * 13;
577					k = procName(1, 11, &t, p);
578					if (k >= 11)
579						k = procName(14, 26, &t, p);
580					if (k >= 26)
581						k = procName(28, 32, &t, p);
582					if (t >= eLongName) {
583						pwarn(
584						    "long filename too long\n");
585						if (!invlfn) {
586							invlfn = vallfn;
587							invcl = valcl;
588						}
589						vallfn = NULL;
590					}
591				}
592				if (p[26] | (p[27] << 8)) {
593					pwarn("long filename record cluster "
594					    "start != 0\n");
595					if (!invlfn) {
596						invlfn = vallfn;
597						invcl = cl;
598					}
599					vallfn = NULL;
600				}
601				continue; 	/* long records don't carry
602						 * further 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 (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	u_char *p;
931
932	if (!ask(1, "Reconnect"))
933		return FSERROR;
934
935	if (!lostDir) {
936		for (lostDir = rootDir->child; lostDir; lostDir = lostDir->next) {
937			if (!strcmp(lostDir->name, LOSTDIR))
938				break;
939		}
940		if (!lostDir) {		/* Create LOSTDIR?		XXX */
941			pwarn("No %s directory\n", LOSTDIR);
942			return FSERROR;
943		}
944	}
945	if (!lfbuf) {
946		lfbuf = malloc(boot->ClusterSize);
947		if (!lfbuf) {
948			perr("No space for buffer");
949			return FSFATAL;
950		}
951		p = NULL;
952	} else
953		p = lfbuf;
954	while (1) {
955		if (p)
956			for (; p < lfbuf + boot->ClusterSize; p += 32)
957				if (*p == SLOT_EMPTY
958				    || *p == SLOT_DELETED)
959					break;
960		if (p && p < lfbuf + boot->ClusterSize)
961			break;
962		lfcl = p ? fat[lfcl].next : lostDir->head;
963		if (lfcl < CLUST_FIRST || lfcl >= boot->NumClusters) {
964			/* Extend LOSTDIR?				XXX */
965			pwarn("No space in %s\n", LOSTDIR);
966			return FSERROR;
967		}
968		lfoff = lfcl * boot->ClusterSize
969		    + boot->ClusterOffset * boot->BytesPerSec;
970		if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
971		    || (size_t)read(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
972			perr("could not read LOST.DIR");
973			return FSFATAL;
974		}
975		p = lfbuf;
976	}
977
978	boot->NumFiles++;
979	/* Ensure uniqueness of entry here!				XXX */
980	memset(&d, 0, sizeof d);
981	(void)snprintf(d.name, sizeof(d.name), "%u", head);
982	d.flags = 0;
983	d.head = head;
984	d.size = fat[head].length * boot->ClusterSize;
985
986	memset(p, 0, 32);
987	memset(p, ' ', 11);
988	memcpy(p, d.name, strlen(d.name));
989	p[26] = (u_char)d.head;
990	p[27] = (u_char)(d.head >> 8);
991	if (boot->ClustMask == CLUST32_MASK) {
992		p[20] = (u_char)(d.head >> 16);
993		p[21] = (u_char)(d.head >> 24);
994	}
995	p[28] = (u_char)d.size;
996	p[29] = (u_char)(d.size >> 8);
997	p[30] = (u_char)(d.size >> 16);
998	p[31] = (u_char)(d.size >> 24);
999	fat[head].flags |= FAT_USED;
1000	if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
1001	    || (size_t)write(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
1002		perr("could not write LOST.DIR");
1003		return FSFATAL;
1004	}
1005	return FSDIRMOD;
1006}
1007
1008void
1009finishlf(void)
1010{
1011	if (lfbuf)
1012		free(lfbuf);
1013	lfbuf = NULL;
1014}
1015