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