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