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