1/*
2 * Copyright (c) 2000 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23/*
24 * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank
25 * Copyright (c) 1995 Martin Husemann
26 * Some structure declaration borrowed from Paul Popelka
27 * (paulp@uts.amdahl.com), see /sys/msdosfs/ for reference.
28 *
29 * Redistribution and use in source and binary forms, with or without
30 * modification, are permitted provided that the following conditions
31 * are met:
32 * 1. Redistributions of source code must retain the above copyright
33 *    notice, this list of conditions and the following disclaimer.
34 * 2. Redistributions in binary form must reproduce the above copyright
35 *    notice, this list of conditions and the following disclaimer in the
36 *    documentation and/or other materials provided with the distribution.
37 * 3. All advertising materials mentioning features or use of this software
38 *    must display the following acknowledgement:
39 *	This product includes software developed by Martin Husemann
40 *	and Wolfgang Solfrank.
41 * 4. Neither the name of the University nor the names of its contributors
42 *    may be used to endorse or promote products derived from this software
43 *    without specific prior written permission.
44 *
45 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
46 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
47 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
48 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
49 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
50 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
51 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
52 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
53 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
54 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
55 */
56
57
58#include <sys/cdefs.h>
59
60#include <stdint.h>
61#include <stdio.h>
62#include <stdlib.h>
63#include <string.h>
64#include <ctype.h>
65#include <stdio.h>
66#include <unistd.h>
67#include <time.h>
68
69#include <sys/param.h>
70#include <sys/errno.h>
71
72#include "ext.h"
73#include "fsutil.h"
74
75#define	SLOT_EMPTY	0x00		/* slot has never been used */
76#define	SLOT_E5		0x05		/* the real value is 0xe5 */
77#define	SLOT_DELETED	0xe5		/* file in this slot deleted */
78
79#define	ATTR_NORMAL	0x00		/* normal file */
80#define	ATTR_READONLY	0x01		/* file is readonly */
81#define	ATTR_HIDDEN	0x02		/* file is hidden */
82#define	ATTR_SYSTEM	0x04		/* file is a system file */
83#define	ATTR_VOLUME	0x08		/* entry is a volume label */
84#define	ATTR_DIRECTORY	0x10		/* entry is a directory name */
85#define	ATTR_ARCHIVE	0x20		/* file is new or modified */
86
87#define	ATTR_WIN95	0x0f		/* long name record */
88
89/*
90 * This is the format of the contents of the deTime field in the direntry
91 * structure.
92 * We don't use bitfields because we don't know how compilers for
93 * arbitrary machines will lay them out.
94 */
95#define DT_2SECONDS_MASK	0x1F	/* seconds divided by 2 */
96#define DT_2SECONDS_SHIFT	0
97#define DT_MINUTES_MASK		0x7E0	/* minutes */
98#define DT_MINUTES_SHIFT	5
99#define DT_HOURS_MASK		0xF800	/* hours */
100#define DT_HOURS_SHIFT		11
101
102/*
103 * This is the format of the contents of the deDate field in the direntry
104 * structure.
105 */
106#define DD_DAY_MASK		0x1F	/* day of month */
107#define DD_DAY_SHIFT		0
108#define DD_MONTH_MASK		0x1E0	/* month */
109#define DD_MONTH_SHIFT		5
110#define DD_YEAR_MASK		0xFE00	/* year - 1980 */
111#define DD_YEAR_SHIFT		9
112
113
114/* dir.c */
115static struct dosDirEntry *newDosDirEntry __P((void));
116static void freeDosDirEntry __P((struct dosDirEntry *));
117static struct dirTodoNode *newDirTodo __P((void));
118static void freeDirTodo __P((struct dirTodoNode *));
119static char *fullpath __P((struct dosDirEntry *));
120static u_char calcShortSum __P((u_char *));
121static int delete(int fd, struct bootblock *boot, cl_t startcl, size_t startoff, cl_t endcl, size_t endoff, int notlast);
122static int msdosfs_removede __P((int, struct bootblock *, u_char *,
123    u_char *, cl_t, cl_t, cl_t, char *, int));
124static int checksize __P((struct bootblock *, u_char *, struct dosDirEntry *));
125static int readDosDirSection __P((int, struct bootblock *, struct dosDirEntry *));
126
127/*
128 * Manage free dosDirEntry structures.
129 */
130static struct dosDirEntry *freede;
131
132static struct dosDirEntry *
133newDosDirEntry()
134{
135	struct dosDirEntry *de;
136
137	if (!(de = freede)) {
138		if (!(de = (struct dosDirEntry *)malloc(sizeof *de)))
139			return 0;
140	} else
141		freede = de->next;
142	return de;
143}
144
145static void
146freeDosDirEntry(de)
147	struct dosDirEntry *de;
148{
149	de->next = freede;
150	freede = de;
151}
152
153/*
154 * The same for dirTodoNode structures.
155 */
156static struct dirTodoNode *freedt;
157
158static struct dirTodoNode *
159newDirTodo()
160{
161	struct dirTodoNode *dt;
162
163	if (!(dt = freedt)) {
164		if (!(dt = (struct dirTodoNode *)malloc(sizeof *dt)))
165			return 0;
166	} else
167		freedt = dt->next;
168	return dt;
169}
170
171static void
172freeDirTodo(dt)
173	struct dirTodoNode *dt;
174{
175	dt->next = freedt;
176	freedt = dt;
177}
178
179/*
180 * The stack of unread directories
181 */
182struct dirTodoNode *pendingDirectories = NULL;
183
184/*
185 * Return the full pathname for a directory entry.
186 */
187static char *
188fullpath(dir)
189	struct dosDirEntry *dir;
190{
191	static char namebuf[MAXPATHLEN + 1];
192	char *cp, *np;
193	size_t nl;
194
195	/*
196	 * The loop below returns the empty string for the root directory.
197	 * So special case it to return "/" instead.
198	 */
199	if (dir == rootDir)
200	{
201		namebuf[0] = '/';
202		namebuf[1] = '\0';
203		return namebuf;
204	}
205
206	cp = namebuf + sizeof namebuf - 1;
207	*cp = '\0';
208	do {
209		np = dir->lname[0] ? dir->lname : dir->name;
210		nl = strlen(np);
211		if ((cp -= nl) <= namebuf + 1)
212			break;
213		memcpy(cp, np, nl);
214		*--cp = '/';
215	} while ((dir = dir->parent) != NULL);
216	if (dir)
217		*--cp = '?';
218	else
219		cp++;
220
221	return cp;
222}
223
224/*
225 * Calculate a checksum over an 8.3 alias name
226 */
227static u_char
228calcShortSum(p)
229	u_char *p;
230{
231	u_char sum = 0;
232	int i;
233
234	for (i = 0; i < 11; i++) {
235		sum = (sum << 7)|(sum >> 1);	/* rotate right */
236		sum += p[i];
237	}
238
239	return sum;
240}
241
242
243/*
244 * markDosDirChain
245 *
246 * Follow the cluster chain pointed to by @dir.  Mark all of the clusters in
247 * use in our bitmap.  If we encounter a cluster that is already marked in
248 * use, out of range, reserved, or EOF, then set @dir->end to that cluster
249 * number.  Also sets @dir->physicalSize to the size, in bytes, of valid
250 * clusters in the chain.
251 *
252 * Assumes that the caller has already verified that the starting cluster
253 * is valid, not CLUST_FREE, and not yet marked used.
254 */
255static int
256markDosDirChain(struct bootblock *boot, struct dosDirEntry *dir)
257{
258	int err = FSOK;
259	cl_t cluster, prev, value;
260	cl_t count;
261
262	cluster = dir->head;
263	prev = 0;
264	count = 0;
265	while (cluster >= CLUST_FIRST && cluster < boot->NumClusters && !isUsed(cluster))
266	{
267		/*
268		 * Clusters that are marked "reserved" or "bad" cannot be part of the
269		 * file.  We must truncate the file at the previous cluster, which
270		 * is why we break from the loop early.
271		 *
272		 * Clusters marked "free", or which point to invalid cluster numbers
273		 * can be allocated to the file my setting them to CLUST_EOF.  We
274		 * catch these cases on the next iteration of the loop so that the
275		 * current cluster will remain part of the file (i.e. it becomes
276		 * "previous" as we iterate once more).
277		 */
278		value = fat_get(cluster);
279		if (value == CLUST_RSRVD || value == CLUST_BAD)
280		{
281			cluster = value;
282			break;
283		}
284		markUsed(cluster);
285		++count;
286		prev = cluster;
287		cluster = fat_get(cluster);
288	}
289
290	/*
291	 * We hit the end of the cluster chain.  If it wasn't due to EOF, then see
292	 * if we can fix the problem.
293	 */
294	if (cluster < CLUST_EOFS)
295	{
296		if (cluster == CLUST_FREE || cluster >= CLUST_RSRVD)
297			pwarn("%s: Cluster chain starting at %u ends with cluster marked %s\n",
298				fullpath(dir), dir->head, rsrvdcltype(cluster));
299		else if (cluster < CLUST_FIRST || cluster >= boot->NumClusters)
300			pwarn("%s: Cluster chain starting at %u continues with cluster out of range (%u)\n",
301				fullpath(dir), dir->head, cluster);
302		else
303			pwarn("%s: Cluster chain starting at %u is cross-linked at cluster %u\n",
304				fullpath(dir), dir->head, cluster);
305		if (ask(1, "Truncate"))
306		{
307			err = fat_set(prev, CLUST_EOF);
308			if (err)
309				cluster = CLUST_ERROR;
310			else
311				cluster = CLUST_EOF;
312		}
313	}
314
315	dir->end = cluster;
316	dir->physicalSize = (u_int64_t)count * boot->ClusterSize;
317	if (cluster == CLUST_ERROR)
318		return FSFATAL;
319
320	return err;
321}
322
323
324/*
325 * Global variables temporarily used during a directory scan
326 */
327static char longName[DOSLONGNAMELEN] = "";
328static u_char *buffer = NULL;
329static u_char *delbuf = NULL;
330
331struct dosDirEntry *rootDir;
332static struct dosDirEntry *lostDir;
333
334/*
335 * Init internal state for a new directory scan.
336 */
337int
338resetDosDirSection(struct bootblock *boot)
339{
340	int b1, b2;
341	cl_t cl;
342	int ret = FSOK;
343
344	b1 = boot->RootDirEnts * 32;
345	b2 = boot->SecPerClust * boot->BytesPerSec;
346
347	if (!(buffer = malloc(b1 > b2 ? b1 : b2))
348	    || !(delbuf = malloc(b2))
349	    || !(rootDir = newDosDirEntry())) {
350		perr("No space for directory");
351		return FSFATAL;
352	}
353	memset(rootDir, 0, sizeof *rootDir);
354	if (boot->flags & FAT32) {
355		if (boot->RootCl < CLUST_FIRST || boot->RootCl >= boot->NumClusters) {
356			pfatal("Root directory starts with cluster out of range(%u)\n",
357			       boot->RootCl);
358			return FSFATAL;
359		}
360
361		cl = fat_get(boot->RootCl);
362		if (cl == CLUST_ERROR)
363			return FSFATAL;
364
365		if (cl < CLUST_FIRST
366		    || (cl >= CLUST_RSRVD && cl< CLUST_EOFS)) {
367			if (cl == CLUST_FREE)
368				pwarn("Root directory starts with free cluster\n");
369			else if (cl >= CLUST_RSRVD)
370				pwarn("Root directory starts with cluster marked %s\n",
371				      rsrvdcltype(cl));
372			if (ask(1, "Fix")) {
373				/*
374				 * This used to assign CLUST_FREE.  How was that a good idea???
375				 */
376				ret = fat_set(boot->RootCl, CLUST_EOF);
377				if (!ret)
378					ret = FSFATMOD;
379
380				/*
381				 * We have to mark the root cluster as free so that
382				 * markDosDirChain below won't think the first root
383				 * cluster is cross-linked to itself.
384				 */
385				markFree(boot->RootCl);
386			} else
387				return FSFATAL;
388		}
389
390		rootDir->head = boot->RootCl;
391		ret |= markDosDirChain(boot, rootDir);
392	}
393
394	return ret;
395}
396
397/*
398 * Cleanup after a directory scan
399 */
400void
401finishDosDirSection()
402{
403	struct dirTodoNode *p, *np;
404	struct dosDirEntry *d, *nd;
405
406	for (p = pendingDirectories; p; p = np) {
407		np = p->next;
408		freeDirTodo(p);
409	}
410	pendingDirectories = 0;
411	for (d = rootDir; d; d = nd) {
412		if ((nd = d->child) != NULL) {
413			d->child = 0;
414			continue;
415		}
416		if (!(nd = d->next))
417			nd = d->parent;
418		freeDosDirEntry(d);
419	}
420	rootDir = lostDir = NULL;
421	free(buffer);
422	free(delbuf);
423	buffer = NULL;
424	delbuf = NULL;
425}
426
427/*
428 * Delete a range of directory entries.
429 *
430 * Inputs:
431 *  fd          File descriptor.
432 *  startcl     Cluster number containing first directory entry.
433 *  startoff    Offset within cluster of first directory entry.
434 *  endcl       Cluster number containing last directory entry.
435 *  endoff      Offset within cluster beyond last byte of last directory entry.
436 *  notlast     If true, don't delete the directory entries in the last cluster
437 *              (endcl); the caller already has that cluster in memory and will
438 *              update those entries itself.
439 */
440static int
441delete(int fd, struct bootblock *boot, cl_t startcl, size_t startoff, cl_t endcl, size_t endoff, int notlast)
442{
443	u_char *s, *e;
444	off_t off;
445	int clsz = boot->SecPerClust * boot->BytesPerSec;
446
447	s = delbuf + startoff;
448	e = delbuf + clsz;
449	while (startcl >= CLUST_FIRST && startcl < boot->NumClusters) {
450		if (startcl == endcl) {
451			if (notlast)
452				break;
453			e = delbuf + endoff;
454		}
455		off = startcl * boot->SecPerClust + boot->ClusterOffset;
456		off *= boot->BytesPerSec;
457		if (lseek(fd, off, SEEK_SET) != off
458		    || read(fd, delbuf, clsz) != clsz) {
459			perr("Unable to read directory");
460			return FSFATAL;
461		}
462		while (s < e) {
463			*s = SLOT_DELETED;
464			s += 32;
465		}
466		if (lseek(fd, off, SEEK_SET) != off
467		    || write(fd, delbuf, clsz) != clsz) {
468			perr("Unable to write directory");
469			return FSFATAL;
470		}
471		if (startcl == endcl)
472			break;
473		startcl = fat_get(startcl);
474		if (startcl == CLUST_ERROR)
475			return FSFATAL;
476		s = delbuf;
477	}
478	return FSOK;
479}
480
481static int
482msdosfs_removede(f, boot, start, end, startcl, endcl, curcl, path, type)
483	int f;
484	struct bootblock *boot;
485	u_char *start;
486	u_char *end;
487	cl_t startcl;
488	cl_t endcl;
489	cl_t curcl;
490	char *path;
491	int type;
492{
493	switch (type) {
494	case 0:
495		pwarn("Invalid long filename entry for %s\n", path);
496		break;
497	case 1:
498		pwarn("Invalid long filename entry at end of directory %s\n", path);
499		break;
500	case 2:
501		pwarn("Invalid long filename entry for volume label\n");
502		break;
503	}
504	if (ask(0, "Remove")) {
505		if (startcl != curcl) {
506			if (delete(f, boot,
507				   startcl, start - buffer,
508				   endcl, end - buffer,
509				   endcl == curcl) == FSFATAL)
510				return FSFATAL;
511			start = buffer;
512		}
513		if (endcl == curcl)
514			for (; start < end; start += 32)
515				*start = SLOT_DELETED;
516		return FSDIRMOD;
517	}
518	return FSERROR;
519}
520
521/*
522 * Check the size of a file represented by an in-memory file entry
523 *
524 * Assumes our caller has checked that dir->head is a valid cluster number.
525 * Assumes that markDosDirChain has been called, and that dir->physicalSize
526 * has been set up.
527 */
528static int
529checksize(boot, p, dir)
530	struct bootblock *boot;
531	u_char *p;
532	struct dosDirEntry *dir;
533{
534	/*
535	 * Check size on ordinary files
536	 */
537	if (dir->physicalSize < dir->size) {
538		pwarn("size of %s is %u, should at most be %llu\n",
539		      fullpath(dir), dir->size, dir->physicalSize);
540		if (ask(1, "Truncate")) {
541			dir->size = (uint32_t)dir->physicalSize;
542			p[28] = (u_char)dir->physicalSize;
543			p[29] = (u_char)(dir->physicalSize >> 8);
544			p[30] = (u_char)(dir->physicalSize >> 16);
545			p[31] = (u_char)(dir->physicalSize >> 24);
546			return FSDIRMOD;
547		} else
548			return FSERROR;
549	} else if (dir->physicalSize - dir->size >= boot->ClusterSize) {
550		pwarn("%s has too many clusters allocated (logical=%u, physical=%llu)\n",
551		      fullpath(dir), dir->size, dir->physicalSize);
552		if (ask(1, "Drop superfluous clusters")) {
553			int mod = 0;
554			cl_t cl, next;
555			u_int64_t sz = 0;
556
557			if (dir->size == 0)
558			{
559				/* Set "first cluster" in directory to 0 */
560				p[20]=p[21]=p[26]=p[27] = 0;
561				mod = FSDIRMOD;
562
563				/* Begin deallocating with the previous first cluster */
564				cl = dir->head;
565			}
566			else
567			{
568				/*
569				 * Skip over the clusters containing the first dir->size bytes
570				 */
571				for (cl = dir->head; (sz += boot->ClusterSize) < dir->size;)
572				{
573					cl = fat_get(cl);
574					if (cl == CLUST_ERROR)
575						return FSFATAL;
576				}
577
578				/* When we get here, "cl" is the new last cluster of the file */
579
580				/*
581				 * Remember the first cluster to be dropped.
582				 * Mark the new last cluster as CLUST_EOF.
583				 */
584				next = fat_get(cl);
585				if (next == CLUST_ERROR)
586					return FSFATAL;
587				if (fat_set(cl, CLUST_EOF))
588					return FSFATAL;
589				cl = next;
590			}
591
592			/*
593			 * Free the clusters up to physicalSize
594			 *
595			 * NOTE: We can't just follow the chain to CLUST_EOF because it
596			 * might be cross-linked with some other chain.  Assumes
597			 * dir->physicalSize is set to the size of the chain before any
598			 * error or cross-link.
599			 */
600			while (sz < dir->physicalSize)
601			{
602				next = fat_get(cl);
603				if (next == CLUST_ERROR)
604					return FSFATAL;
605				if (fat_set(cl, CLUST_FREE))
606					return FSFATAL;
607				cl = next;
608				sz += boot->ClusterSize;
609			}
610
611			return mod | FSFATMOD;
612		} else
613			return FSERROR;
614	}
615	return FSOK;
616}
617
618
619/*
620 * Is the given directory entry really a subdirectory?
621 *
622 * Read the first cluster of the given subdirectory and check whether the
623 * first two short names are "." and "..".
624 *
625 * Return values:
626 *  0           Is a valid subdirectory
627 *  ENOTDIR     Is not a valid subdirectory
628 *  ENOMEM      Unable to allocate memory for an I/O buffer
629 *  EIO         Unable to read from the subdirectory
630 */
631static errno_t isSubdirectory(int fd, struct bootblock *boot, struct dosDirEntry *dir)
632{
633    off_t offset;
634    ssize_t amount;
635    char *buf;
636    errno_t err = 0;
637
638    buf = malloc(boot->BytesPerSec);
639    if (buf == NULL) {
640        perr("No memory for subdirectory buffer");
641        return ENOMEM;
642    }
643
644    offset = ((off_t)dir->head * boot->SecPerClust + boot->ClusterOffset) * boot->BytesPerSec;
645    amount = pread(fd, buf, boot->BytesPerSec, offset);
646    if (amount != boot->BytesPerSec) {
647        pfatal("Unable to read cluster %u", dir->head);
648        err = EIO;
649        goto fail;
650    }
651
652    /* Make sure the first two children are named "." and ".." */
653    if (memcmp(buf, ".          ", 11) || memcmp(buf+32, "..         ", 11)) {
654        err = ENOTDIR;
655        goto fail;
656    }
657
658    /* Make sure "." and ".." are marked as directories */
659    if ((buf[11] & ATTR_DIRECTORY) == 0 || (buf[32+11] & ATTR_DIRECTORY) == 0) {
660        err = ENOTDIR;
661    }
662
663fail:
664    free(buf);
665    return err;
666}
667
668/*
669 * Read a directory and
670 *   - resolve long name records
671 *   - enter file and directory records into the parent's list
672 *   - push directories onto the todo-stack
673 */
674static int
675readDosDirSection(f, boot, dir)
676	int f;
677	struct bootblock *boot;
678	struct dosDirEntry *dir;
679{
680	struct dosDirEntry dirent, *d;
681	u_char *p, *vallfn, *invlfn, *empty;
682	off_t off;
683	int i, j, k, last;
684	cl_t cl, valcl = ~0, invcl = ~0, empcl = ~0;
685	cl_t last_cl;
686	char *t;
687	u_int lidx = 0;
688	int shortSum;
689	int mod = FSOK;
690#define	THISMOD	0x8000			/* Only used within this routine */
691
692	cl = dir->head;
693	if (dir->parent && (cl < CLUST_FIRST || cl >= boot->NumClusters)) {
694		/*
695		 * Already handled somewhere else.
696		 */
697		fprintf(stderr, "readDosDirSection: Start cluster (%u) out of range; ignoring\n", cl);
698		return FSOK;
699	}
700
701	shortSum = -1;
702	vallfn = invlfn = empty = NULL;
703	do {
704		last_cl = cl;	/* Remember last cluster accessed before exiting loop */
705
706        /*
707         * Get the starting offset and length of the current chunk of the
708         * directory.
709         */
710		if (!(boot->flags & FAT32) && !dir->parent) {
711			last = boot->RootDirEnts * 32;
712			off = boot->ResSectors + boot->FATs * boot->FATsecs;
713		} else {
714			last = boot->SecPerClust * boot->BytesPerSec;
715			off = cl * boot->SecPerClust + boot->ClusterOffset;
716		}
717
718		off *= boot->BytesPerSec;
719		if (lseek(f, off, SEEK_SET) != off
720		    || read(f, buffer, last) != last) {
721			perr("Unable to read directory");
722			return FSFATAL;
723		}
724		last /= 32;
725
726		/*
727         * For each "slot" in the directory...
728         */
729        for (p = buffer, i = 0; i < last; i++, p += 32) {
730			if (dir->fsckflags & DIREMPWARN) {
731				*p = SLOT_EMPTY;
732				mod |= THISMOD|FSDIRMOD;
733				continue;
734			}
735
736			if (*p == SLOT_EMPTY || *p == SLOT_DELETED) {
737				if (*p == SLOT_EMPTY) {
738					dir->fsckflags |= DIREMPTY;
739					empty = p;
740					empcl = cl;
741				}
742				continue;
743			}
744
745			if (dir->fsckflags & DIREMPTY) {
746				if (!(dir->fsckflags & DIREMPWARN)) {
747					pwarn("%s has entries after end of directory\n",
748					      fullpath(dir));
749					if (ask(1, "Truncate"))
750						dir->fsckflags |= DIREMPWARN;
751					else if (ask(0, "Extend")) {
752						u_char *q;
753
754						dir->fsckflags &= ~DIREMPTY;
755						if (delete(f, boot,
756							   empcl, empty - buffer,
757							   cl, p - buffer, 1) == FSFATAL)
758							return FSFATAL;
759						q = empcl == cl ? empty : buffer;
760						for (; q < p; q += 32)
761							*q = SLOT_DELETED;
762						mod |= THISMOD|FSDIRMOD;
763					}
764				}
765				if (dir->fsckflags & DIREMPWARN) {
766					*p = SLOT_EMPTY;
767					mod |= THISMOD|FSDIRMOD;
768					continue;
769				} else if (dir->fsckflags & DIREMPTY)
770					mod |= FSERROR;
771				empty = NULL;
772			}
773
774            /*
775             * Check long name entries
776             */
777			if (p[11] == ATTR_WIN95) {
778                /* Remember or validate the long name checksum */
779				if (*p & LRFIRST) {
780					if (shortSum != -1) {
781						if (!invlfn) {
782							invlfn = vallfn;
783							invcl = valcl;
784						}
785					}
786					memset(longName, 0, sizeof longName);
787					shortSum = p[13];
788					vallfn = p;
789					valcl = cl;
790				} else if (shortSum != p[13]
791					   || lidx != (*p & LRNOMASK)) {
792					if (!invlfn) {
793						invlfn = vallfn;
794						invcl = valcl;
795					}
796					if (!invlfn) {
797						invlfn = p;
798						invcl = cl;
799					}
800					vallfn = NULL;
801				}
802				lidx = *p & LRNOMASK;
803
804                /*
805                 * Gather the characters from this long name entry
806                 */
807				t = longName + --lidx * 13;
808				for (k = 1; k < 11 && t < longName + sizeof(longName); k += 2) {
809					if (!p[k] && !p[k + 1])
810						break;
811					*t++ = p[k];
812					/*
813					 * Warn about those unusable chars in msdosfs here?	XXX
814					 */
815					if (p[k + 1])
816						t[-1] = '?';
817				}
818				if (k >= 11)
819					for (k = 14; k < 26 && t < longName + sizeof(longName); k += 2) {
820						if (!p[k] && !p[k + 1])
821							break;
822						*t++ = p[k];
823						if (p[k + 1])
824							t[-1] = '?';
825					}
826				if (k >= 26)
827					for (k = 28; k < 32 && t < longName + sizeof(longName); k += 2) {
828						if (!p[k] && !p[k + 1])
829							break;
830						*t++ = p[k];
831						if (p[k + 1])
832							t[-1] = '?';
833					}
834
835				if (t >= longName + sizeof(longName)) {
836					pwarn("long filename too long\n");
837					if (!invlfn) {
838						invlfn = vallfn;
839						invcl = valcl;
840					}
841					vallfn = NULL;
842				}
843				if (p[26] | (p[27] << 8)) {
844					pwarn("long filename record cluster start != 0\n");
845					if (!invlfn) {
846						invlfn = vallfn;
847						invcl = cl;
848					}
849					vallfn = NULL;
850				}
851				continue;	/* long records don't carry further
852						 * information */
853			}
854
855			/*
856			 * This is a standard msdosfs directory entry.
857			 */
858			memset(&dirent, 0, sizeof dirent);
859
860			/*
861			 * it's a short name record, but we need to know
862			 * more, so get the flags first.
863			 */
864			dirent.flags = p[11];
865
866			/*
867             * Gather the base name of the short name (the "8" in "8.3").
868             * Remove any trailing space padding.
869			 */
870			for (j = 0; j < 8; j++)
871				dirent.name[j] = p[j];
872			dirent.name[8] = '\0';
873			for (k = 7; k >= 0 && dirent.name[k] == ' '; k--)
874				dirent.name[k] = '\0';
875			if (dirent.name[k] != '\0')
876				k++;
877			if (dirent.name[0] == SLOT_E5)
878				dirent.name[0] = 0xe5;
879
880			if (dirent.flags & ATTR_VOLUME) {
881				if (vallfn || invlfn) {
882					mod |= msdosfs_removede(f, boot,
883							invlfn ? invlfn : vallfn, p,
884							invlfn ? invcl : valcl, cl, cl,
885							fullpath(dir), 2);
886					vallfn = NULL;
887					invlfn = NULL;
888				}
889				continue;
890			}
891
892            /*
893             * Gather the extension of the short name (if any).
894             */
895			if (p[8] != ' ')
896				dirent.name[k++] = '.';
897			for (j = 0; j < 3; j++)
898				dirent.name[k++] = p[j+8];
899			dirent.name[k] = '\0';
900			for (k--; k >= 0 && dirent.name[k] == ' '; k--)
901				dirent.name[k] = '\0';
902
903            /* If there was a long name, make sure its checksum matches. */
904			if (vallfn && shortSum != calcShortSum(p)) {
905				if (!invlfn) {
906					invlfn = vallfn;
907					invcl = valcl;
908				}
909				vallfn = NULL;
910			}
911
912            /* Get the starting cluster number field(s) */
913			dirent.head = p[26] | (p[27] << 8);
914			if (boot->ClustMask == CLUST32_MASK)
915				dirent.head |= (p[20] << 16) | (p[21] << 24);
916            /* Get the file size */
917			dirent.size = p[28] | (p[29] << 8) | (p[30] << 16) | (p[31] << 24);
918            /* Copy the long name, if there is one */
919			if (vallfn) {
920				strlcpy(dirent.lname, longName, sizeof(dirent.lname));
921				longName[0] = '\0';
922				shortSum = -1;
923			}
924
925			dirent.parent = dir;
926			dirent.next = dir->child;
927
928			if (invlfn) {
929				mod |= k = msdosfs_removede(f, boot,
930						    invlfn, vallfn ? vallfn : p,
931						    invcl, vallfn ? valcl : cl, cl,
932						    fullpath(&dirent), 0);
933				if (mod & FSFATAL)
934					return FSFATAL;
935				if (vallfn
936				    ? (valcl == cl && vallfn != buffer)
937				    : p != buffer)
938					if (k & FSDIRMOD)
939						mod |= THISMOD;
940			}
941
942			vallfn = NULL; /* not used any longer */
943			invlfn = NULL;
944
945			if (!strcmp(dirent.name, ".") || !strcmp(dirent.name,".."))
946			{
947				/*
948				 * Don't do size or in-use checks for "." or ".."
949				 * They'll be checked more below.
950				 */
951				goto MarkedChain;
952			}
953			else
954			{
955				if (dirent.head == CLUST_FREE)
956				{
957					if (dirent.flags & ATTR_DIRECTORY || dirent.size != 0)
958					{
959						pwarn("%s has no clusters\n", fullpath(&dirent));
960						goto remove_or_truncate;
961					}
962				}
963				else
964				{
965					cl_t next;
966
967					if (dirent.head < CLUST_FIRST || dirent.head >= boot->NumClusters)
968					{
969						pwarn("%s starts with cluster out of range (%u)\n",
970								fullpath(&dirent), dirent.head);
971						goto remove_or_truncate;
972					}
973
974					if (isUsed(dirent.head))
975					{
976						pwarn("%s starts with cross-linked cluster (%u)\n",
977								fullpath(&dirent), dirent.head);
978						goto remove_or_truncate;
979					}
980
981					next = fat_get(dirent.head);
982					if (next == CLUST_ERROR)
983						return FSFATAL;
984
985					if (next == CLUST_FREE)
986					{
987						pwarn("%s starts with free cluster\n", fullpath(&dirent));
988						goto remove_or_truncate;
989					}
990
991					if (next >= CLUST_RSRVD && next < CLUST_EOFS)
992					{
993						pwarn("%s starts with cluster marked %s\n",
994							  fullpath(&dirent),
995							  rsrvdcltype(next));
996	remove_or_truncate:
997						if (dirent.flags & ATTR_DIRECTORY) {
998							if (ask(0, "Remove")) {
999								*p = SLOT_DELETED;
1000								mod |= THISMOD|FSDIRMOD;
1001							} else
1002								mod |= FSERROR;
1003							continue;
1004						} else {
1005							if (ask(1, "Truncate")) {
1006								p[28] = p[29] = p[30] = p[31] = 0;
1007								p[26] = p[27] = 0;
1008								if (boot->ClustMask == CLUST32_MASK)
1009									p[20] = p[21] = 0;
1010								dirent.head = 0;
1011								dirent.size = 0;
1012								mod |= THISMOD|FSDIRMOD;
1013							} else
1014								mod |= FSERROR;
1015						}
1016					}
1017				}
1018			}
1019
1020			if (dirent.head >= CLUST_FIRST && dirent.head < boot->NumClusters)
1021			{
1022				mod |= markDosDirChain(boot, &dirent);
1023				if (mod & FSFATAL)
1024					return FSFATAL;
1025			}
1026
1027MarkedChain:
1028			if (dirent.flags & ATTR_DIRECTORY) {
1029				/*
1030				 * gather more info for directories
1031				 */
1032				if (dirent.size) {
1033					pwarn("Directory %s has size != 0\n",
1034					      fullpath(&dirent));
1035					if (ask(1, "Correct")) {
1036						p[28] = p[29] = p[30] = p[31] = 0;
1037						dirent.size = 0;
1038						mod |= THISMOD|FSDIRMOD;
1039					} else
1040						mod |= FSERROR;
1041				}
1042				/*
1043				 * handle `.' and `..' specially
1044				 */
1045				if (strcmp(dirent.name, ".") == 0) {
1046					if (dirent.head != dir->head) {
1047						pwarn("`.' entry in %s has incorrect start cluster\n",
1048						      fullpath(dir));
1049						if (ask(1, "Correct")) {
1050							dirent.head = dir->head;
1051							p[26] = (u_char)dirent.head;
1052							p[27] = (u_char)(dirent.head >> 8);
1053							if (boot->ClustMask == CLUST32_MASK) {
1054								p[20] = (u_char)(dirent.head >> 16);
1055								p[21] = (u_char)(dirent.head >> 24);
1056							}
1057							mod |= THISMOD|FSDIRMOD;
1058						} else
1059							mod |= FSERROR;
1060					}
1061					continue;
1062				}
1063				if (strcmp(dirent.name, "..") == 0) {
1064					if (dir->parent) {		/* XXX */
1065						if (!dir->parent->parent) {
1066							if (dirent.head) {
1067								pwarn("`..' entry in %s has non-zero start cluster\n",
1068								      fullpath(dir));
1069								if (ask(1, "Correct")) {
1070									dirent.head = 0;
1071									p[26] = p[27] = 0;
1072									if (boot->ClustMask == CLUST32_MASK)
1073										p[20] = p[21] = 0;
1074									mod |= THISMOD|FSDIRMOD;
1075								} else
1076									mod |= FSERROR;
1077							}
1078						} else if (dirent.head != dir->parent->head) {
1079							pwarn("`..' entry in %s has incorrect start cluster\n",
1080							      fullpath(dir));
1081							if (ask(1, "Correct")) {
1082								dirent.head = dir->parent->head;
1083								p[26] = (u_char)dirent.head;
1084								p[27] = (u_char)(dirent.head >> 8);
1085								if (boot->ClustMask == CLUST32_MASK) {
1086									p[20] = (u_char)(dirent.head >> 16);
1087									p[21] = (u_char)(dirent.head >> 24);
1088								}
1089								mod |= THISMOD|FSDIRMOD;
1090							} else
1091								mod |= FSERROR;
1092						}
1093					}
1094					continue;
1095				}
1096
1097                /*
1098                 * We've found something that claims to be a subdirectory.
1099                 * Make sure the contents of the first cluster contain "."
1100                 * and ".." entries; if not, assume this is actually a file.
1101                 */
1102                errno_t err = isSubdirectory(f, boot, &dirent);
1103                if (err) {
1104                    if (err == ENOTDIR) {
1105                        pwarn("Item %s does not appear to be a subdirectory\n", fullpath(&dirent));
1106                        if (ask(0, "Correct")) {
1107                            p[11] &= ~ATTR_DIRECTORY;
1108                            dirent.flags &= ~ATTR_DIRECTORY;
1109                            mod |= THISMOD|FSDIRMOD;
1110                            goto check_file;
1111                        } else {
1112                            mod |= FSERROR;
1113                        }
1114                    } else {
1115                        return FSFATAL;
1116                    }
1117                }
1118
1119				/* create directory tree node */
1120				if (!(d = newDosDirEntry())) {
1121					perr("No space for directory");
1122					return FSFATAL;
1123				}
1124				memcpy(d, &dirent, sizeof(struct dosDirEntry));
1125				/* link it into the tree */
1126				dir->child = d;
1127
1128				/* Enter this directory into the todo list */
1129                struct dirTodoNode *n;
1130				if (!(n = newDirTodo())) {
1131					perr("No space for todo list");
1132					return FSFATAL;
1133				}
1134				n->next = pendingDirectories;
1135				n->dir = d;
1136				pendingDirectories = n;
1137			} else {
1138            check_file:
1139				mod |= k = checksize(boot, p, &dirent);
1140				if (k & FSDIRMOD)
1141					mod |= THISMOD;
1142			}
1143			boot->NumFiles++;
1144		}
1145		if (mod & THISMOD) {
1146			if (lseek(f, off, SEEK_SET) != off
1147			    || write(f, buffer, last*32) != last*32) {
1148				perr("Unable to write directory");
1149				return FSFATAL;
1150			}
1151			mod &= ~THISMOD;
1152		}
1153
1154		/*
1155		 * If this is a FAT12 or FAT16 root directory, there is no cluster chain
1156		 * to follow.  In this case, we'll exit the loop with cl==0.
1157		 */
1158		if (!(boot->flags & FAT32) && !dir->parent)
1159			break;
1160
1161	/* What about errors below? */
1162	} while ((cl = fat_get(cl)) >= CLUST_FIRST && cl < boot->NumClusters && cl != dir->end);
1163	if (cl == CLUST_ERROR)
1164		mod |= FSFATAL;
1165	if (invlfn || vallfn)
1166	{
1167		mod |= msdosfs_removede(f, boot,
1168				invlfn ? invlfn : vallfn, p,
1169				invlfn ? invcl : valcl, last_cl, last_cl,
1170				fullpath(dir), 1);
1171		if (lseek(f, off, SEEK_SET) != off
1172			|| write(f, buffer, last*32) != last*32) {
1173			perr("Unable to write directory");
1174			return FSFATAL;
1175		}
1176	}
1177	return mod & ~THISMOD;
1178}
1179
1180int
1181handleDirTree(int dosfs, struct bootblock *boot)
1182{
1183	int mod;
1184
1185	mod = readDosDirSection(dosfs, boot, rootDir);
1186	if (mod & FSFATAL)
1187		return FSFATAL;
1188
1189	/*
1190	 * process the directory todo list
1191	 */
1192	while (pendingDirectories) {
1193		struct dosDirEntry *dir = pendingDirectories->dir;
1194		struct dirTodoNode *n = pendingDirectories->next;
1195
1196		/*
1197		 * remove TODO entry now, the list might change during
1198		 * directory reads
1199		 */
1200		freeDirTodo(pendingDirectories);
1201		pendingDirectories = n;
1202
1203		/*
1204		 * handle subdirectory
1205		 */
1206		mod |= readDosDirSection(dosfs, boot, dir);
1207		if (mod & FSFATAL)
1208			return FSFATAL;
1209	}
1210
1211	return mod;
1212}
1213