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