summaryrefslogtreecommitdiff
path: root/usr/src/cmd/fs.d/pcfs/fsck/dir.c
blob: 022c9350c764dacfe618014300bba094503fb1a5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * fsck_pcfs -- routines for manipulating directories.
 */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <libintl.h>
#include <ctype.h>
#include <time.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/byteorder.h>
#include <sys/dktp/fdisk.h>
#include <sys/fs/pc_fs.h>
#include <sys/fs/pc_dir.h>
#include <sys/fs/pc_label.h>
#include "pcfs_common.h"
#include "fsck_pcfs.h"

extern	int32_t	HiddenClusterCount;
extern	int32_t	FileClusterCount;
extern	int32_t	DirClusterCount;
extern	int32_t	HiddenFileCount;
extern	int32_t	LastCluster;
extern	int32_t	FileCount;
extern	int32_t	BadCount;
extern	int32_t	DirCount;
extern	int32_t	FATSize;
extern	off64_t	PartitionOffset;
extern	bpb_t	TheBIOSParameterBlock;
extern	int	ReadOnly;
extern	int	IsFAT32;
extern	int	Verbose;

static uchar_t *CHKsList = NULL;

ClusterContents	TheRootDir;
int32_t	RootDirSize;
int	RootDirModified;
int	OkayToRelink = 1;

/*
 * We have a bunch of routines for handling CHK names.  A CHK name is
 * simply a file name of the form "FILEnnnn.CHK", where the n's are the
 * digits in the numbers from 1 to 9999.  There are always four digits
 * used, leading zeros are added as necessary.
 *
 * We use CHK names to link orphaned cluster chains back into the file
 * system's root directory under an auspicious name so that the user
 * may be able to recover some of their data.
 *
 * We use these routines to ensure CHK names we use don't conflict
 * with any already present in the file system.
 */
static int
hasCHKName(struct pcdir *dp)
{
	return (dp->pcd_filename[CHKNAME_F] == 'F' &&
	    dp->pcd_filename[CHKNAME_I] == 'I' &&
	    dp->pcd_filename[CHKNAME_L] == 'L' &&
	    dp->pcd_filename[CHKNAME_E] == 'E' &&
	    isdigit(dp->pcd_filename[CHKNAME_THOUSANDS]) &&
	    isdigit(dp->pcd_filename[CHKNAME_HUNDREDS]) &&
	    isdigit(dp->pcd_filename[CHKNAME_TENS]) &&
	    isdigit(dp->pcd_filename[CHKNAME_ONES]) &&
	    dp->pcd_ext[CHKNAME_C] == 'C' &&
	    dp->pcd_ext[CHKNAME_H] == 'H' &&
	    dp->pcd_ext[CHKNAME_K] == 'K');
}

void
addEntryToCHKList(int chkNumber)
{
	/* silent failure on bogus value */
	if (chkNumber < 0 || chkNumber > MAXCHKVAL)
		return;
	CHKsList[chkNumber / NBBY] |= (1 << (chkNumber % NBBY));
}

static void
addToCHKList(struct pcdir *dp)
{
	int chknum;

	chknum = 1000 * (dp->pcd_filename[CHKNAME_THOUSANDS] - '0');
	chknum += 100 * (dp->pcd_filename[CHKNAME_HUNDREDS] - '0');
	chknum += 10 * (dp->pcd_filename[CHKNAME_TENS] - '0');
	chknum += (dp->pcd_filename[CHKNAME_ONES] - '0');
	addEntryToCHKList(chknum);
}

static int
inUseCHKName(int chkNumber)
{
	return (CHKsList[chkNumber / NBBY] & (1 << (chkNumber % NBBY)));
}

static void
appendToPath(struct pcdir *dp, char *thePath, int *theLen)
{
	int i = 0;

	/*
	 * Sometimes caller doesn't care about keeping track of the path
	 */
	if (thePath == NULL)
		return;

	/*
	 *  Prepend /
	 */
	if (*theLen < MAXPATHLEN)
		*(thePath + (*theLen)++) = '/';
	/*
	 *  Print out the file name part, but only up to the first
	 *  space.
	 */
	while (*theLen < MAXPATHLEN && i < PCFNAMESIZE) {
		/*
		 *  When we start seeing spaces we assume that's the
		 *  end of the interesting characters in the name.
		 */
		if ((dp->pcd_filename[i] == ' ') ||
		    !(pc_validchar(dp->pcd_filename[i])))
			break;
		*(thePath + (*theLen)++) = dp->pcd_filename[i++];
	}
	/*
	 *  Leave now, if we don't have an extension (or room for one)
	 */
	if ((dp->pcd_ext[i] == ' ') || ((*theLen) >= MAXPATHLEN) ||
	    (!(pc_validchar(dp->pcd_ext[i]))))
		return;
	/*
	 *  Tack on the extension
	 */
	*(thePath + (*theLen)++) = '.';
	i = 0;
	while ((*theLen < MAXPATHLEN) && (i < PCFEXTSIZE)) {
		if ((dp->pcd_ext[i] == ' ') || !(pc_validchar(dp->pcd_ext[i])))
			break;
		*(thePath + (*theLen)++) = dp->pcd_ext[i++];
	}
}

static void
printName(FILE *outDest, struct pcdir *dp)
{
	int i;
	for (i = 0; i < PCFNAMESIZE; i++) {
		if ((dp->pcd_filename[i] == ' ') ||
		    !(pc_validchar(dp->pcd_filename[i])))
			break;
		(void) fprintf(outDest, "%c", dp->pcd_filename[i]);
	}
	(void) fprintf(outDest, ".");
	for (i = 0; i < PCFEXTSIZE; i++) {
		if (!(pc_validchar(dp->pcd_ext[i])))
			break;
		(void) fprintf(outDest, "%c", dp->pcd_ext[i]);
	}
}

/*
 *  sanityCheckSize
 *	Make sure the size in the directory entry matches what is
 *	actually allocated.  If there is a mismatch, orphan all
 *	the allocated clusters.  Returns SIZE_MATCHED if everything matches
 *	up, TRUNCATED to indicate truncation was necessary.
 */
static int
sanityCheckSize(int fd, struct pcdir *dp, int32_t actualClusterCount,
    int isDir, int32_t startCluster, struct nameinfo *fullPathName,
    struct pcdir **orphanEntry)
{
	uint32_t sizeFromDir;
	int32_t ignorei = 0;
	int64_t bpc;

	bpc = TheBIOSParameterBlock.bpb.sectors_per_cluster *
	    TheBIOSParameterBlock.bpb.bytes_per_sector;
	sizeFromDir = extractSize(dp);
	if (isDir) {
		if (sizeFromDir == 0)
			return (SIZE_MATCHED);
	} else {
		if ((sizeFromDir > ((actualClusterCount - 1) * bpc)) &&
		    (sizeFromDir <= (actualClusterCount * bpc)))
			return (SIZE_MATCHED);
	}
	if (fullPathName != NULL) {
		fullPathName->references++;
		(void) fprintf(stderr, "%s\n", fullPathName->fullName);
	}
	squirrelPath(fullPathName, startCluster);
	(void) fprintf(stderr,
	    gettext("Truncating chain due to incorrect size "
	    "in directory.  Size from directory = %u bytes,\n"), sizeFromDir);
	if (actualClusterCount == 0) {
		(void) fprintf(stderr,
		    gettext("Zero bytes are allocated to the file.\n"));
	} else {
		(void) fprintf(stderr,
		    gettext("Allocated size in range %llu - %llu bytes.\n"),
		    ((actualClusterCount - 1) * bpc) + 1,
		    (actualClusterCount * bpc));
	}
	/*
	 * Use splitChain() to make an orphan that is the entire allocation
	 * chain.
	 */
	splitChain(fd, dp, startCluster, orphanEntry, &ignorei);
	return (TRUNCATED);
}

static int
noteUsage(int fd, int32_t startAt, struct pcdir *dp, struct pcdir *lp,
    int32_t longEntryStartCluster, int isHidden, int isDir,
    struct nameinfo *fullPathName)
{
	struct pcdir *orphanEntry;
	int32_t chain = startAt;
	int32_t count = 0;
	int savePathNextIteration = 0;
	int haveBad = 0;
	ClusterInfo *tmpl = NULL;

	while ((chain >= FIRST_CLUSTER) && (chain <= LastCluster)) {
		if ((markInUse(fd, chain, dp, lp, longEntryStartCluster,
		    isHidden ? HIDDEN : VISIBLE, &tmpl))
			!= CLINFO_NEWLY_ALLOCED)
			break;
		count++;
		if (savePathNextIteration == 1) {
			savePathNextIteration = 0;
			if (fullPathName != NULL)
				fullPathName->references++;
			squirrelPath(fullPathName, chain);
		}
		if (isMarkedBad(chain)) {
			haveBad = 1;
			savePathNextIteration = 1;
		}
		if (isHidden)
			HiddenClusterCount++;
		else if (isDir)
			DirClusterCount++;
		else
			FileClusterCount++;
		chain = nextInChain(chain);
	}
	/*
	 * Do a sanity check on the file size in the directory entry.
	 * This may create an orphaned cluster chain.
	 */
	if (sanityCheckSize(fd, dp, count, isDir, startAt,
	    fullPathName, &orphanEntry) == TRUNCATED) {
		/*
		 * The pre-existing directory entry has been truncated,
		 * so the chain associated with it no longer has any
		 * bad clusters.  Instead, the new orphan has them.
		 */
		if (haveBad > 0) {
			truncChainWithBadCluster(fd, orphanEntry, startAt);
		}
		haveBad = 0;
	}
	return (haveBad);
}

static void
storeInfoAboutEntry(int fd, struct pcdir *dp, struct pcdir *ldp, int depth,
    int32_t longEntryStartCluster, char *fullPath, int *fullLen)
{
	struct nameinfo *pathCopy;
	int32_t start;
	int haveBad;
	int hidden = (dp->pcd_attr & PCA_HIDDEN || dp->pcd_attr & PCA_SYSTEM);
	int dir = (dp->pcd_attr & PCA_DIR);
	int i;

	if (hidden)
		HiddenFileCount++;
	else if (dir)
		DirCount++;
	else
		FileCount++;
	appendToPath(dp, fullPath, fullLen);

	/*
	 * Make a copy of the name at this point.  We may want it to
	 * note the original source of an orphaned cluster.
	 */
	if ((pathCopy =
	    (struct nameinfo *)malloc(sizeof (struct nameinfo))) != NULL) {
		if ((pathCopy->fullName =
		    (char *)malloc(*fullLen + 1)) != NULL) {
			pathCopy->references = 0;
			(void) strncpy(pathCopy->fullName, fullPath, *fullLen);
			pathCopy->fullName[*fullLen] = '\0';
		} else {
			free(pathCopy);
			pathCopy = NULL;
		}
	}
	if (Verbose) {
		for (i = 0; i < depth; i++)
			(void) fprintf(stderr, "  ");
		if (hidden)
			(void) fprintf(stderr, "[");
		else if (dir)
			(void) fprintf(stderr, "|_");
		else
			(void) fprintf(stderr, gettext("(%06d) "), FileCount);
		printName(stderr, dp);
		if (hidden)
			(void) fprintf(stderr, "]");
		(void) fprintf(stderr,
		    gettext(", %u bytes, start cluster %d"),
		    extractSize(dp), extractStartCluster(dp));
		(void) fprintf(stderr, "\n");
	}
	start = extractStartCluster(dp);
	haveBad = noteUsage(fd, start, dp, ldp, longEntryStartCluster,
	    hidden, dir, pathCopy);
	if (haveBad > 0) {
		if (dir && pathCopy->fullName != NULL) {
			(void) fprintf(stderr,
			    gettext("Adjusting for bad allocation units in "
			    "the meta-data of:\n  "));
			(void) fprintf(stderr, pathCopy->fullName);
			(void) fprintf(stderr, "\n");
		}
		truncChainWithBadCluster(fd, dp, start);
	}
	if ((pathCopy != NULL) && (pathCopy->references == 0)) {
		free(pathCopy->fullName);
		free(pathCopy);
	}
}

static void
storeInfoAboutLabel(struct pcdir *dp)
{
	/*
	 * XXX eventually depth should be passed to this routine just
	 * as it is with storeInfoAboutEntry().  If it isn't zero, then
	 * we've got a bogus directory entry.
	 */
	if (Verbose) {
		(void) fprintf(stderr, gettext("** "));
		printName(stderr, dp);
		(void) fprintf(stderr, gettext(" **\n"));
	}
}

static void
searchChecks(struct pcdir *dp, int operation, char matchRequired,
    struct pcdir **found)
{
	/*
	 *  We support these searching operations:
	 *
	 *  PCFS_FIND_ATTR
	 *	look for the first file with a certain attribute
	 *	(e.g, find all hidden files)
	 *  PCFS_FIND_STATUS
	 *	look for the first file with a certain status
	 *	(e.g., the file has been marked deleted; making
	 *	its directory entry reusable)
	 *  PCFS_FIND_CHKS
	 *	look for all files with short names of the form
	 *	FILENNNN.CHK.  These are the file names we give
	 *	to chains of orphaned clusters we relink into the
	 *	file system.  This find facility allows us to seek
	 *	out all existing files of this naming form so that
	 *	we may create unique file names for new orphans.
	 */
	if (operation == PCFS_FIND_ATTR && dp->pcd_attr == matchRequired) {
		*found = dp;
	} else if (operation == PCFS_FIND_STATUS &&
	    dp->pcd_filename[0] == matchRequired) {
		*found = dp;
	} else if (operation == PCFS_FIND_CHKS && hasCHKName(dp)) {
		addToCHKList(dp);
	}
}

static void
catalogEntry(int fd, struct pcdir *dp, struct pcdir *longdp,
    int32_t currentCluster, int depth, char *recordPath, int *pathLen)
{
	if (dp->pcd_attr & PCA_LABEL) {
		storeInfoAboutLabel(dp);
	} else {
		storeInfoAboutEntry(fd, dp, longdp, depth, currentCluster,
		    recordPath, pathLen);
	}
}

/*
 * visitNodes()
 *
 * This is the main workhouse routine for traversing pcfs metadata.
 * There isn't a lot to the metadata.  Basically there is a root
 * directory somewhere (either in its own special place outside the
 * data area or in a data cluster).  The root directory (and all other
 * directories) are filled with a number of fixed size entries.  An
 * entry has the filename and extension, the file's attributes, the
 * file's size, and the starting data cluster of the storage allocated
 * to the file.  To determine which clusters are assigned to the file,
 * you start at the starting cluster entry in the FAT, and follow the
 * chain of entries in the FAT.
 *
 *	Arguments are:
 *	fd
 *		descriptor for accessing the raw file system data
 *	currentCluster
 *		original caller supplies the initial starting cluster,
 *		subsequent recursive calls are made with updated
 *		cluster numbers for the sub-directories.
 *	dirData
 *		pointer to the directory data bytes
 *	dirDataLen
 *		size of the whole buffer of data bytes (usually it is
 *		the size of a cluster, but the root directory on
 *		FAT12/16 is not necessarily the same size as a cluster).
 *	depth
 *		original caller should set it to zero (assuming they are
 *		starting from the root directory).  This number is used to
 *		change the indentation of file names presented as debug info.
 *	descend
 *		boolean indicates if we should descend into subdirectories.
 *	operation
 *		what, if any, matching should be performed.
 *		The PCFS_TRAVERSE_ALL operation is a depth first traversal
 *		of all nodes in the metadata tree, that tracks all the
 *		clusters in use (according to the meta-data, at least)
 *	matchRequired
 *		value to be matched (if any)
 *	found
 *		output parameter
 *		used to return pointer to a directory entry that matches
 *		the search requirement
 *		original caller should pass in a pointer to a NULL pointer.
 *	lastDirCluster
 *		output parameter
 *		if no match found, last cluster num of starting directory
 *	dirEnd
 *		output parameter
 *		if no match found, return parameter stores pointer to where
 *		new directory entry could be appended to existing directory
 *	recordPath
 *		output parameter
 *		as files are discovered, and directories traversed, this
 *		buffer is used to store the current full path name.
 *	pathLen
 *		output parameter
 *		this is in the integer length of the current full path name.
 */
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)
{
	struct pcdir *longdp = NULL;
	struct pcdir *dp;
	int32_t longStart;
	int withinLongName = 0;
	int saveLen = *pathLen;

	dp = dirData->dirp;

	/*
	 *  A directory entry where the first character of the name is
	 *  PCD_UNUSED indicates the end of the directory.
	 */
	while ((uchar_t *)dp < dirData->bytes + dirDataLen &&
	    dp->pcd_filename[0] != PCD_UNUSED) {
		/*
		 *  Handle the special case find operations.
		 */
		searchChecks(dp, operation, matchRequired, found);
		if (*found)
			break;
		/*
		 * Are we looking at part of a long file name entry?
		 * If so, we may need to note the start of the name.
		 * We don't do any further processing of long file
		 * name entries.
		 *
		 * We also skip deleted entries and the '.' and '..'
		 * entries.
		 */
		if ((dp->pcd_attr & PCDL_LFN_BITS) == PCDL_LFN_BITS) {
			if (!withinLongName) {
				withinLongName++;
				longStart = currentCluster;
				longdp = dp;
			}
			dp++;
			continue;
		} else if ((dp->pcd_filename[0] == PCD_ERASED) ||
		    (dp->pcd_filename[0] == '.')) {
			/*
			 * XXX - if we were within a long name, then
			 * its existence is bogus, because it is not
			 * attached to any real file.
			 */
			withinLongName = 0;
			dp++;
			continue;
		}
		withinLongName = 0;
		if (operation == PCFS_TRAVERSE_ALL)
			catalogEntry(fd, dp, longdp, longStart, depth,
			    recordPath, pathLen);
		longdp = NULL;
		longStart = 0;
		if (dp->pcd_attr & PCA_DIR && descend == PCFS_VISIT_SUBDIRS) {
			traverseDir(fd, extractStartCluster(dp), depth + 1,
			    descend, operation, matchRequired, found,
			    lastDirCluster, dirEnd, recordPath, pathLen);
			if (*found)
				break;
		}
		dp++;
		*pathLen = saveLen;
	}
	if (*found)
		return;
	if ((uchar_t *)dp < dirData->bytes + dirDataLen) {
		/*
		 * We reached the end of directory before the end of
		 * our provided data (a cluster).  That means this cluster
		 * is the last one in this directory's chain.  It also
		 * means we've just looked at the last directory entry.
		 */
		*lastDirCluster = currentCluster;
		*dirEnd = dp;
		return;
	}
	/*
	 * If there is more to the directory we'll go get it otherwise we
	 * are done traversing this directory.
	 */
	if ((currentCluster == FAKE_ROOTDIR_CLUST) ||
	    (lastInFAT(currentCluster))) {
		*lastDirCluster = currentCluster;
		return;
	} else {
		traverseDir(fd, nextInChain(currentCluster),
		    depth, descend, operation, matchRequired,
		    found, lastDirCluster, dirEnd, recordPath, pathLen);
		*pathLen = saveLen;
	}
}

/*
 *  traverseFromRoot()
 *	For use with 12 and 16 bit FATs that have a root directory outside
 *	of the file system.  This is a general purpose routine that
 *	can be used simply to visit all of the nodes in the metadata or
 *	to find the first instance of something, e.g., the first directory
 *	entry where the file is marked deleted.
 *
 *	Inputs are described in the commentary for visitNodes() above.
 */
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)
{
	visitNodes(fd, FAKE_ROOTDIR_CLUST, &TheRootDir, RootDirSize, depth,
	    descend, operation, matchRequired, found, lastDirCluster, dirEnd,
	    recordPath, pathLen);
}

/*
 *  traverseDir()
 *	For use with all FATs outside of the initial root directory on
 *	12 and 16 bit FAT file systems.  This is a general purpose routine
 *	that can be used simply to visit all of the nodes in the metadata or
 *	to find the first instance of something, e.g., the first directory
 *	entry where the file is marked deleted.
 *
 *	Unique Input is:
 *	startAt
 *		starting cluster of the directory
 *
 *	This is the cluster that is the first one in this directory.
 *	We read it right away, so we can provide it as data to visitNodes().
 *	Note that we cache this cluster as we read it, because it is
 *	metadata and we cache all metadata.  By doing so, we can
 *	keep pointers to directory entries for quickly moving around and
 *	fixing up any problems we find.  Of course if we get a big
 *	filesystem with a huge amount of metadata we may be hosed, as
 *	we'll likely run out of memory.
 *
 *	I believe in the future this will have to be addressed.  It
 *	may be possible to do more of the processing of problems
 *	within directories as they are cached, so that when memory
 *	runs short we can free cached directories we are already
 *	finished visiting.
 *
 *	The remainder of inputs are described in visitNodes() comments.
 */
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)
{
	ClusterContents dirdata;
	int32_t dirdatasize = 0;

	if (startAt < FIRST_CLUSTER || startAt > LastCluster)
		return;

	if (readCluster(fd, startAt, &(dirdata.bytes), &dirdatasize,
	    RDCLUST_DO_CACHE) != RDCLUST_GOOD) {
		(void) fprintf(stderr,
		    gettext("Unable to get more directory entries!\n"));
		return;
	}

	if (operation == PCFS_TRAVERSE_ALL) {
		if (Verbose)
			(void) fprintf(stderr,
			    gettext("Directory traversal enters "
			    "allocation unit %d.\n"), startAt);
	}
	visitNodes(fd, startAt, &dirdata, dirdatasize, depth, descend,
	    operation, matchRequired, found, lastDirCluster, dirEnd,
	    recordPath, pathLen);
}

void
createCHKNameList(int fd)
{
	struct pcdir *ignorep1, *ignorep2;
	int32_t ignore32;
	char *ignorecp = NULL;
	char ignore = '\0';
	int ignoreint = 0;

	ignorep1 = ignorep2 = NULL;
	if (!OkayToRelink || CHKsList != NULL)
		return;

	/*
	 *  Allocate an array to keep a bit map of the integer
	 *  values used in CHK names.
	 */
	if ((CHKsList =
	    (uchar_t *)calloc(1, idivceil(MAXCHKVAL, NBBY))) == NULL) {
		OkayToRelink = 0;
		return;
	}

	/*
	 *  Search the root directory for all the files with names of
	 *  the form FILEXXXX.CHK.  The root directory is an area
	 *  outside of the file space on FAT12 and FAT16 file systems.
	 *  On FAT32 file systems, the root directory is in a file
	 *  area cluster just like any other directory.
	 */
	if (!IsFAT32) {
		traverseFromRoot(fd, 0, PCFS_NO_SUBDIRS, PCFS_FIND_CHKS,
		    ignore, &ignorep1, &ignore32, &ignorep2, ignorecp,
		    &ignoreint);
	} else {
		DirCount++;
		traverseDir(fd, TheBIOSParameterBlock.bpb32.root_dir_clust,
		    0, PCFS_NO_SUBDIRS, PCFS_FIND_CHKS, ignore,
		    &ignorep1, &ignore32, &ignorep2, ignorecp, &ignoreint);
	}
}


char *
nextAvailableCHKName(int *chosen)
{
	static char nameBuf[PCFNAMESIZE];
	int i;

	if (!OkayToRelink)
		return (NULL);

	nameBuf[CHKNAME_F] = 'F';
	nameBuf[CHKNAME_I] = 'I';
	nameBuf[CHKNAME_L] = 'L';
	nameBuf[CHKNAME_E] = 'E';

	for (i = 1; i <= MAXCHKVAL; i++) {
		if (!inUseCHKName(i))
			break;
	}
	if (i <= MAXCHKVAL) {
		nameBuf[CHKNAME_THOUSANDS] = '0' + (i / 1000);
		nameBuf[CHKNAME_HUNDREDS] = '0' + ((i % 1000) / 100);
		nameBuf[CHKNAME_TENS] = '0' + ((i % 100) / 10);
		nameBuf[CHKNAME_ONES] = '0' + (i % 10);
		*chosen = i;
		return (nameBuf);
	} else {
		(void) fprintf(stderr,
		    gettext("Sorry, no names available for "
		    "relinking orphan chains!\n"));
		OkayToRelink = 0;
		return (NULL);
	}
}

uint32_t
extractSize(struct pcdir *dp)
{
	uint32_t returnMe;

	read_32_bits((uchar_t *)&(dp->pcd_size), &returnMe);
	return (returnMe);
}

int32_t
extractStartCluster(struct pcdir *dp)
{
	uint32_t lo, hi;

	if (IsFAT32) {
		read_16_bits((uchar_t *)&(dp->un.pcd_scluster_hi), &hi);
		read_16_bits((uchar_t *)&(dp->pcd_scluster_lo), &lo);
		return ((int32_t)((hi << 16) | lo));
	} else {
		read_16_bits((uchar_t *)&(dp->pcd_scluster_lo), &lo);
		return ((int32_t)lo);
	}
}

static struct pcdir *
findAvailableRootDirEntSlot(int fd, int32_t *clusterWithSlot)
{
	struct pcdir *deletedEntry = NULL;
	struct pcdir *appendPoint = NULL;
	char *ignorecp = NULL;
	int ignore = 0;

	*clusterWithSlot = 0;

	/*
	 *  First off, try to find an erased entry in the root
	 *  directory.  The root directory is an area outside of the
	 *  file space on FAT12 and FAT16 file systems.  On FAT32 file
	 *  systems, the root directory is in a file area cluster just
	 *  like any other directory.
	 */
	if (!IsFAT32) {
		traverseFromRoot(fd, 0, PCFS_NO_SUBDIRS, PCFS_FIND_STATUS,
		    PCD_ERASED, &deletedEntry, clusterWithSlot,
		    &appendPoint, ignorecp, &ignore);
	} else {
		DirCount++;
		traverseDir(fd, TheBIOSParameterBlock.bpb32.root_dir_clust,
		    0, PCFS_NO_SUBDIRS, PCFS_FIND_STATUS, PCD_ERASED,
		    &deletedEntry, clusterWithSlot, &appendPoint, ignorecp,
		    &ignore);
	}
	/*
	 *  If we found a deleted file in the directory we'll overwrite
	 *  that entry.
	 */
	if (deletedEntry)
		return (deletedEntry);
	/*
	 *  If there is room at the end of the existing directory, we
	 *  should place the new entry there.
	 */
	if (appendPoint)
		return (appendPoint);
	/*
	 *  XXX need to grow the directory
	 */
	return (NULL);
}

static void
insertDirEnt(struct pcdir *slot, struct pcdir *entry, int32_t clusterWithSlot)
{
	(void) memcpy(slot, entry, sizeof (struct pcdir));
	markClusterModified(clusterWithSlot);
}

/*
 *  Convert current UNIX time into a PCFS timestamp (which is in local time).
 *
 *  Since the "seconds" field of that is only accurate to 2sec precision,
 *  we allow for the optional (used only for creation times on FAT) "msec"
 *  parameter that takes the fractional part.
 */
static void
getNow(struct pctime *pctp, uchar_t *msec)
{
	time_t		now;
	struct tm	tm;
	ushort_t	tim, dat;

	/*
	 * Disable daylight savings corrections - Solaris PCFS doesn't
	 * support such conversions yet. Save timestamps in local time.
	 */
	daylight = 0;

	(void) time(&now);
	(void) localtime_r(&now, &tm);

	dat = (tm.tm_year - 80) << YEARSHIFT;
	dat |= tm.tm_mon << MONSHIFT;
	dat |= tm.tm_mday << DAYSHIFT;
	tim = tm.tm_hour << HOURSHIFT;
	tim |= tm.tm_min << MINSHIFT;
	tim |= (tm.tm_sec / 2) << SECSHIFT;

	/*
	 * Sanity check. If we overflow the PCFS timestamp range
	 * we set the time to 01/01/1980, 00:00:00
	 */
	if (dat < 80 || dat > 227)
		dat = tim = 0;

	pctp->pct_date = LE_16(dat);
	pctp->pct_time = LE_16(tim);
	if (msec)
		*msec = (tm.tm_sec & 1) ? 100 : 0;
}

/*
 *  FAT file systems store the following time information in a directory
 *  entry:
 *		timestamp		member of "struct pcdir"
 * ======================================================================
 *		creation time		pcd_crtime.pct_time
 *		creation date		pcd_crtime.pct_date
 *		last access date	pcd_ladate
 *		last modify time	pcd_mtime.pct_time
 *		last modify date	pcd_mtime.pct_date
 *
 *  No access time is kept.
 */
static void
updateDirEnt_CreatTime(struct pcdir *dp)
{
	getNow(&dp->pcd_crtime, &dp->pcd_crtime_msec);
	markClusterModified(findImpactedCluster(dp));
}

static void
updateDirEnt_ModTimes(struct pcdir *dp)
{
	timestruc_t	ts;

	getNow(&dp->pcd_mtime, NULL);
	dp->pcd_ladate = dp->pcd_mtime.pct_date;
	dp->pcd_attr |= PCA_ARCH;
	markClusterModified(findImpactedCluster(dp));
}

struct pcdir *
addRootDirEnt(int fd, struct pcdir *new)
{
	struct pcdir *added;
	int32_t inCluster;

	if ((added = findAvailableRootDirEntSlot(fd, &inCluster)) != NULL) {
		insertDirEnt(added, new, inCluster);
		return (added);
	}
	return (NULL);
}

/*
 *  FAT12 and FAT16 have a root directory outside the normal file space,
 *  so we have separate routines for finding and reading the root directory.
 */
static off64_t
seekRootDirectory(int fd)
{
	off64_t seekto;

	/*
	 *  The RootDir immediately follows the FATs, which in
	 *  turn immediately follow the reserved sectors.
	 */
	seekto = (off64_t)TheBIOSParameterBlock.bpb.resv_sectors *
		    TheBIOSParameterBlock.bpb.bytes_per_sector +
		    (off64_t)FATSize * TheBIOSParameterBlock.bpb.num_fats +
		    (off64_t)PartitionOffset;
	if (Verbose)
		(void) fprintf(stderr,
		    gettext("Seeking root directory @%lld.\n"), seekto);
	return (lseek64(fd, seekto, SEEK_SET));
}

void
getRootDirectory(int fd)
{
	ssize_t bytesRead;

	if (TheRootDir.bytes != NULL)
		return;
	else if ((TheRootDir.bytes = (uchar_t *)malloc(RootDirSize)) == NULL) {
		mountSanityCheckFails();
		perror(gettext("No memory for a copy of the root directory"));
		(void) close(fd);
		exit(8);
	}

	if (seekRootDirectory(fd) < 0) {
		mountSanityCheckFails();
		perror(gettext("Cannot seek to RootDir"));
		(void) close(fd);
		exit(8);
	}

	if (Verbose)
		(void) fprintf(stderr,
		    gettext("Reading root directory.\n"));
	if ((bytesRead = read(fd, TheRootDir.bytes, RootDirSize)) !=
	    RootDirSize) {
		mountSanityCheckFails();
		if (bytesRead < 0) {
			perror(gettext("Cannot read a RootDir"));
		} else {
			(void) fprintf(stderr,
			    gettext("Short read of RootDir\n"));
		}
		(void) close(fd);
		exit(8);
	}
	if (Verbose) {
		(void) fprintf(stderr,
		    gettext("Dump of root dir's first 256 bytes.\n"));
		header_for_dump();
		dump_bytes(TheRootDir.bytes, 256);
	}
}

void
writeRootDirMods(int fd)
{
	ssize_t bytesWritten;

	if (!TheRootDir.bytes) {
		(void) fprintf(stderr,
		    gettext("Internal error: No Root directory to write\n"));
		(void) close(fd);
		exit(12);
	}
	if (!RootDirModified) {
		if (Verbose) {
			(void) fprintf(stderr,
			    gettext("No root directory changes need to "
			    "be written.\n"));
		}
		return;
	}
	if (ReadOnly)
		return;
	if (Verbose)
		(void) fprintf(stderr,
		    gettext("Writing root directory.\n"));
	if (seekRootDirectory(fd) < 0) {
		perror(gettext("Cannot write the RootDir (seek failed)"));
		(void) close(fd);
		exit(12);
	}
	if ((bytesWritten = write(fd, TheRootDir.bytes, RootDirSize)) !=
	    RootDirSize) {
		if (bytesWritten < 0) {
			perror(gettext("Cannot write the RootDir"));
		} else {
			(void) fprintf(stderr,
			    gettext("Short write of root directory\n"));
		}
		(void) close(fd);
		exit(12);
	}
	RootDirModified = 0;
}

struct pcdir *
newDirEnt(struct pcdir *copyme)
{
	struct pcdir *ndp;

	if ((ndp = (struct pcdir *)calloc(1, sizeof (struct pcdir))) == NULL) {
		(void) fprintf(stderr, gettext("Out of memory to create a "
		    "new directory entry!\n"));
		return (ndp);
	}
	if (copyme)
		(void) memcpy(ndp, copyme, sizeof (struct pcdir));
	ndp->pcd_ext[CHKNAME_C] = 'C';
	ndp->pcd_ext[CHKNAME_H] = 'H';
	ndp->pcd_ext[CHKNAME_K] = 'K';
	updateDirEnt_CreatTime(ndp);
	updateDirEnt_ModTimes(ndp);
	return (ndp);
}

void
updateDirEnt_Size(struct pcdir *dp, uint32_t newSize)
{
	uchar_t *p = (uchar_t *)&(dp->pcd_size);
	store_32_bits(&p, newSize);
	markClusterModified(findImpactedCluster(dp));
}

void
updateDirEnt_Start(struct pcdir *dp, int32_t newStart)
{
	uchar_t *p = (uchar_t *)&(dp->pcd_scluster_lo);
	store_16_bits(&p, newStart & 0xffff);
	if (IsFAT32) {
		p = (uchar_t *)&(dp->un.pcd_scluster_hi);
		store_16_bits(&p, newStart >> 16);
	}
	markClusterModified(findImpactedCluster(dp));
}

void
updateDirEnt_Name(struct pcdir *dp, char *newName)
{
	int i;

	for (i = 0; i < PCFNAMESIZE; i++) {
		if (*newName)
			dp->pcd_filename[i] = *newName++;
		else
			dp->pcd_filename[i] = ' ';
	}
	markClusterModified(findImpactedCluster(dp));
}