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