xref: /titanic_41/usr/src/cmd/fs.d/pcfs/fsck/dir.c (revision fd9cb95cbb2f626355a60efb9d02c5f0a33c10e6)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright (c) 1999,2000 by Sun Microsystems, Inc.
24  * All rights reserved.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * fsck_pcfs -- routines for manipulating directories.
31  */
32 #include <stdio.h>
33 #include <string.h>
34 #include <unistd.h>
35 #include <stdlib.h>
36 #include <libintl.h>
37 #include <ctype.h>
38 #include <sys/param.h>
39 #include <sys/time.h>
40 #include <sys/dktp/fdisk.h>
41 #include <sys/fs/pc_fs.h>
42 #include <sys/fs/pc_dir.h>
43 #include <sys/fs/pc_label.h>
44 #include "pcfs_common.h"
45 #include "fsck_pcfs.h"
46 
47 extern	int32_t	HiddenClusterCount;
48 extern	int32_t	FileClusterCount;
49 extern	int32_t	DirClusterCount;
50 extern	int32_t	HiddenFileCount;
51 extern	int32_t	LastCluster;
52 extern	int32_t	FileCount;
53 extern	int32_t	BadCount;
54 extern	int32_t	DirCount;
55 extern	int32_t	FATSize;
56 extern	off64_t	PartitionOffset;
57 extern	bpb_t	TheBIOSParameterBlock;
58 extern	int	ReadOnly;
59 extern	int	IsFAT32;
60 extern	int	Verbose;
61 
62 static uchar_t *CHKsList = NULL;
63 
64 ClusterContents	TheRootDir;
65 int32_t	RootDirSize;
66 int	RootDirModified;
67 int	OkayToRelink = 1;
68 
69 /*
70  *  We import these routines from the pc_subr.c file in the kernel.
71  */
72 extern void pc_tvtopct(timestruc_t *, struct pctime *);
73 extern int pc_validchar(char);
74 
75 /*
76  * We have a bunch of routines for handling CHK names.  A CHK name is
77  * simply a file name of the form "FILEnnnn.CHK", where the n's are the
78  * digits in the numbers from 1 to 9999.  There are always four digits
79  * used, leading zeros are added as necessary.
80  *
81  * We use CHK names to link orphaned cluster chains back into the file
82  * system's root directory under an auspicious name so that the user
83  * may be able to recover some of their data.
84  *
85  * We use these routines to ensure CHK names we use don't conflict
86  * with any already present in the file system.
87  */
88 static int
89 hasCHKName(struct pcdir *dp)
90 {
91 	return (dp->pcd_filename[CHKNAME_F] == 'F' &&
92 	    dp->pcd_filename[CHKNAME_I] == 'I' &&
93 	    dp->pcd_filename[CHKNAME_L] == 'L' &&
94 	    dp->pcd_filename[CHKNAME_E] == 'E' &&
95 	    isdigit(dp->pcd_filename[CHKNAME_THOUSANDS]) &&
96 	    isdigit(dp->pcd_filename[CHKNAME_HUNDREDS]) &&
97 	    isdigit(dp->pcd_filename[CHKNAME_TENS]) &&
98 	    isdigit(dp->pcd_filename[CHKNAME_ONES]) &&
99 	    dp->pcd_ext[CHKNAME_C] == 'C' &&
100 	    dp->pcd_ext[CHKNAME_H] == 'H' &&
101 	    dp->pcd_ext[CHKNAME_K] == 'K');
102 }
103 
104 void
105 addEntryToCHKList(int chkNumber)
106 {
107 	/* silent failure on bogus value */
108 	if (chkNumber < 0 || chkNumber > MAXCHKVAL)
109 		return;
110 	CHKsList[chkNumber / NBBY] |= (1 << (chkNumber % NBBY));
111 }
112 
113 static void
114 addToCHKList(struct pcdir *dp)
115 {
116 	int chknum;
117 
118 	chknum = 1000 * (dp->pcd_filename[CHKNAME_THOUSANDS] - '0');
119 	chknum += 100 * (dp->pcd_filename[CHKNAME_HUNDREDS] - '0');
120 	chknum += 10 * (dp->pcd_filename[CHKNAME_TENS] - '0');
121 	chknum += (dp->pcd_filename[CHKNAME_ONES] - '0');
122 	addEntryToCHKList(chknum);
123 }
124 
125 static int
126 inUseCHKName(int chkNumber)
127 {
128 	return (CHKsList[chkNumber / NBBY] & (1 << (chkNumber % NBBY)));
129 }
130 
131 static void
132 appendToPath(struct pcdir *dp, char *thePath, int *theLen)
133 {
134 	int i = 0;
135 
136 	/*
137 	 * Sometimes caller doesn't care about keeping track of the path
138 	 */
139 	if (thePath == NULL)
140 		return;
141 
142 	/*
143 	 *  Prepend /
144 	 */
145 	if (*theLen < MAXPATHLEN)
146 		*(thePath + (*theLen)++) = '/';
147 	/*
148 	 *  Print out the file name part, but only up to the first
149 	 *  space.
150 	 */
151 	while (*theLen < MAXPATHLEN && i < PCFNAMESIZE) {
152 		/*
153 		 *  When we start seeing spaces we assume that's the
154 		 *  end of the interesting characters in the name.
155 		 */
156 		if ((dp->pcd_filename[i] == ' ') ||
157 		    !(pc_validchar(dp->pcd_filename[i])))
158 			break;
159 		*(thePath + (*theLen)++) = dp->pcd_filename[i++];
160 	}
161 	/*
162 	 *  Leave now, if we don't have an extension (or room for one)
163 	 */
164 	if ((dp->pcd_ext[i] == ' ') || ((*theLen) >= MAXPATHLEN) ||
165 	    (!(pc_validchar(dp->pcd_ext[i]))))
166 		return;
167 	/*
168 	 *  Tack on the extension
169 	 */
170 	*(thePath + (*theLen)++) = '.';
171 	i = 0;
172 	while ((*theLen < MAXPATHLEN) && (i < PCFEXTSIZE)) {
173 		if ((dp->pcd_ext[i] == ' ') || !(pc_validchar(dp->pcd_ext[i])))
174 			break;
175 		*(thePath + (*theLen)++) = dp->pcd_ext[i++];
176 	}
177 }
178 
179 static void
180 printName(FILE *outDest, struct pcdir *dp)
181 {
182 	int i;
183 	for (i = 0; i < PCFNAMESIZE; i++) {
184 		if ((dp->pcd_filename[i] == ' ') ||
185 		    !(pc_validchar(dp->pcd_filename[i])))
186 			break;
187 		(void) fprintf(outDest, "%c", dp->pcd_filename[i]);
188 	}
189 	(void) fprintf(outDest, ".");
190 	for (i = 0; i < PCFEXTSIZE; i++) {
191 		if (!(pc_validchar(dp->pcd_ext[i])))
192 			break;
193 		(void) fprintf(outDest, "%c", dp->pcd_ext[i]);
194 	}
195 }
196 
197 /*
198  *  sanityCheckSize
199  *	Make sure the size in the directory entry matches what is
200  *	actually allocated.  If there is a mismatch, orphan all
201  *	the allocated clusters.  Returns SIZE_MATCHED if everything matches
202  *	up, TRUNCATED to indicate truncation was necessary.
203  */
204 static int
205 sanityCheckSize(int fd, struct pcdir *dp, int32_t actualClusterCount,
206     int isDir, int32_t startCluster, struct nameinfo *fullPathName,
207     struct pcdir **orphanEntry)
208 {
209 	uint32_t sizeFromDir;
210 	int32_t ignorei = 0;
211 	int64_t bpc;
212 
213 	bpc = TheBIOSParameterBlock.bpb.sectors_per_cluster *
214 	    TheBIOSParameterBlock.bpb.bytes_per_sector;
215 	sizeFromDir = extractSize(dp);
216 	if (isDir) {
217 		if (sizeFromDir == 0)
218 			return (SIZE_MATCHED);
219 	} else {
220 		if ((sizeFromDir > ((actualClusterCount - 1) * bpc)) &&
221 		    (sizeFromDir <= (actualClusterCount * bpc)))
222 			return (SIZE_MATCHED);
223 	}
224 	if (fullPathName != NULL) {
225 		fullPathName->references++;
226 		(void) fprintf(stderr, "%s\n", fullPathName->fullName);
227 	}
228 	squirrelPath(fullPathName, startCluster);
229 	(void) fprintf(stderr,
230 	    gettext("Truncating chain due to incorrect size "
231 	    "in directory.  Size from directory = %u bytes,\n"), sizeFromDir);
232 	if (actualClusterCount == 0) {
233 		(void) fprintf(stderr,
234 		    gettext("Zero bytes are allocated to the file.\n"));
235 	} else {
236 		(void) fprintf(stderr,
237 		    gettext("Allocated size in range %llu - %llu bytes.\n"),
238 		    ((actualClusterCount - 1) * bpc) + 1,
239 		    (actualClusterCount * bpc));
240 	}
241 	/*
242 	 * Use splitChain() to make an orphan that is the entire allocation
243 	 * chain.
244 	 */
245 	splitChain(fd, dp, startCluster, orphanEntry, &ignorei);
246 	return (TRUNCATED);
247 }
248 
249 static int
250 noteUsage(int fd, int32_t startAt, struct pcdir *dp, struct pcdir *lp,
251     int32_t longEntryStartCluster, int isHidden, int isDir,
252     struct nameinfo *fullPathName)
253 {
254 	struct pcdir *orphanEntry;
255 	int32_t chain = startAt;
256 	int32_t count = 0;
257 	int savePathNextIteration = 0;
258 	int haveBad = 0;
259 	ClusterInfo *tmpl = NULL;
260 
261 	while ((chain >= FIRST_CLUSTER) && (chain <= LastCluster)) {
262 		if ((markInUse(fd, chain, dp, lp, longEntryStartCluster,
263 		    isHidden ? HIDDEN : VISIBLE, &tmpl))
264 			!= CLINFO_NEWLY_ALLOCED)
265 			break;
266 		count++;
267 		if (savePathNextIteration == 1) {
268 			savePathNextIteration = 0;
269 			if (fullPathName != NULL)
270 				fullPathName->references++;
271 			squirrelPath(fullPathName, chain);
272 		}
273 		if (isMarkedBad(chain)) {
274 			haveBad = 1;
275 			savePathNextIteration = 1;
276 		}
277 		if (isHidden)
278 			HiddenClusterCount++;
279 		else if (isDir)
280 			DirClusterCount++;
281 		else
282 			FileClusterCount++;
283 		chain = nextInChain(chain);
284 	}
285 	/*
286 	 * Do a sanity check on the file size in the directory entry.
287 	 * This may create an orphaned cluster chain.
288 	 */
289 	if (sanityCheckSize(fd, dp, count, isDir, startAt,
290 	    fullPathName, &orphanEntry) == TRUNCATED) {
291 		/*
292 		 * The pre-existing directory entry has been truncated,
293 		 * so the chain associated with it no longer has any
294 		 * bad clusters.  Instead, the new orphan has them.
295 		 */
296 		if (haveBad > 0) {
297 			truncChainWithBadCluster(fd, orphanEntry, startAt);
298 		}
299 		haveBad = 0;
300 	}
301 	return (haveBad);
302 }
303 
304 static void
305 storeInfoAboutEntry(int fd, struct pcdir *dp, struct pcdir *ldp, int depth,
306     int32_t longEntryStartCluster, char *fullPath, int *fullLen)
307 {
308 	struct nameinfo *pathCopy;
309 	int32_t start;
310 	int haveBad;
311 	int hidden = (dp->pcd_attr & PCA_HIDDEN || dp->pcd_attr & PCA_SYSTEM);
312 	int dir = (dp->pcd_attr & PCA_DIR);
313 	int i;
314 
315 	if (hidden)
316 		HiddenFileCount++;
317 	else if (dir)
318 		DirCount++;
319 	else
320 		FileCount++;
321 	appendToPath(dp, fullPath, fullLen);
322 
323 	/*
324 	 * Make a copy of the name at this point.  We may want it to
325 	 * note the original source of an orphaned cluster.
326 	 */
327 	if ((pathCopy =
328 	    (struct nameinfo *)malloc(sizeof (struct nameinfo))) != NULL) {
329 		if ((pathCopy->fullName =
330 		    (char *)malloc(*fullLen + 1)) != NULL) {
331 			pathCopy->references = 0;
332 			(void) strncpy(pathCopy->fullName, fullPath, *fullLen);
333 			pathCopy->fullName[*fullLen] = '\0';
334 		} else {
335 			free(pathCopy);
336 			pathCopy = NULL;
337 		}
338 	}
339 	if (Verbose) {
340 		for (i = 0; i < depth; i++)
341 			(void) fprintf(stderr, "  ");
342 		if (hidden)
343 			(void) fprintf(stderr, "[");
344 		else if (dir)
345 			(void) fprintf(stderr, "|_");
346 		else
347 			(void) fprintf(stderr, gettext("(%06d) "), FileCount);
348 		printName(stderr, dp);
349 		if (hidden)
350 			(void) fprintf(stderr, "]");
351 		(void) fprintf(stderr,
352 		    gettext(", %u bytes, start cluster %d"),
353 		    extractSize(dp), extractStartCluster(dp));
354 		(void) fprintf(stderr, "\n");
355 	}
356 	start = extractStartCluster(dp);
357 	haveBad = noteUsage(fd, start, dp, ldp, longEntryStartCluster,
358 	    hidden, dir, pathCopy);
359 	if (haveBad > 0) {
360 		if (dir && pathCopy->fullName != NULL) {
361 			(void) fprintf(stderr,
362 			    gettext("Adjusting for bad allocation units in "
363 			    "the meta-data of:\n  "));
364 			(void) fprintf(stderr, pathCopy->fullName);
365 			(void) fprintf(stderr, "\n");
366 		}
367 		truncChainWithBadCluster(fd, dp, start);
368 	}
369 	if ((pathCopy != NULL) && (pathCopy->references == 0)) {
370 		free(pathCopy->fullName);
371 		free(pathCopy);
372 	}
373 }
374 
375 static void
376 storeInfoAboutLabel(struct pcdir *dp)
377 {
378 	/*
379 	 * XXX eventually depth should be passed to this routine just
380 	 * as it is with storeInfoAboutEntry().  If it isn't zero, then
381 	 * we've got a bogus directory entry.
382 	 */
383 	if (Verbose) {
384 		(void) fprintf(stderr, gettext("** "));
385 		printName(stderr, dp);
386 		(void) fprintf(stderr, gettext(" **\n"));
387 	}
388 }
389 
390 static void
391 searchChecks(struct pcdir *dp, int operation, char matchRequired,
392     struct pcdir **found)
393 {
394 	/*
395 	 *  We support these searching operations:
396 	 *
397 	 *  PCFS_FIND_ATTR
398 	 *	look for the first file with a certain attribute
399 	 *	(e.g, find all hidden files)
400 	 *  PCFS_FIND_STATUS
401 	 *	look for the first file with a certain status
402 	 *	(e.g., the file has been marked deleted; making
403 	 *	its directory entry reusable)
404 	 *  PCFS_FIND_CHKS
405 	 *	look for all files with short names of the form
406 	 *	FILENNNN.CHK.  These are the file names we give
407 	 *	to chains of orphaned clusters we relink into the
408 	 *	file system.  This find facility allows us to seek
409 	 *	out all existing files of this naming form so that
410 	 *	we may create unique file names for new orphans.
411 	 */
412 	if (operation == PCFS_FIND_ATTR && dp->pcd_attr == matchRequired) {
413 		*found = dp;
414 	} else if (operation == PCFS_FIND_STATUS &&
415 	    dp->pcd_filename[0] == matchRequired) {
416 		*found = dp;
417 	} else if (operation == PCFS_FIND_CHKS && hasCHKName(dp)) {
418 		addToCHKList(dp);
419 	}
420 }
421 
422 static void
423 catalogEntry(int fd, struct pcdir *dp, struct pcdir *longdp,
424     int32_t currentCluster, int depth, char *recordPath, int *pathLen)
425 {
426 	if (dp->pcd_attr & PCA_LABEL) {
427 		storeInfoAboutLabel(dp);
428 	} else {
429 		storeInfoAboutEntry(fd, dp, longdp, depth, currentCluster,
430 		    recordPath, pathLen);
431 	}
432 }
433 
434 /*
435  * visitNodes()
436  *
437  * This is the main workhouse routine for traversing pcfs metadata.
438  * There isn't a lot to the metadata.  Basically there is a root
439  * directory somewhere (either in its own special place outside the
440  * data area or in a data cluster).  The root directory (and all other
441  * directories) are filled with a number of fixed size entries.  An
442  * entry has the filename and extension, the file's attributes, the
443  * file's size, and the starting data cluster of the storage allocated
444  * to the file.  To determine which clusters are assigned to the file,
445  * you start at the starting cluster entry in the FAT, and follow the
446  * chain of entries in the FAT.
447  *
448  *	Arguments are:
449  *	fd
450  *		descriptor for accessing the raw file system data
451  *	currentCluster
452  *		original caller supplies the initial starting cluster,
453  *		subsequent recursive calls are made with updated
454  *		cluster numbers for the sub-directories.
455  *	dirData
456  *		pointer to the directory data bytes
457  *	dirDataLen
458  *		size of the whole buffer of data bytes (usually it is
459  *		the size of a cluster, but the root directory on
460  *		FAT12/16 is not necessarily the same size as a cluster).
461  *	depth
462  *		original caller should set it to zero (assuming they are
463  *		starting from the root directory).  This number is used to
464  *		change the indentation of file names presented as debug info.
465  *	descend
466  *		boolean indicates if we should descend into subdirectories.
467  *	operation
468  *		what, if any, matching should be performed.
469  *		The PCFS_TRAVERSE_ALL operation is a depth first traversal
470  *		of all nodes in the metadata tree, that tracks all the
471  *		clusters in use (according to the meta-data, at least)
472  *	matchRequired
473  *		value to be matched (if any)
474  *	found
475  *		output parameter
476  *		used to return pointer to a directory entry that matches
477  *		the search requirement
478  *		original caller should pass in a pointer to a NULL pointer.
479  *	lastDirCluster
480  *		output parameter
481  *		if no match found, last cluster num of starting directory
482  *	dirEnd
483  *		output parameter
484  *		if no match found, return parameter stores pointer to where
485  *		new directory entry could be appended to existing directory
486  *	recordPath
487  *		output parameter
488  *		as files are discovered, and directories traversed, this
489  *		buffer is used to store the current full path name.
490  *	pathLen
491  *		output parameter
492  *		this is in the integer length of the current full path name.
493  */
494 static void
495 visitNodes(int fd, int32_t currentCluster, ClusterContents *dirData,
496     int32_t dirDataLen, int depth, int descend, int operation,
497     char matchRequired,  struct pcdir **found, int32_t *lastDirCluster,
498     struct pcdir **dirEnd, char *recordPath, int *pathLen)
499 {
500 	struct pcdir *longdp = NULL;
501 	struct pcdir *dp;
502 	int32_t longStart;
503 	int withinLongName = 0;
504 	int saveLen = *pathLen;
505 
506 	dp = dirData->dirp;
507 
508 	/*
509 	 *  A directory entry where the first character of the name is
510 	 *  PCD_UNUSED indicates the end of the directory.
511 	 */
512 	while ((uchar_t *)dp < dirData->bytes + dirDataLen &&
513 	    dp->pcd_filename[0] != PCD_UNUSED) {
514 		/*
515 		 *  Handle the special case find operations.
516 		 */
517 		searchChecks(dp, operation, matchRequired, found);
518 		if (*found)
519 			break;
520 		/*
521 		 * Are we looking at part of a long file name entry?
522 		 * If so, we may need to note the start of the name.
523 		 * We don't do any further processing of long file
524 		 * name entries.
525 		 *
526 		 * We also skip deleted entries and the '.' and '..'
527 		 * entries.
528 		 */
529 		if ((dp->pcd_attr & PCDL_LFN_BITS) == PCDL_LFN_BITS) {
530 			if (!withinLongName) {
531 				withinLongName++;
532 				longStart = currentCluster;
533 				longdp = dp;
534 			}
535 			dp++;
536 			continue;
537 		} else if ((dp->pcd_filename[0] == PCD_ERASED) ||
538 		    (dp->pcd_filename[0] == '.')) {
539 			/*
540 			 * XXX - if we were within a long name, then
541 			 * its existence is bogus, because it is not
542 			 * attached to any real file.
543 			 */
544 			withinLongName = 0;
545 			dp++;
546 			continue;
547 		}
548 		withinLongName = 0;
549 		if (operation == PCFS_TRAVERSE_ALL)
550 			catalogEntry(fd, dp, longdp, longStart, depth,
551 			    recordPath, pathLen);
552 		longdp = NULL;
553 		longStart = 0;
554 		if (dp->pcd_attr & PCA_DIR && descend == PCFS_VISIT_SUBDIRS) {
555 			traverseDir(fd, extractStartCluster(dp), depth + 1,
556 			    descend, operation, matchRequired, found,
557 			    lastDirCluster, dirEnd, recordPath, pathLen);
558 			if (*found)
559 				break;
560 		}
561 		dp++;
562 		*pathLen = saveLen;
563 	}
564 	if (*found)
565 		return;
566 	if ((uchar_t *)dp < dirData->bytes + dirDataLen) {
567 		/*
568 		 * We reached the end of directory before the end of
569 		 * our provided data (a cluster).  That means this cluster
570 		 * is the last one in this directory's chain.  It also
571 		 * means we've just looked at the last directory entry.
572 		 */
573 		*lastDirCluster = currentCluster;
574 		*dirEnd = dp;
575 		return;
576 	}
577 	/*
578 	 * If there is more to the directory we'll go get it otherwise we
579 	 * are done traversing this directory.
580 	 */
581 	if ((currentCluster == FAKE_ROOTDIR_CLUST) ||
582 	    (lastInFAT(currentCluster))) {
583 		*lastDirCluster = currentCluster;
584 		return;
585 	} else {
586 		traverseDir(fd, nextInChain(currentCluster),
587 		    depth, descend, operation, matchRequired,
588 		    found, lastDirCluster, dirEnd, recordPath, pathLen);
589 		*pathLen = saveLen;
590 	}
591 }
592 
593 /*
594  *  traverseFromRoot()
595  *	For use with 12 and 16 bit FATs that have a root directory outside
596  *	of the file system.  This is a general purpose routine that
597  *	can be used simply to visit all of the nodes in the metadata or
598  *	to find the first instance of something, e.g., the first directory
599  *	entry where the file is marked deleted.
600  *
601  *	Inputs are described in the commentary for visitNodes() above.
602  */
603 void
604 traverseFromRoot(int fd, int depth, int descend, int operation,
605     char matchRequired,  struct pcdir **found, int32_t *lastDirCluster,
606     struct pcdir **dirEnd, char *recordPath, int *pathLen)
607 {
608 	visitNodes(fd, FAKE_ROOTDIR_CLUST, &TheRootDir, RootDirSize, depth,
609 	    descend, operation, matchRequired, found, lastDirCluster, dirEnd,
610 	    recordPath, pathLen);
611 }
612 
613 /*
614  *  traverseDir()
615  *	For use with all FATs outside of the initial root directory on
616  *	12 and 16 bit FAT file systems.  This is a general purpose routine
617  *	that can be used simply to visit all of the nodes in the metadata or
618  *	to find the first instance of something, e.g., the first directory
619  *	entry where the file is marked deleted.
620  *
621  *	Unique Input is:
622  *	startAt
623  *		starting cluster of the directory
624  *
625  *	This is the cluster that is the first one in this directory.
626  *	We read it right away, so we can provide it as data to visitNodes().
627  *	Note that we cache this cluster as we read it, because it is
628  *	metadata and we cache all metadata.  By doing so, we can
629  *	keep pointers to directory entries for quickly moving around and
630  *	fixing up any problems we find.  Of course if we get a big
631  *	filesystem with a huge amount of metadata we may be hosed, as
632  *	we'll likely run out of memory.
633  *
634  *	I believe in the future this will have to be addressed.  It
635  *	may be possible to do more of the processing of problems
636  *	within directories as they are cached, so that when memory
637  *	runs short we can free cached directories we are already
638  *	finished visiting.
639  *
640  *	The remainder of inputs are described in visitNodes() comments.
641  */
642 void
643 traverseDir(int fd, int32_t startAt, int depth, int descend, int operation,
644     char matchRequired,  struct pcdir **found, int32_t *lastDirCluster,
645     struct pcdir **dirEnd, char *recordPath, int *pathLen)
646 {
647 	ClusterContents dirdata;
648 	int32_t dirdatasize = 0;
649 
650 	if (startAt < FIRST_CLUSTER || startAt > LastCluster)
651 		return;
652 
653 	if (readCluster(fd, startAt, &(dirdata.bytes), &dirdatasize,
654 	    RDCLUST_DO_CACHE) != RDCLUST_GOOD) {
655 		(void) fprintf(stderr,
656 		    gettext("Unable to get more directory entries!\n"));
657 		return;
658 	}
659 
660 	if (operation == PCFS_TRAVERSE_ALL) {
661 		if (Verbose)
662 			(void) fprintf(stderr,
663 			    gettext("Directory traversal enters "
664 			    "allocation unit %d.\n"), startAt);
665 	}
666 	visitNodes(fd, startAt, &dirdata, dirdatasize, depth, descend,
667 	    operation, matchRequired, found, lastDirCluster, dirEnd,
668 	    recordPath, pathLen);
669 }
670 
671 void
672 createCHKNameList(int fd)
673 {
674 	struct pcdir *ignorep1, *ignorep2;
675 	int32_t ignore32;
676 	char *ignorecp = NULL;
677 	char ignore = '\0';
678 	int ignoreint = 0;
679 
680 	ignorep1 = ignorep2 = NULL;
681 	if (!OkayToRelink || CHKsList != NULL)
682 		return;
683 
684 	/*
685 	 *  Allocate an array to keep a bit map of the integer
686 	 *  values used in CHK names.
687 	 */
688 	if ((CHKsList =
689 	    (uchar_t *)calloc(1, idivceil(MAXCHKVAL, NBBY))) == NULL) {
690 		OkayToRelink = 0;
691 		return;
692 	}
693 
694 	/*
695 	 *  Search the root directory for all the files with names of
696 	 *  the form FILEXXXX.CHK.  The root directory is an area
697 	 *  outside of the file space on FAT12 and FAT16 file systems.
698 	 *  On FAT32 file systems, the root directory is in a file
699 	 *  area cluster just like any other directory.
700 	 */
701 	if (!IsFAT32) {
702 		traverseFromRoot(fd, 0, PCFS_NO_SUBDIRS, PCFS_FIND_CHKS,
703 		    ignore, &ignorep1, &ignore32, &ignorep2, ignorecp,
704 		    &ignoreint);
705 	} else {
706 		DirCount++;
707 		traverseDir(fd, TheBIOSParameterBlock.bpb32.root_dir_clust,
708 		    0, PCFS_NO_SUBDIRS, PCFS_FIND_CHKS, ignore,
709 		    &ignorep1, &ignore32, &ignorep2, ignorecp, &ignoreint);
710 	}
711 }
712 
713 
714 char *
715 nextAvailableCHKName(int *chosen)
716 {
717 	static char nameBuf[PCFNAMESIZE];
718 	int i;
719 
720 	if (!OkayToRelink)
721 		return (NULL);
722 
723 	nameBuf[CHKNAME_F] = 'F';
724 	nameBuf[CHKNAME_I] = 'I';
725 	nameBuf[CHKNAME_L] = 'L';
726 	nameBuf[CHKNAME_E] = 'E';
727 
728 	for (i = 1; i <= MAXCHKVAL; i++) {
729 		if (!inUseCHKName(i))
730 			break;
731 	}
732 	if (i <= MAXCHKVAL) {
733 		nameBuf[CHKNAME_THOUSANDS] = '0' + (i / 1000);
734 		nameBuf[CHKNAME_HUNDREDS] = '0' + ((i % 1000) / 100);
735 		nameBuf[CHKNAME_TENS] = '0' + ((i % 100) / 10);
736 		nameBuf[CHKNAME_ONES] = '0' + (i % 10);
737 		*chosen = i;
738 		return (nameBuf);
739 	} else {
740 		(void) fprintf(stderr,
741 		    gettext("Sorry, no names available for "
742 		    "relinking orphan chains!\n"));
743 		OkayToRelink = 0;
744 		return (NULL);
745 	}
746 }
747 
748 uint32_t
749 extractSize(struct pcdir *dp)
750 {
751 	uint32_t returnMe;
752 
753 	read_32_bits((uchar_t *)&(dp->pcd_size), &returnMe);
754 	return (returnMe);
755 }
756 
757 int32_t
758 extractStartCluster(struct pcdir *dp)
759 {
760 	uint32_t lo, hi;
761 
762 	if (IsFAT32) {
763 		read_16_bits((uchar_t *)&(dp->un.pcd_scluster_hi), &hi);
764 		read_16_bits((uchar_t *)&(dp->pcd_scluster_lo), &lo);
765 		return ((int32_t)((hi << 16) | lo));
766 	} else {
767 		read_16_bits((uchar_t *)&(dp->pcd_scluster_lo), &lo);
768 		return ((int32_t)lo);
769 	}
770 }
771 
772 static struct pcdir *
773 findAvailableRootDirEntSlot(int fd, int32_t *clusterWithSlot)
774 {
775 	struct pcdir *deletedEntry = NULL;
776 	struct pcdir *appendPoint = NULL;
777 	char *ignorecp = NULL;
778 	int ignore = 0;
779 
780 	*clusterWithSlot = 0;
781 
782 	/*
783 	 *  First off, try to find an erased entry in the root
784 	 *  directory.  The root directory is an area outside of the
785 	 *  file space on FAT12 and FAT16 file systems.  On FAT32 file
786 	 *  systems, the root directory is in a file area cluster just
787 	 *  like any other directory.
788 	 */
789 	if (!IsFAT32) {
790 		traverseFromRoot(fd, 0, PCFS_NO_SUBDIRS, PCFS_FIND_STATUS,
791 		    PCD_ERASED, &deletedEntry, clusterWithSlot,
792 		    &appendPoint, ignorecp, &ignore);
793 	} else {
794 		DirCount++;
795 		traverseDir(fd, TheBIOSParameterBlock.bpb32.root_dir_clust,
796 		    0, PCFS_NO_SUBDIRS, PCFS_FIND_STATUS, PCD_ERASED,
797 		    &deletedEntry, clusterWithSlot, &appendPoint, ignorecp,
798 		    &ignore);
799 	}
800 	/*
801 	 *  If we found a deleted file in the directory we'll overwrite
802 	 *  that entry.
803 	 */
804 	if (deletedEntry)
805 		return (deletedEntry);
806 	/*
807 	 *  If there is room at the end of the existing directory, we
808 	 *  should place the new entry there.
809 	 */
810 	if (appendPoint)
811 		return (appendPoint);
812 	/*
813 	 *  XXX need to grow the directory
814 	 */
815 	return (NULL);
816 }
817 
818 static void
819 insertDirEnt(struct pcdir *slot, struct pcdir *entry, int32_t clusterWithSlot)
820 {
821 	(void) memcpy(slot, entry, sizeof (struct pcdir));
822 	markClusterModified(clusterWithSlot);
823 }
824 
825 static void
826 getNow(timestruc_t *ts)
827 {
828 	struct timeval	tv;
829 
830 	if (gettimeofday(&tv, NULL) == 0) {
831 		ts->tv_sec = tv.tv_sec;
832 		ts->tv_nsec = tv.tv_usec * 1000;
833 	} else {
834 		/* Failed to get time, set create time to the Solaris epoch */
835 		ts->tv_sec = 0;
836 		ts->tv_nsec = 0;
837 	}
838 }
839 
840 /*
841  *  FAT file systems store the following time information in a directory
842  *  entry:
843  *		creation time
844  *		creation date
845  *		last access date
846  *		last modify time
847  *		last modify date
848  *
849  *  No access time is kept.
850  */
851 static void
852 updateDirEnt_CreatTime(struct pcdir *dp)
853 {
854 	timestruc_t	ts;
855 
856 	getNow(&ts);
857 	pc_tvtopct(&ts, &(dp->pcd_crtime)); /* sets creation time/date */
858 	dp->pcd_crtime_msec = 0;
859 	markClusterModified(findImpactedCluster(dp));
860 }
861 
862 static void
863 updateDirEnt_ModTimes(struct pcdir *dp)
864 {
865 	timestruc_t	ts;
866 
867 	getNow(&ts);
868 	pc_tvtopct(&ts, &(dp->pcd_mtime)); /* sets modification time/date */
869 	dp->pcd_ladate = dp->pcd_mtime.pct_date; /* sets access date */
870 	dp->pcd_attr |= PCA_ARCH;
871 	markClusterModified(findImpactedCluster(dp));
872 }
873 
874 struct pcdir *
875 addRootDirEnt(int fd, struct pcdir *new)
876 {
877 	struct pcdir *added;
878 	int32_t inCluster;
879 
880 	if ((added = findAvailableRootDirEntSlot(fd, &inCluster)) != NULL) {
881 		insertDirEnt(added, new, inCluster);
882 		return (added);
883 	}
884 	return (NULL);
885 }
886 
887 /*
888  *  FAT12 and FAT16 have a root directory outside the normal file space,
889  *  so we have separate routines for finding and reading the root directory.
890  */
891 static off64_t
892 seekRootDirectory(int fd)
893 {
894 	off64_t seekto;
895 
896 	/*
897 	 *  The RootDir immediately follows the FATs, which in
898 	 *  turn immediately follow the reserved sectors.
899 	 */
900 	seekto = (off64_t)TheBIOSParameterBlock.bpb.resv_sectors *
901 		    TheBIOSParameterBlock.bpb.bytes_per_sector +
902 		    (off64_t)FATSize * TheBIOSParameterBlock.bpb.num_fats +
903 		    (off64_t)PartitionOffset;
904 	if (Verbose)
905 		(void) fprintf(stderr,
906 		    gettext("Seeking root directory @%lld.\n"), seekto);
907 	return (lseek64(fd, seekto, SEEK_SET));
908 }
909 
910 void
911 getRootDirectory(int fd)
912 {
913 	ssize_t bytesRead;
914 
915 	if (TheRootDir.bytes != NULL)
916 		return;
917 	else if ((TheRootDir.bytes = (uchar_t *)malloc(RootDirSize)) == NULL) {
918 		mountSanityCheckFails();
919 		perror(gettext("No memory for a copy of the root directory"));
920 		(void) close(fd);
921 		exit(8);
922 	}
923 
924 	if (seekRootDirectory(fd) < 0) {
925 		mountSanityCheckFails();
926 		perror(gettext("Cannot seek to RootDir"));
927 		(void) close(fd);
928 		exit(8);
929 	}
930 
931 	if (Verbose)
932 		(void) fprintf(stderr,
933 		    gettext("Reading root directory.\n"));
934 	if ((bytesRead = read(fd, TheRootDir.bytes, RootDirSize)) !=
935 	    RootDirSize) {
936 		mountSanityCheckFails();
937 		if (bytesRead < 0) {
938 			perror(gettext("Cannot read a RootDir"));
939 		} else {
940 			(void) fprintf(stderr,
941 			    gettext("Short read of RootDir\n"));
942 		}
943 		(void) close(fd);
944 		exit(8);
945 	}
946 	if (Verbose) {
947 		(void) fprintf(stderr,
948 		    gettext("Dump of root dir's first 256 bytes.\n"));
949 		header_for_dump();
950 		dump_bytes(TheRootDir.bytes, 256);
951 	}
952 }
953 
954 void
955 writeRootDirMods(int fd)
956 {
957 	ssize_t bytesWritten;
958 
959 	if (!TheRootDir.bytes) {
960 		(void) fprintf(stderr,
961 		    gettext("Internal error: No Root directory to write\n"));
962 		(void) close(fd);
963 		exit(12);
964 	}
965 	if (!RootDirModified) {
966 		if (Verbose) {
967 			(void) fprintf(stderr,
968 			    gettext("No root directory changes need to "
969 			    "be written.\n"));
970 		}
971 		return;
972 	}
973 	if (ReadOnly)
974 		return;
975 	if (Verbose)
976 		(void) fprintf(stderr,
977 		    gettext("Writing root directory.\n"));
978 	if (seekRootDirectory(fd) < 0) {
979 		perror(gettext("Cannot write the RootDir (seek failed)"));
980 		(void) close(fd);
981 		exit(12);
982 	}
983 	if ((bytesWritten = write(fd, TheRootDir.bytes, RootDirSize)) !=
984 	    RootDirSize) {
985 		if (bytesWritten < 0) {
986 			perror(gettext("Cannot write the RootDir"));
987 		} else {
988 			(void) fprintf(stderr,
989 			    gettext("Short write of root directory\n"));
990 		}
991 		(void) close(fd);
992 		exit(12);
993 	}
994 	RootDirModified = 0;
995 }
996 
997 struct pcdir *
998 newDirEnt(struct pcdir *copyme)
999 {
1000 	struct pcdir *ndp;
1001 
1002 	if ((ndp = (struct pcdir *)calloc(1, sizeof (struct pcdir))) == NULL) {
1003 		(void) fprintf(stderr, gettext("Out of memory to create a "
1004 		    "new directory entry!\n"));
1005 		return (ndp);
1006 	}
1007 	if (copyme)
1008 		(void) memcpy(ndp, copyme, sizeof (struct pcdir));
1009 	ndp->pcd_ext[CHKNAME_C] = 'C';
1010 	ndp->pcd_ext[CHKNAME_H] = 'H';
1011 	ndp->pcd_ext[CHKNAME_K] = 'K';
1012 	updateDirEnt_CreatTime(ndp);
1013 	updateDirEnt_ModTimes(ndp);
1014 	return (ndp);
1015 }
1016 
1017 void
1018 updateDirEnt_Size(struct pcdir *dp, uint32_t newSize)
1019 {
1020 	uchar_t *p = (uchar_t *)&(dp->pcd_size);
1021 	store_32_bits(&p, newSize);
1022 	markClusterModified(findImpactedCluster(dp));
1023 }
1024 
1025 void
1026 updateDirEnt_Start(struct pcdir *dp, int32_t newStart)
1027 {
1028 	uchar_t *p = (uchar_t *)&(dp->pcd_scluster_lo);
1029 	store_16_bits(&p, newStart & 0xffff);
1030 	if (IsFAT32) {
1031 		p = (uchar_t *)&(dp->un.pcd_scluster_hi);
1032 		store_16_bits(&p, newStart >> 16);
1033 	}
1034 	markClusterModified(findImpactedCluster(dp));
1035 }
1036 
1037 void
1038 updateDirEnt_Name(struct pcdir *dp, char *newName)
1039 {
1040 	int i;
1041 
1042 	for (i = 0; i < PCFNAMESIZE; i++) {
1043 		if (*newName)
1044 			dp->pcd_filename[i] = *newName++;
1045 		else
1046 			dp->pcd_filename[i] = ' ';
1047 	}
1048 	markClusterModified(findImpactedCluster(dp));
1049 }
1050