1/*
2 * Copyright (c) 2000-2009 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24#include "Scavenger.h"
25#include "DecompDataEnums.h"
26#include "DecompData.h"
27
28#include <sys/stat.h>
29
30extern int RcdFCntErr( SGlobPtr GPtr, OSErr type, UInt32 correct, UInt32 incorrect, HFSCatalogNodeID);
31extern int RcdHsFldCntErr( SGlobPtr GPtr, OSErr type, UInt32 correct, UInt32 incorrect, HFSCatalogNodeID);
32
33/*
34 * information collected when visiting catalog records
35 */
36struct CatalogIterationSummary {
37	UInt32 parentID;
38	UInt32 rootDirCount;   /* hfs only */
39	UInt32 rootFileCount;  /* hfs only */
40	UInt32 dirCount;
41	UInt32 dirThreads;
42	UInt32 fileCount;
43	UInt32 filesWithThreads; /* hfs only */
44	UInt32 fileThreads;
45	UInt32 nextCNID;
46	UInt64 encodings;
47	void * hardLinkRef;
48};
49
50/* Globals used during Catalog record checks */
51struct CatalogIterationSummary gCIS;
52
53SGlobPtr gScavGlobals;
54
55/* Local routines for checking catalog structures */
56static int  CheckCatalogRecord(SGlobPtr GPtr, const HFSPlusCatalogKey *key,
57                               const CatalogRecord *rec, UInt16 reclen);
58static int  CheckCatalogRecord_HFS(const HFSCatalogKey *key,
59                                   const CatalogRecord *rec, UInt16 reclen);
60
61static int  CheckDirectory(const HFSPlusCatalogKey * key, const HFSPlusCatalogFolder * dir);
62static int  CheckFile(const HFSPlusCatalogKey * key, const HFSPlusCatalogFile * file);
63static int  CheckThread(const HFSPlusCatalogKey * key, const HFSPlusCatalogThread * thread);
64
65static int  CheckDirectory_HFS(const HFSCatalogKey * key, const HFSCatalogFolder * dir);
66static int  CheckFile_HFS(const HFSCatalogKey * key, const HFSCatalogFile * file);
67static int  CheckThread_HFS(const HFSCatalogKey * key, const HFSCatalogThread * thread);
68
69static void CheckBSDInfo(const HFSPlusCatalogKey * key, const HFSPlusBSDInfo * bsdInfo, int isdir);
70static int  CheckCatalogName(u_int16_t charCount, const u_int16_t *uniChars,
71                             u_int32_t parentID, Boolean thread);
72static int  CheckCatalogName_HFS(u_int16_t charCount, const u_char *filename,
73                                 u_int32_t parentID, Boolean thread);
74
75static int  CaptureMissingThread(UInt32 threadID, const HFSPlusCatalogKey *nextKey);
76static 	OSErr	UniqueDotName( 	SGlobPtr GPtr,
77                                CatalogName * theNewNamePtr,
78                                UInt32 theParID,
79                                Boolean isSingleDotName,
80                                Boolean isHFSPlus );
81static Boolean 	FixDecomps(	u_int16_t charCount, const u_int16_t *inFilename, HFSUniStr255 *outFilename );
82
83/*
84 * This structure is used to keep track of the folderCount field in
85 * HFSPlusCatalogFolder records.  For now, this is only done on HFSX volumes.
86 */
87struct folderCountInfo {
88	UInt32 folderID;
89	UInt32 recordedCount;
90	UInt32 computedCount;
91	struct folderCountInfo *next;
92};
93
94/*
95 * Print a symbolic link name given the fileid
96 */
97static void
98printSymLinkName(SGlobPtr GPtr, UInt32 fid)
99{
100	char pathname[PATH_MAX+1], filename[PATH_MAX+1];
101	unsigned int path_len = sizeof(pathname), fname_len = sizeof(filename);
102	u_int16_t status;
103
104	if (GetFileNamePathByID(GPtr, fid, pathname, &path_len, filename, &fname_len, &status) == 0) {
105		fsckPrint(GPtr->context, E_BadSymLinkName, pathname);
106	}
107	return;
108}
109
110/*
111 * CountFolderRecords - Counts the number of folder records contained within a
112 * given folder.  That is, how many direct subdirectories it has.  This is used
113 * to update the folderCount field, if necessary.
114 *
115 * CountFolderRecords is a straight-forward iteration:  given a HFSPlusCatalogFolder
116 * record, it iterates through the catalog BTree until it runs out of records that
117 * belong to it.  For each folder record it finds, it increments a count.  When it's
118 * done, it compares the two, and if there is a mismatch, requests a repair to be
119 * done.
120 */
121static OSErr
122CountFolderRecords(HFSPlusCatalogKey *myKey, HFSPlusCatalogFolder *folder, SGlobPtr GPtr)
123{
124	SFCB *fcb = GPtr->calculatedCatalogFCB;
125	OSErr err = 0;
126	BTreeIterator iterator;
127	FSBufferDescriptor btRecord;
128	union {
129		HFSPlusCatalogFolder catRecord;
130		HFSPlusCatalogFile catFile;
131	} catRecord;
132	HFSPlusCatalogKey *key;
133	UInt16 recordSize = 0;
134	UInt32 folderCount = 0;
135
136	ClearMemory(&iterator, sizeof(iterator));
137
138	key = (HFSPlusCatalogKey*)&iterator.key;
139	BuildCatalogKey(folder->folderID, NULL, true, (CatalogKey*)key);
140	btRecord.bufferAddress = &catRecord;
141	btRecord.itemCount = 1;
142	btRecord.itemSize = sizeof(catRecord);
143
144	for (err = BTSearchRecord(fcb, &iterator, kNoHint, &btRecord, &recordSize, &iterator);
145		err == 0;
146		err = BTIterateRecord(fcb, kBTreeNextRecord, &iterator, &btRecord, &recordSize)) {
147		switch (catRecord.catRecord.recordType) {
148		case kHFSPlusFolderThreadRecord:
149		case kHFSPlusFileThreadRecord:
150			continue;
151		}
152		if (key->parentID != folder->folderID)
153			break;
154		if (catRecord.catRecord.recordType == kHFSPlusFolderRecord) {
155			folderCount++;
156		} else if ((catRecord.catRecord.recordType == kHFSPlusFileRecord) &&
157			   (catRecord.catFile.flags & kHFSHasLinkChainMask) &&
158			   (catRecord.catFile.userInfo.fdType == kHFSAliasType) &&
159			   (catRecord.catFile.userInfo.fdCreator == kHFSAliasCreator) &&
160			   (key->parentID != GPtr->filelink_priv_dir_id)) {
161			/* A directory hard link is treated as normal directory
162			 * for calculation of folder count.
163			 */
164			folderCount++;
165		}
166	}
167	if (err == btNotFound)
168		err = 0;
169	if (err == 0) {
170		if (folderCount != folder->folderCount) {
171			err = RcdFCntErr( GPtr,
172				E_FldCount,
173				folderCount,
174				folder->folderCount,
175				folder->folderID);
176		}
177	}
178	return err;
179}
180
181static void
182releaseFolderCountInfo(struct folderCountInfo *fcip, int numFolders)
183{
184	int i;
185
186	for (i = 0; i < numFolders; i++) {
187		struct folderCountInfo *f = &fcip[i];
188
189		f = f->next;
190		while (f) {
191			struct folderCountInfo *t = f->next;
192			free(f);
193			f = t;
194		}
195	}
196	free(fcip);
197}
198
199static struct folderCountInfo *
200findFolderEntry(struct folderCountInfo *fcip, int numFolders, UInt32 fid)
201{
202	struct folderCountInfo *retval = NULL;
203	int indx;
204
205	indx = fid % numFolders;	// Slot index
206
207	retval = &fcip[indx];
208	if (retval->folderID == fid) {
209		goto done;
210	}
211	while (retval->next != NULL) {
212		retval = retval->next;
213		if (retval->folderID == fid)
214			goto done;
215	}
216	retval = NULL;
217done:
218	return retval;
219}
220
221static struct folderCountInfo *
222addFolderEntry(struct folderCountInfo *fcip, int numFolders, UInt32 fid)
223{
224	struct folderCountInfo *retval = NULL;
225	int indx;
226
227	indx = fid % numFolders;
228	retval = &fcip[indx];
229
230	if (retval->folderID == fid)
231		goto done;
232	while (retval->folderID != 0) {
233		if (retval->next == NULL) {
234			retval->next = calloc(1, sizeof(struct folderCountInfo));
235			if (retval->next == NULL) {
236				retval = NULL;
237				goto done;
238			} else
239				retval = retval->next;
240		} else if (retval->folderID == fid) {
241			goto done;
242		} else
243			retval = retval->next;
244	}
245
246	retval->folderID = fid;
247
248done:
249	return retval;
250}
251
252/*
253 * folderCountAdd - Accounts for given folder record or directory hard link
254 * for folder count of the given parent directory.  For directory hard links,
255 * the folder ID and count should be zero.  For a folder record, the values
256 * read from the catalog record are provided which are used to add the
257 * given folderID to the cache (folderCountInfo *ficp).
258 */
259static int
260folderCountAdd(struct folderCountInfo *fcip, int numFolders, UInt32 parentID, UInt32 folderID, UInt32 count)
261{
262	int retval = 0;
263	struct folderCountInfo *curp = NULL;
264
265
266	/* Only add directories represented by folder record to the cache */
267	if (folderID != 0) {
268		/*
269		 * We track two things here.
270		 * First, we need to find the entry matching this folderID.  If we don't find it,
271		 * we add it.  If we do find it, or if we add it, we set the recordedCount.
272		 */
273
274		curp = findFolderEntry(fcip, numFolders, folderID);
275		if (curp == NULL) {
276			curp = addFolderEntry(fcip, numFolders, folderID);
277			if (curp == NULL) {
278				retval = ENOMEM;
279				goto done;
280			}
281		}
282		curp->recordedCount = count;
283
284	}
285
286	/*
287	 * After that, we try to find the parent to this entry.  When we find it
288	 * (or if we add it to the list), we increment the computedCount.
289	 */
290	curp = findFolderEntry(fcip, numFolders, parentID);
291	if (curp == NULL) {
292		curp = addFolderEntry(fcip, numFolders, parentID);
293		if (curp == NULL) {
294			retval = ENOMEM;
295			goto done;
296		}
297	}
298	curp->computedCount++;
299
300done:
301	return retval;
302}
303
304/*
305 * CheckFolderCount - Verify the folderCount fields of the HFSPlusCatalogFolder records
306 * in the catalog BTree.  This is currently only done for HFSX.
307 *
308 * Conceptually, this is a fairly simple routine:  simply iterate through the catalog
309 * BTree, and count the number of subfolders contained in each folder.  This value
310 * is used for the stat.st_nlink field, on HFSX.
311 *
312 * However, since scanning the entire catalog can be a very costly operation, we dot
313 * it one of two ways.  The first way is to simply iterate through the catalog once,
314 * and keep track of each folder ID we come across.  This uses a fair bit of memory,
315 * so we limit the cache to 5MBytes, which works out to some 400k folderCountInfo
316 * entries (at the current size of three 4-byte entries per folderCountInfo entry).
317 * If the filesystem has more than that, we instead use the slower (but significantly
318 * less memory-intensive) method in CountFolderRecords:  for each folder ID we
319 * come across, we call CountFolderRecords, which does its own iteration through the
320 * catalog, looking for children of the given folder.
321 */
322
323OSErr
324CheckFolderCount( SGlobPtr GPtr )
325{
326	OSErr err = 0;
327	int numFolders;
328	BTreeIterator iterator;
329	FSBufferDescriptor btRecord;
330	HFSPlusCatalogKey *key;
331	union {
332		HFSPlusCatalogFolder catRecord;
333		HFSPlusCatalogFile catFile;
334	} catRecord;
335	UInt16 recordSize = 0;
336	struct folderCountInfo *fcip = NULL;
337
338	ClearMemory(&iterator, sizeof(iterator));
339	if (!VolumeObjectIsHFSX(GPtr)) {
340		goto done;
341	}
342
343	if (GPtr->calculatedVCB == NULL) {
344		err = EINVAL;
345		goto done;
346	}
347
348#if 0
349	/*
350	 * We add two so we can account for the root folder, and
351	 * the root folder's parent.  Neither of which is real,
352	 * but they show up as parent IDs in the catalog.
353	 */
354	numFolders = GPtr->calculatedVCB->vcbFolderCount + 2;
355#else
356	/*
357	 * Since we're using a slightly smarter hash method,
358	 * we don't care so much about the number of folders
359	 * allegedly on the volume; instead, we'll pick a nice
360	 * prime number to use as the number of buckets.
361	 * This bears some performance checking later.
362	 */
363	numFolders = 257;
364#endif
365
366	/*
367	 * Limit the size of the folder count cache to 5Mbytes;
368	 * if the requested number of folders don't fit, then
369	 * we don't use the cache at all.
370	 */
371#define MAXCACHEMEM	(5 * 1024 * 1024)	/* 5Mbytes */
372#define LCALLOC(c, s, l)	\
373	({ __typeof(c) _count = (c); __typeof(s) _size = (s); __typeof(l) _lim = (l); \
374		((_count * _size) > _lim) ? NULL : calloc(_count, _size); })
375
376	fcip = LCALLOC(numFolders, sizeof(*fcip), MAXCACHEMEM);
377#undef MAXCACHEMEM
378#undef LCALLOC
379
380restart:
381	/* these objects are used by the BT* functions to iterate through the catalog */
382	key = (HFSPlusCatalogKey*)&iterator.key;
383	BuildCatalogKey(kHFSRootFolderID, NULL, true, (CatalogKey*)key);
384	btRecord.bufferAddress = &catRecord;
385	btRecord.itemCount = 1;
386	btRecord.itemSize = sizeof(catRecord);
387
388	/*
389	 * Iterate through the catalog BTree until the end.
390	 * For each folder we either cache the value, or we call CheckFolderCount.
391	 * We also check the kHFSHasFolderCountMask flag in the folder flags field;
392	 * if it's not set, we set it.  (When migrating a volume from an older version.
393	 * this will affect every folder entry; after that, it will only affect any
394	 * corrupted areas.)
395	 */
396	for (err = BTIterateRecord(GPtr->calculatedCatalogFCB, kBTreeFirstRecord,
397			&iterator, &btRecord, &recordSize);
398		err == 0;
399		err = BTIterateRecord(GPtr->calculatedCatalogFCB, kBTreeNextRecord,
400			&iterator, &btRecord, &recordSize)) {
401
402		switch (catRecord.catRecord.recordType) {
403		case kHFSPlusFolderRecord:
404			if (!(catRecord.catRecord.flags & kHFSHasFolderCountMask)) {
405				/* RcdHsFldCntErr requests a repair order to fix up the flags field */
406				err = RcdHsFldCntErr( GPtr,
407							E_HsFldCount,
408							catRecord.catRecord.flags | kHFSHasFolderCountMask,
409							catRecord.catRecord.flags,
410							catRecord.catRecord.folderID );
411				if (err != 0)
412					goto done;
413			}
414			if (fcip) {
415				if (folderCountAdd(fcip, numFolders,
416					key->parentID,
417					catRecord.catRecord.folderID,
418					catRecord.catRecord.folderCount)) {
419					/*
420					 * We got an error -- this only happens if folderCountAdd()
421					 * cannot allocate memory for a new node.  In that case, we
422					 * need to bail on the whole cache, and use the slow method.
423					 * This also lets us release the memory, which will hopefully
424					 * let some later allocations succeed.  We restart just after
425					 * the cache was allocated, and start over as if we had never
426					 * allocated a cache in the first place.
427					 */
428					releaseFolderCountInfo(fcip, numFolders);
429					fcip = NULL;
430					goto restart;
431				}
432			} else {
433				err = CountFolderRecords(key, &catRecord.catRecord, GPtr);
434				if (err != 0)
435					goto done;
436			}
437			break;
438		case kHFSPlusFileRecord:
439			/* If this file record is a directory hard link, count
440			 * it towards our folder count calculations.
441			 */
442			if ((catRecord.catFile.flags & kHFSHasLinkChainMask) &&
443			    (catRecord.catFile.userInfo.fdType == kHFSAliasType) &&
444			    (catRecord.catFile.userInfo.fdCreator == kHFSAliasCreator) &&
445			    (key->parentID != GPtr->filelink_priv_dir_id)) {
446			    	/* If we are using folder count cache, account
447				 * for directory hard links by incrementing
448				 * associated parentID in the cache.  If an
449				 * extensive search for catalog is being
450				 * performed, account for directory hard links
451				 * in CountFolderRecords()
452				 */
453			    	if (fcip) {
454					if (folderCountAdd(fcip, numFolders,
455						key->parentID, 0, 0)) {
456						/* See above for why we release & restart */
457						releaseFolderCountInfo(fcip, numFolders);
458						fcip = NULL;
459						goto restart;
460					}
461				}
462			}
463			break;
464		}
465	}
466
467	if (err == btNotFound)
468		err = 0;	// We hit the end of the file, which is okay
469	if (err == 0 && fcip != NULL) {
470		int i;
471
472		/*
473		 * At this point, we are itereating through the cache, looking for
474		 * mis-counts. (If we're not using the cache, then CountFolderRecords has
475		 * already dealt with any miscounts.)
476		 */
477		for (i = 0; i < numFolders; i++) {
478			struct folderCountInfo *curp;
479
480			for (curp = &fcip[i]; curp; curp = curp->next) {
481				if (curp->folderID == 0) {
482					// fplog(stderr, "fcip[%d] has a folderID of 0?\n", i);
483				} else if (curp->folderID == kHFSRootParentID) {
484					// Root's parent doesn't really exist
485					continue;
486				} else {
487					if (curp->recordedCount != curp->computedCount) {
488						/* RcdFCntErr requests a repair order to correct the folder count */
489						err = RcdFCntErr( GPtr,
490									E_FldCount,
491									curp->computedCount,
492									curp->recordedCount,
493									curp->folderID );
494						if (err != 0)
495							goto done;
496					}
497				}
498			}
499		}
500	}
501done:
502	if (fcip) {
503		releaseFolderCountInfo(fcip, numFolders);
504		fcip = NULL;
505	}
506	return err;
507}
508
509/*
510 * CheckCatalogBTree - Verifies the catalog B-tree structure
511 *
512 * Causes CheckCatalogRecord to be called for every leaf record
513 */
514OSErr
515CheckCatalogBTree( SGlobPtr GPtr )
516{
517	OSErr err;
518	int hfsplus;
519
520	gScavGlobals = GPtr;
521	hfsplus = VolumeObjectIsHFSPlus( );
522
523	ClearMemory(&gCIS, sizeof(gCIS));
524	gCIS.parentID = kHFSRootParentID;
525	gCIS.nextCNID = kHFSFirstUserCatalogNodeID;
526
527	if (hfsplus) {
528		/* Initialize check for file hard links */
529        	HardLinkCheckBegin(gScavGlobals, &gCIS.hardLinkRef);
530
531		/* Initialize check for directory hard links */
532		dirhardlink_init(gScavGlobals);
533	}
534
535	GPtr->journal_file_id = GPtr->jib_file_id = 0;
536
537	if (CheckIfJournaled(GPtr, true)) {
538		CatalogName fname;
539		CatalogKey key;
540		CatalogRecord rec;
541		UInt16 recSize;
542		int i;
543
544#define	HFS_JOURNAL_FILE	".journal"
545#define	HFS_JOURNAL_INFO	".journal_info_block"
546
547		fname.ustr.length = strlen(HFS_JOURNAL_FILE);
548		for (i = 0; i < fname.ustr.length; i++)
549			fname.ustr.unicode[i] = HFS_JOURNAL_FILE[i];
550		BuildCatalogKey(kHFSRootFolderID, &fname, true, &key);
551		if (SearchBTreeRecord(GPtr->calculatedCatalogFCB, &key, kNoHint, NULL, &rec, &recSize, NULL) == noErr &&
552			rec.recordType == kHFSPlusFileRecord) {
553			GPtr->journal_file_id = rec.hfsPlusFile.fileID;
554		}
555		fname.ustr.length = strlen(HFS_JOURNAL_INFO);
556		for (i = 0; i < fname.ustr.length; i++)
557			fname.ustr.unicode[i] = HFS_JOURNAL_INFO[i];
558		BuildCatalogKey(kHFSRootFolderID, &fname, true, &key);
559		if (SearchBTreeRecord(GPtr->calculatedCatalogFCB, &key, kNoHint, NULL, &rec, &recSize, NULL) == noErr &&
560			rec.recordType == kHFSPlusFileRecord) {
561			GPtr->jib_file_id = rec.hfsPlusFile.fileID;
562		}
563	}
564
565	/* for compatibility, init these globals */
566	gScavGlobals->TarID = kHFSCatalogFileID;
567	GetVolumeObjectBlockNum( &gScavGlobals->TarBlock );
568
569	/*
570	 * Check out the BTree structure
571	 */
572	err = BTCheck(gScavGlobals, kCalculatedCatalogRefNum, (CheckLeafRecordProcPtr)CheckCatalogRecord);
573	if (err) goto exit;
574
575	if (gCIS.dirCount != gCIS.dirThreads) {
576		RcdError(gScavGlobals, E_IncorrectNumThdRcd);
577		gScavGlobals->CBTStat |= S_Orphan;  /* a directory record is missing */
578		if (fsckGetVerbosity(gScavGlobals->context) >= kDebugLog) {
579			plog ("\t%s: dirCount = %u, dirThread = %u\n", __FUNCTION__, gCIS.dirCount, gCIS.dirThreads);
580		}
581	}
582
583	if (hfsplus && (gCIS.fileCount != gCIS.fileThreads)) {
584		RcdError(gScavGlobals, E_IncorrectNumThdRcd);
585		gScavGlobals->CBTStat |= S_Orphan;
586		if (fsckGetVerbosity(gScavGlobals->context) >= kDebugLog) {
587			plog ("\t%s: fileCount = %u, fileThread = %u\n", __FUNCTION__, gCIS.fileCount, gCIS.fileThreads);
588		}
589	}
590
591	if (!hfsplus && (gCIS.fileThreads != gCIS.filesWithThreads)) {
592		RcdError(gScavGlobals, E_IncorrectNumThdRcd);
593		gScavGlobals->CBTStat |= S_Orphan;
594		if (fsckGetVerbosity(gScavGlobals->context) >= kDebugLog) {
595			plog ("\t%s: fileThreads = %u, filesWithThread = %u\n", __FUNCTION__, gCIS.fileThreads, gCIS.filesWithThreads);
596		}
597	}
598
599	gScavGlobals->calculatedVCB->vcbEncodingsBitmap = gCIS.encodings;
600	gScavGlobals->calculatedVCB->vcbNextCatalogID = gCIS.nextCNID;
601	gScavGlobals->calculatedVCB->vcbFolderCount = gCIS.dirCount - 1;
602	gScavGlobals->calculatedVCB->vcbFileCount = gCIS.fileCount;
603	if (!hfsplus) {
604		gScavGlobals->calculatedVCB->vcbNmRtDirs = gCIS.rootDirCount;
605		gScavGlobals->calculatedVCB->vcbNmFls = gCIS.rootFileCount;
606	}
607
608	/*
609	 * Check out the allocation map structure
610	 */
611	err = BTMapChk(gScavGlobals, kCalculatedCatalogRefNum);
612	if (err) goto exit;
613
614	/*
615	 * Make sure unused nodes in the B-tree are zero filled.
616	 */
617	err = BTCheckUnusedNodes(gScavGlobals, kCalculatedCatalogRefNum, &gScavGlobals->CBTStat);
618	if (err) goto exit;
619
620	/*
621	 * Compare BTree header record on disk with scavenger's BTree header record
622	 */
623	err = CmpBTH(gScavGlobals, kCalculatedCatalogRefNum);
624	if (err) goto exit;
625
626	/*
627	 * Compare BTree map on disk with scavenger's BTree map
628	 */
629	err = CmpBTM(gScavGlobals, kCalculatedCatalogRefNum);
630
631	if (hfsplus) {
632		if (scanflag == 0) {
633			(void) CheckHardLinks(gCIS.hardLinkRef);
634
635			/* If any unrepairable corruption was detected for file
636			 * hard links, stop the verification process by returning
637			 * negative value.
638			 */
639			if (gScavGlobals->CatStat & S_LinkErrNoRepair) {
640				err = -1;
641				goto exit;
642			}
643		}
644	}
645
646 exit:
647	if (hfsplus)
648		HardLinkCheckEnd(gCIS.hardLinkRef);
649
650	return (err);
651}
652
653/*
654 * CheckCatalogRecord - verify a catalog record
655 *
656 * Called in leaf-order for every leaf record in the Catalog B-tree
657 */
658static int
659CheckCatalogRecord(SGlobPtr GPtr, const HFSPlusCatalogKey *key, const CatalogRecord *rec, UInt16 reclen)
660{
661	int 						result = 0;
662	Boolean						isHFSPlus;
663
664	isHFSPlus = VolumeObjectIsHFSPlus( );
665	++gScavGlobals->itemsProcessed;
666
667	if (!isHFSPlus)
668		return CheckCatalogRecord_HFS((HFSCatalogKey *)key, rec, reclen);
669
670	gScavGlobals->CNType = rec->recordType;
671
672	switch (rec->recordType) {
673	case kHFSPlusFolderRecord:
674		++gCIS.dirCount;
675		if (reclen != sizeof(HFSPlusCatalogFolder)){
676			RcdError(gScavGlobals, E_LenDir);
677			result = E_LenDir;
678			break;
679		}
680		if (key->parentID != gCIS.parentID) {
681			result = CaptureMissingThread(key->parentID, key);
682			if (result) break;
683			/* Pretend thread record was there */
684			++gCIS.dirThreads;
685			gCIS.parentID = key->parentID;
686		}
687		result = CheckDirectory(key, (HFSPlusCatalogFolder *)rec);
688		break;
689
690	case kHFSPlusFileRecord:
691		++gCIS.fileCount;
692		if (reclen != sizeof(HFSPlusCatalogFile)){
693			RcdError(gScavGlobals, E_LenFil);
694			result = E_LenFil;
695			break;
696		}
697		if (key->parentID != gCIS.parentID) {
698			result = CaptureMissingThread(key->parentID, key);
699			if (result) break;
700			/* Pretend thread record was there */
701			++gCIS.dirThreads;
702			gCIS.parentID = key->parentID;
703		}
704		result = CheckFile(key, (HFSPlusCatalogFile *)rec);
705		break;
706
707	case kHFSPlusFolderThreadRecord:
708		++gCIS.dirThreads;
709		gCIS.parentID = key->parentID;
710		/* Fall through */
711
712	case kHFSPlusFileThreadRecord:
713		if (rec->recordType == kHFSPlusFileThreadRecord)
714			++gCIS.fileThreads;
715
716		if (reclen > sizeof(HFSPlusCatalogThread) ||
717			reclen < sizeof(HFSPlusCatalogThread) - sizeof(HFSUniStr255)) {
718			RcdError(gScavGlobals, E_LenThd);
719			result = E_LenThd;
720			break;
721		} else if (reclen == sizeof(HFSPlusCatalogThread)) {
722			gScavGlobals->VeryMinorErrorsStat |= S_BloatedThreadRecordFound;
723		}
724		result = CheckThread(key, (HFSPlusCatalogThread *)rec);
725		break;
726
727	default:
728		RcdError(gScavGlobals, E_CatRec);
729		result = E_CatRec;
730	}
731
732	return (result);
733}
734
735/*
736 * CheckCatalogRecord_HFS - verify an HFS catalog record
737 *
738 * Called in leaf-order for every leaf record in the Catalog B-tree
739 */
740static int
741CheckCatalogRecord_HFS(const HFSCatalogKey *key, const CatalogRecord *rec, UInt16 reclen)
742{
743	int result = 0;
744
745	gScavGlobals->CNType = rec->recordType;
746
747	switch (rec->recordType) {
748	case kHFSFolderRecord:
749		++gCIS.dirCount;
750		if (key->parentID == kHFSRootFolderID )
751			++gCIS.rootDirCount;
752		if (reclen != sizeof(HFSCatalogFolder)){
753			RcdError(gScavGlobals, E_LenDir);
754			result = E_LenDir;
755			break;
756		}
757		if (key->parentID != gCIS.parentID) {
758			result = CaptureMissingThread(key->parentID, (HFSPlusCatalogKey *)key);
759			if (result) break;
760			/* Pretend thread record was there */
761			++gCIS.dirThreads;
762			gCIS.parentID = key->parentID;
763		}
764		result = CheckDirectory_HFS(key, (HFSCatalogFolder *)rec);
765		break;
766
767	case kHFSFileRecord:
768		++gCIS.fileCount;
769		if (key->parentID == kHFSRootFolderID )
770			++gCIS.rootFileCount;
771		if (reclen != sizeof(HFSCatalogFile)){
772			RcdError(gScavGlobals, E_LenFil);
773			result = E_LenFil;
774			break;
775		}
776		if (key->parentID != gCIS.parentID) {
777			result = CaptureMissingThread(key->parentID, (HFSPlusCatalogKey *)key);
778			if (result) break;
779			/* Pretend thread record was there */
780			++gCIS.dirThreads;
781			gCIS.parentID = key->parentID;
782		}
783		result = CheckFile_HFS(key, (HFSCatalogFile *)rec);
784		break;
785
786	case kHFSFolderThreadRecord:
787		++gCIS.dirThreads;
788		gCIS.parentID = key->parentID;
789		/* Fall through */
790	case kHFSFileThreadRecord:
791		if (rec->recordType == kHFSFileThreadRecord)
792			++gCIS.fileThreads;
793
794		if (reclen != sizeof(HFSCatalogThread)) {
795			RcdError(gScavGlobals, E_LenThd);
796			result = E_LenThd;
797			break;
798		}
799		result = CheckThread_HFS(key, (HFSCatalogThread *)rec);
800		break;
801
802
803	default:
804		RcdError(gScavGlobals, E_CatRec);
805		result = E_CatRec;
806	}
807
808	return (result);
809}
810
811/*
812 * CheckDirectory - verify a catalog directory record
813 *
814 * Also collects info for later processing.
815 * Called in leaf-order for every directory record in the Catalog B-tree
816 */
817static int
818CheckDirectory(const HFSPlusCatalogKey * key, const HFSPlusCatalogFolder * dir)
819{
820	UInt32 dirID;
821	int result = 0;
822
823	dirID = dir->folderID;
824
825	/* Directory cannot have these two flags set */
826	if ((dir->flags & (kHFSFileLockedMask | kHFSThreadExistsMask)) != 0) {
827		RcdError(gScavGlobals, E_CatalogFlagsNotZero);
828		gScavGlobals->CBTStat |= S_ReservedNotZero;
829	}
830
831	RecordXAttrBits(gScavGlobals, dir->flags, dir->folderID, kCalculatedCatalogRefNum);
832#if DEBUG_XATTR
833	plog ("%s: Record folderID=%d for prime modulus calculations\n", __FUNCTION__, dir->folderID);
834#endif
835
836	if (dirID < kHFSFirstUserCatalogNodeID  &&
837            dirID != kHFSRootFolderID) {
838		RcdError(gScavGlobals, E_InvalidID);
839		return (E_InvalidID);
840	}
841	if (dirID >= gCIS.nextCNID )
842		gCIS.nextCNID = dirID + 1;
843
844	gCIS.encodings |= (u_int64_t)(1ULL << MapEncodingToIndex(dir->textEncoding & 0x7F));
845
846	CheckBSDInfo(key, &dir->bsdInfo, true);
847
848	CheckCatalogName(key->nodeName.length, &key->nodeName.unicode[0], key->parentID, false);
849
850	/* Keep track of the directory inodes found */
851	if (dir->flags & kHFSHasLinkChainMask) {
852	    	gScavGlobals->calculated_dirinodes++;
853	}
854
855	return (result);
856}
857
858/*
859 * CheckFile - verify a HFS+ catalog file record
860 * - sanity check values
861 * - collect info for later processing
862 *
863 * Called in leaf-order for every file record in the Catalog B-tree
864 */
865static int
866CheckFile(const HFSPlusCatalogKey * key, const HFSPlusCatalogFile * file)
867{
868	UInt32 fileID;
869	UInt32 blocks;
870	UInt64 bytes;
871	int result = 0;
872	int islink = 0;
873	int isjrnl = 0;
874	size_t	len;
875	unsigned char filename[256 * 3];
876
877	(void) utf_encodestr(key->nodeName.unicode,
878				key->nodeName.length * 2,
879				filename, &len, sizeof(filename));
880	filename[len] = '\0';
881
882	RecordXAttrBits(gScavGlobals, file->flags, file->fileID, kCalculatedCatalogRefNum);
883#if DEBUG_XATTR
884	plog ("%s: Record fileID=%d for prime modulus calculations\n", __FUNCTION__, file->fileID);
885#endif
886
887	fileID = file->fileID;
888	if (fileID < kHFSFirstUserCatalogNodeID) {
889		RcdError(gScavGlobals, E_InvalidID);
890		result = E_InvalidID;
891		return (result);
892	}
893	if (fileID >= gCIS.nextCNID )
894		gCIS.nextCNID = fileID + 1;
895
896	gCIS.encodings |= (u_int64_t)(1ULL << MapEncodingToIndex(file->textEncoding & 0x7F));
897
898	CheckBSDInfo(key, &file->bsdInfo, false);
899
900	/* check out data fork extent info */
901	result = CheckFileExtents(gScavGlobals, file->fileID, kDataFork, NULL,
902                                file->dataFork.extents, &blocks);
903	if (result != noErr)
904		return (result);
905
906	if (file->dataFork.totalBlocks != blocks) {
907		result = RecordBadAllocation(key->parentID, filename, kDataFork,
908					file->dataFork.totalBlocks, blocks);
909		if (result)
910			return (result);
911	} else {
912		bytes = (UInt64)blocks * (UInt64)gScavGlobals->calculatedVCB->vcbBlockSize;
913		if (file->dataFork.logicalSize > bytes) {
914			result = RecordTruncation(key->parentID, filename, kDataFork,
915					file->dataFork.logicalSize, bytes);
916			if (result)
917				return (result);
918		}
919	}
920	/* check out resource fork extent info */
921	result = CheckFileExtents(gScavGlobals, file->fileID, kRsrcFork, NULL,
922	                          file->resourceFork.extents, &blocks);
923	if (result != noErr)
924		return (result);
925
926	if (file->resourceFork.totalBlocks != blocks) {
927		result = RecordBadAllocation(key->parentID, filename, kRsrcFork,
928					file->resourceFork.totalBlocks, blocks);
929		if (result)
930			return (result);
931	} else {
932		bytes = (UInt64)blocks * (UInt64)gScavGlobals->calculatedVCB->vcbBlockSize;
933		if (file->resourceFork.logicalSize > bytes) {
934			result = RecordTruncation(key->parentID, filename, kRsrcFork,
935					file->resourceFork.logicalSize, bytes);
936			if (result)
937				return (result);
938		}
939	}
940
941	/* Collect indirect link info for later */
942	if (file->userInfo.fdType == kHardLinkFileType  &&
943            file->userInfo.fdCreator == kHFSPlusCreator) {
944		islink = 1;
945		CaptureHardLink(gCIS.hardLinkRef, file);
946	}
947
948	CheckCatalogName(key->nodeName.length, &key->nodeName.unicode[0], key->parentID, false);
949
950	/* Keep track of the directory hard links found */
951	if ((file->flags & kHFSHasLinkChainMask) &&
952	    ((file->userInfo.fdType == kHFSAliasType) ||
953	    (file->userInfo.fdCreator == kHFSAliasCreator)) &&
954		(key->parentID != gScavGlobals->filelink_priv_dir_id &&
955		key->parentID != gScavGlobals->dirlink_priv_dir_id)) {
956	    	gScavGlobals->calculated_dirlinks++;
957		islink = 1;
958	}
959
960	/* For non-journaled filesystems, the cached journal file IDs will be 0 */
961	if (file->fileID &&
962		(file->fileID == gScavGlobals->journal_file_id ||
963		file->fileID == gScavGlobals->jib_file_id)) {
964		isjrnl = 1;
965	}
966
967	if (islink == 0) {
968		if (file->flags & kHFSHasLinkChainMask &&
969			(gScavGlobals->filelink_priv_dir_id != key->parentID &&
970			 gScavGlobals->dirlink_priv_dir_id != key->parentID)) {
971			RepairOrderPtr p;
972			fsckPrint(gScavGlobals->context, E_LinkChainNonLink, file->fileID);
973			p = AllocMinorRepairOrder(gScavGlobals, 0);
974			if (p) {
975				p->type = E_LinkChainNonLink;
976				p->correct = 0;
977				p->incorrect = 0;
978				p->parid = file->fileID;
979				p->hint = 0;
980			} else {
981				result = memFullErr;
982			}
983			gScavGlobals->CatStat |= S_LinkErrRepair;
984		}
985
986		if (((file->bsdInfo.fileMode & S_IFMT) == S_IFREG) &&
987			gScavGlobals->filelink_priv_dir_id != key->parentID &&
988			file->bsdInfo.special.linkCount > 1 &&
989			isjrnl == 0) {
990			RepairOrderPtr p;
991			char badstr[16];
992			fsckPrint(gScavGlobals->context, E_FileLinkCountError, file->fileID);
993			snprintf(badstr, sizeof(badstr), "%u", file->bsdInfo.special.linkCount);
994			fsckPrint(gScavGlobals->context, E_BadValue, "1", badstr);
995
996			p = AllocMinorRepairOrder(gScavGlobals, 0);
997			if (p) {
998				p->type = E_FileLinkCountError;
999				p->correct = 1;
1000				p->incorrect = file->bsdInfo.special.linkCount;
1001				p->parid = file->fileID;
1002				p->hint = 0;
1003			} else {
1004				result = memFullErr;
1005			}
1006			gScavGlobals->CatStat |= S_LinkErrRepair;
1007		}
1008		/*
1009		 * Check for symlinks.
1010		 * Currently, d_check_slink is 0x1000, so -D 0x1000 on the command line.
1011		 */
1012		if ((cur_debug_level & d_check_slink) != 0) {
1013			if (((file->bsdInfo.fileMode & S_IFMT) == S_IFLNK) ||
1014			    file->userInfo.fdType == kSymLinkFileType ||
1015			    file->userInfo.fdCreator == kSymLinkCreator) {
1016				// Okay, it claims to be a symlink, at least somehow.
1017				// Check all the info
1018				if (((file->bsdInfo.fileMode & S_IFMT) != S_IFLNK) ||
1019				    file->userInfo.fdType != kSymLinkFileType ||
1020				    file->userInfo.fdCreator != kSymLinkCreator) {
1021					fsckPrint(gScavGlobals->context, E_BadSymLink, file->fileID);
1022					// Should find a way to print out the path, no?
1023				}
1024				if (file->dataFork.logicalSize > PATH_MAX) {
1025					fsckPrint(gScavGlobals->context, E_BadSymLinkLength, file->fileID, (unsigned int)file->dataFork.logicalSize, (unsigned int)PATH_MAX);
1026					printSymLinkName(gScavGlobals, file->fileID);
1027				} else {
1028					/*
1029					 * Reading is hard.
1030					 * It's made easier by PATH_MAX being so small, so we can assume
1031					 * (for now) that the file is entirely in the 8 extents in the catalog
1032					 * record.  (In most cases, it'll be only one extent; in the worst
1033					 * case, it will only be 2, at least until PATH_MAX is increased.)
1034					 */
1035					uint8_t *dataBuffer = malloc(file->dataFork.totalBlocks * gScavGlobals->calculatedVCB->vcbBlockSize + 1);
1036
1037					if (dataBuffer == NULL) {
1038						plog("Unable to allocate %llu bytes for reading symlink", file->dataFork.logicalSize);
1039					} else {
1040						char *curPtr = (char*)dataBuffer;
1041						size_t nread = 0;
1042						size_t dataLen;
1043						HFSPlusExtentDescriptor *ep = (HFSPlusExtentDescriptor*)&file->dataFork.extents[0];
1044
1045						while (nread < file->dataFork.logicalSize) {
1046							Buf_t *bufp;
1047							int rv = CacheRead(&fscache, ep->startBlock * (off_t)gScavGlobals->calculatedVCB->vcbBlockSize, ep->blockCount * gScavGlobals->calculatedVCB->vcbBlockSize, &bufp);
1048							if (rv) {
1049								abort(); // do something better
1050							}
1051							memcpy(curPtr, bufp->Buffer, bufp->Length);
1052							curPtr += bufp->Length;
1053							nread += bufp->Length;
1054							CacheRelease(&fscache, bufp, 0);
1055						}
1056						dataLen = strnlen((char*)dataBuffer, file->dataFork.totalBlocks * gScavGlobals->calculatedVCB->vcbBlockSize);
1057						if (dataLen != file->dataFork.logicalSize) {
1058							fsckPrint(gScavGlobals->context, E_BadSymLinkLength, file->fileID, (unsigned int)dataLen, (unsigned int)file->dataFork.logicalSize);
1059							printSymLinkName(gScavGlobals, file->fileID);
1060							if (debug)
1061								plog("Symlink for file id %u has bad data length\n", file->fileID);
1062						}
1063						free(dataBuffer);
1064					}
1065				}
1066			}
1067		}
1068	}
1069	if (islink == 1 && file->dataFork.totalBlocks != 0) {
1070		fsckPrint(gScavGlobals->context, E_LinkHasData, file->fileID);
1071		gScavGlobals->CatStat |= S_LinkErrNoRepair;
1072	}
1073
1074	return (result);
1075}
1076
1077/*
1078 * CheckThread - verify a catalog thread
1079 *
1080 * Called in leaf-order for every thread record in the Catalog B-tree
1081 */
1082static int
1083CheckThread(const HFSPlusCatalogKey * key, const HFSPlusCatalogThread * thread)
1084{
1085	int result = 0;
1086
1087	if (key->nodeName.length != 0) {
1088		RcdError(gScavGlobals, E_ThdKey);
1089		return (E_ThdKey);
1090	}
1091
1092	result = CheckCatalogName(thread->nodeName.length, &thread->nodeName.unicode[0],
1093                         thread->parentID, true);
1094	if (result != noErr) {
1095		RcdError(gScavGlobals, E_ThdCN);
1096		return (E_ThdCN);
1097	}
1098
1099	if (key->parentID < kHFSFirstUserCatalogNodeID  &&
1100            key->parentID != kHFSRootParentID  &&
1101            key->parentID != kHFSRootFolderID) {
1102		RcdError(gScavGlobals, E_InvalidID);
1103		return (E_InvalidID);
1104	}
1105
1106	if (thread->parentID == kHFSRootParentID) {
1107		if (key->parentID != kHFSRootFolderID) {
1108			RcdError(gScavGlobals, E_InvalidID);
1109			return (E_InvalidID);
1110		}
1111	} else if (thread->parentID < kHFSFirstUserCatalogNodeID &&
1112	           thread->parentID != kHFSRootFolderID) {
1113		RcdError(gScavGlobals, E_InvalidID);
1114		return (E_InvalidID);
1115	}
1116
1117	return (0);
1118}
1119
1120/*
1121 * CheckDirectory - verify an HFS catalog directory record
1122 *
1123 * Also collects info for later processing.
1124 * Called in leaf-order for every directory record in the Catalog B-tree
1125 */
1126static int
1127CheckDirectory_HFS(const HFSCatalogKey * key, const HFSCatalogFolder * dir)
1128{
1129	UInt32 dirID;
1130	int result = 0;
1131
1132	dirID = dir->folderID;
1133
1134	/* Directory cannot have these two flags set */
1135	if ((dir->flags & (kHFSFileLockedMask | kHFSThreadExistsMask)) != 0) {
1136		RcdError(gScavGlobals, E_CatalogFlagsNotZero);
1137		gScavGlobals->CBTStat |= S_ReservedNotZero;
1138	}
1139
1140	if (dirID < kHFSFirstUserCatalogNodeID  &&
1141            dirID != kHFSRootFolderID) {
1142		RcdError(gScavGlobals, E_InvalidID);
1143		return (E_InvalidID);
1144	}
1145	if (dirID >= gCIS.nextCNID )
1146		gCIS.nextCNID = dirID + 1;
1147
1148	CheckCatalogName_HFS(key->nodeName[0], &key->nodeName[1], key->parentID, false);
1149
1150	return (result);
1151}
1152
1153/*
1154 * CheckFile_HFS - verify a HFS catalog file record
1155 * - sanity check values
1156 * - collect info for later processing
1157 *
1158 * Called in b-tree leaf order for every HFS file
1159 * record in the Catalog B-tree.
1160 */
1161static int
1162CheckFile_HFS(const HFSCatalogKey * key, const HFSCatalogFile * file)
1163{
1164	UInt32 fileID;
1165	UInt32 blocks;
1166	char idstr[20];
1167	int result = 0;
1168
1169	if (file->flags & kHFSThreadExistsMask)
1170		++gCIS.filesWithThreads;
1171
1172	/* 3843017 : Check for reserved field removed to support new bits in future */
1173	if ((file->dataStartBlock)          ||
1174	    (file->rsrcStartBlock)          ||
1175	    (file->reserved))
1176	{
1177		RcdError(gScavGlobals, E_CatalogFlagsNotZero);
1178		gScavGlobals->CBTStat |= S_ReservedNotZero;
1179	}
1180
1181	fileID = file->fileID;
1182	if (fileID < kHFSFirstUserCatalogNodeID) {
1183		RcdError(gScavGlobals, E_InvalidID);
1184		result = E_InvalidID;
1185		return (result);
1186	}
1187	if (fileID >= gCIS.nextCNID )
1188		gCIS.nextCNID = fileID + 1;
1189
1190	/* check out data fork extent info */
1191	result = CheckFileExtents(gScavGlobals, file->fileID, kDataFork, NULL,
1192                                file->dataExtents, &blocks);
1193	if (result != noErr)
1194		return (result);
1195	if (file->dataPhysicalSize > ((UInt64)blocks * (UInt64)gScavGlobals->calculatedVCB->vcbBlockSize)) {
1196		snprintf (idstr, sizeof(idstr), "id=%u", fileID);
1197		fsckPrint(gScavGlobals->context, E_PEOF, idstr);
1198		return (noErr);		/* we don't fix this, ignore the error */
1199	}
1200	if (file->dataLogicalSize > file->dataPhysicalSize) {
1201		snprintf (idstr, sizeof(idstr), "id=%u", fileID);
1202		fsckPrint(gScavGlobals->context, E_LEOF, idstr);
1203                return (noErr);		/* we don't fix this, ignore the error */
1204	}
1205
1206	/* check out resource fork extent info */
1207	result = CheckFileExtents(gScavGlobals, file->fileID, kRsrcFork, NULL,
1208				file->rsrcExtents, &blocks);
1209	if (result != noErr)
1210		return (result);
1211	if (file->rsrcPhysicalSize > ((UInt64)blocks * (UInt64)gScavGlobals->calculatedVCB->vcbBlockSize)) {
1212		snprintf (idstr, sizeof(idstr), "id=%u", fileID);
1213		fsckPrint(gScavGlobals->context, E_PEOF, idstr);
1214                return (noErr);		/* we don't fix this, ignore the error */
1215	}
1216	if (file->rsrcLogicalSize > file->rsrcPhysicalSize) {
1217		snprintf (idstr, sizeof(idstr), "id=%u", fileID);
1218		fsckPrint(gScavGlobals->context, E_LEOF, idstr);
1219                return (noErr);		/* we don't fix this, ignore the error */
1220	}
1221#if 1
1222	/* Keeping handle in globals of file ID's for HFS volume only */
1223	if (PtrAndHand(&file->fileID, (Handle)gScavGlobals->validFilesList, sizeof(UInt32) ) )
1224		return (R_NoMem);
1225#endif
1226	CheckCatalogName_HFS(key->nodeName[0], &key->nodeName[1], key->parentID, false);
1227
1228	return (result);
1229}
1230
1231/*
1232 * CheckThread - verify a catalog thread
1233 *
1234 * Called in leaf-order for every thread record in the Catalog B-tree
1235 */
1236static int
1237CheckThread_HFS(const HFSCatalogKey * key, const HFSCatalogThread * thread)
1238{
1239	int result = 0;
1240
1241	if (key->nodeName[0] != 0) {
1242		RcdError(gScavGlobals, E_ThdKey);
1243		return (E_ThdKey);
1244	}
1245
1246	result = CheckCatalogName_HFS(thread->nodeName[0], &thread->nodeName[1],
1247                         	  thread->parentID, true);
1248	if (result != noErr) {
1249		RcdError(gScavGlobals, E_ThdCN);
1250		return (E_ThdCN);
1251	}
1252
1253	if (key->parentID < kHFSFirstUserCatalogNodeID  &&
1254            key->parentID != kHFSRootParentID  &&
1255            key->parentID != kHFSRootFolderID) {
1256		RcdError(gScavGlobals, E_InvalidID);
1257		return (E_InvalidID);
1258	}
1259
1260	if (thread->parentID == kHFSRootParentID) {
1261		if (key->parentID != kHFSRootFolderID) {
1262			RcdError(gScavGlobals, E_InvalidID);
1263			return (E_InvalidID);
1264		}
1265	} else if (thread->parentID < kHFSFirstUserCatalogNodeID &&
1266	           thread->parentID != kHFSRootFolderID) {
1267		RcdError(gScavGlobals, E_InvalidID);
1268		return (E_InvalidID);
1269	}
1270
1271	return (0);
1272}
1273
1274
1275/* File types from BSD Mode */
1276#define FT_MASK    0170000	/* Mask of file type. */
1277#define FT_FIFO    0010000	/* Named pipe (fifo). */
1278#define FT_CHR     0020000	/* Character device. */
1279#define FT_DIR     0040000	/* Directory file. */
1280#define FT_BLK     0060000	/* Block device. */
1281#define FT_REG     0100000	/* Regular file. */
1282#define FT_LNK     0120000	/* Symbolic link. */
1283#define FT_SOCK    0140000	/* BSD domain socket. */
1284
1285/*
1286 * CheckBSDInfo - Check BSD Permissions data
1287 * (HFS Plus volumes only)
1288 *
1289 * if repairable then log the error and create a repair order
1290 */
1291static void
1292CheckBSDInfo(const HFSPlusCatalogKey * key, const HFSPlusBSDInfo * bsdInfo, int isdir)
1293{
1294
1295	Boolean reset = false;
1296
1297	/* skip uninitialized BSD info */
1298	if (bsdInfo->fileMode == 0)
1299		return;
1300
1301	switch (bsdInfo->fileMode & FT_MASK) {
1302	  case FT_DIR:
1303		if (!isdir)
1304			reset = true;
1305		break;
1306	  case FT_REG:
1307	  case FT_BLK:
1308	  case FT_CHR:
1309	  case FT_LNK:
1310	  case FT_SOCK:
1311	  case FT_FIFO:
1312		if (isdir)
1313			reset = true;
1314		break;
1315	  default:
1316		reset = true;
1317	}
1318
1319	if (reset) {
1320		RepairOrderPtr p;
1321		int n;
1322
1323		gScavGlobals->TarBlock = bsdInfo->fileMode & FT_MASK;
1324		RcdError(gScavGlobals, E_InvalidPermissions);
1325
1326		n = CatalogNameSize( (CatalogName *) &key->nodeName, true );
1327
1328		p = AllocMinorRepairOrder(gScavGlobals, n);
1329		if (p == NULL) return;
1330
1331 		CopyCatalogName((const CatalogName *)&key->nodeName,
1332		(CatalogName*)&p->name, true);
1333
1334		p->type      = E_InvalidPermissions;
1335		p->correct   = 0;
1336		p->incorrect = bsdInfo->fileMode;
1337		p->parid = key->parentID;
1338		p->hint = 0;
1339
1340		gScavGlobals->CatStat |= S_Permissions;
1341	}
1342}
1343
1344/*
1345 * Validate a Unicode filename for HFS+ volumes
1346 *
1347 * check character count
1348 * check for illegal names
1349 *
1350 * if repairable then log the error and create a repair order
1351 */
1352static int
1353CheckCatalogName(u_int16_t charCount, const u_int16_t *uniChars, u_int32_t parentID, Boolean thread)
1354{
1355	OSErr 				result;
1356	u_int16_t *			myPtr;
1357	RepairOrderPtr 		roPtr;
1358	int					myLength;
1359    CatalogName			newName;
1360
1361	if ((charCount == 0) || (charCount > kHFSPlusMaxFileNameChars))
1362		return( E_CName );
1363
1364    // only do the remaining checks for files or directories
1365    if ( thread )
1366        return( noErr );
1367
1368    // look for objects with illegal names of "." or "..".  We only do this for
1369    // file or folder catalog records (the thread records will be taken care of
1370    // in the repair routines).
1371    if ( charCount < 3 && *uniChars == 0x2E )
1372    {
1373        if ( charCount == 1 || (charCount == 2 && *(uniChars + 1) == 0x2E) )
1374        {
1375		fsckPrint(gScavGlobals->context, E_IllegalName);
1376            if ( fsckGetVerbosity(gScavGlobals->context) >= kDebugLog ) {
1377               	plog( "\tillegal name is 0x" );
1378                PrintName( charCount, (UInt8 *) uniChars, true );
1379           }
1380
1381            // get a new name to use when we rename the file system object
1382            result = UniqueDotName( gScavGlobals, &newName, parentID,
1383                                    ((charCount == 1) ? true : false), true );
1384            if ( result != noErr )
1385                return( noErr );
1386
1387            // we will copy the old and new names to our RepairOrder.  The names will
1388            // look like this:
1389            // 	 2 byte length of old name
1390            //   unicode characters for old name
1391            //   2 byte length of new name
1392            //   unicode characters for new name
1393            myLength = (charCount + 1) * 2; // bytes needed for old name
1394            myLength += ((newName.ustr.length + 1) * 2); // bytes needed for new name
1395
1396            roPtr = AllocMinorRepairOrder( gScavGlobals, myLength );
1397            if ( roPtr == NULL )
1398                return( noErr );
1399
1400            myPtr = (u_int16_t *) &roPtr->name;
1401            *myPtr++ = charCount; // copy in length of old name and bump past it
1402            CopyMemory( uniChars, myPtr, (charCount * 2) ); // copy in old name
1403            myPtr += charCount; // bump past old name
1404            *myPtr++ = newName.ustr.length; // copy in length of new name and bump past it
1405            CopyMemory( newName.ustr.unicode, myPtr, (newName.ustr.length * 2) ); // copy in new name
1406            if ( fsckGetVerbosity(gScavGlobals->context) >= kDebugLog ) {
1407               	plog( "\treplacement name is 0x" );
1408                PrintName( newName.ustr.length, (UInt8 *) &newName.ustr.unicode, true );
1409           }
1410
1411 			roPtr->type = E_IllegalName;
1412            roPtr->parid = parentID;
1413            gScavGlobals->CatStat |= S_IllName;
1414            return( E_IllegalName );
1415        }
1416    }
1417
1418    // look for Unicode decomposition errors in file system object names created before Jaguar (10.2)
1419    if ( FixDecomps( charCount, uniChars, &newName.ustr ) )
1420    {
1421	fsckPrint(gScavGlobals->context, E_IllegalName);
1422        if ( fsckGetVerbosity(gScavGlobals->context) >= kDebugLog ) {
1423            plog( "\tillegal name is 0x" );
1424            PrintName( charCount, (UInt8 *) uniChars, true );
1425        }
1426
1427        // we will copy the old and new names to our RepairOrder.  The names will
1428        // look like this:
1429        // 	 2 byte length of old name
1430        //   unicode characters for old name
1431        //   2 byte length of new name
1432        //   unicode characters for new name
1433        myLength = (charCount + 1) * 2; // bytes needed for old name
1434        myLength += ((newName.ustr.length + 1) * 2); // bytes needed for new name
1435
1436        roPtr = AllocMinorRepairOrder( gScavGlobals, myLength );
1437        if ( roPtr == NULL )
1438            return( noErr );
1439
1440        myPtr = (u_int16_t *) &roPtr->name;
1441        *myPtr++ = charCount; // copy in length of old name and bump past it
1442        CopyMemory( uniChars, myPtr, (charCount * 2) ); // copy in old name
1443        myPtr += charCount; // bump past old name
1444        *myPtr++ = newName.ustr.length; // copy in length of new name and bump past it
1445        CopyMemory( newName.ustr.unicode, myPtr, (newName.ustr.length * 2) ); // copy in new name
1446        if ( fsckGetVerbosity(gScavGlobals->context) >= kDebugLog ) {
1447            plog( "\treplacement name is 0x" );
1448            PrintName( newName.ustr.length, (UInt8 *) &newName.ustr.unicode, true );
1449        }
1450
1451        roPtr->type = E_IllegalName;
1452        roPtr->parid = parentID;
1453        gScavGlobals->CatStat |= S_IllName;
1454        return( E_IllegalName );
1455    }
1456
1457	return( noErr );
1458}
1459
1460
1461/*
1462 * Validate an HFS filename
1463 *
1464 * check character count
1465 * check for illegal names
1466 *
1467 * if repairable then log the error and create a repair order
1468 */
1469static int
1470CheckCatalogName_HFS(u_int16_t charCount, const u_char *filename, u_int32_t parentID, Boolean thread)
1471{
1472	u_char *			myPtr;
1473	RepairOrderPtr 		roPtr;
1474	int					myLength;
1475    CatalogName			newName;
1476
1477	if ((charCount == 0) || (charCount > kHFSMaxFileNameChars))
1478		return( E_CName );
1479
1480     // only do the remaining checks for files or directories
1481    if ( thread )
1482        return( noErr );
1483
1484    // look for objects with illegal names of "." or "..".  We only do this for
1485    // file or folder catalog records (the thread records will be taken care of
1486    // in the repair routines).
1487    if ( charCount < 3 && *filename == 0x2E )
1488    {
1489        if ( charCount == 1 || (charCount == 2 && *(filename + 1) == 0x2E) )
1490        {
1491            OSErr 				result;
1492	    fsckPrint(gScavGlobals->context, E_IllegalName);
1493            if ( fsckGetVerbosity(gScavGlobals->context) >= kDebugLog ) {
1494               	plog( "\tillegal name is 0x" );
1495                PrintName( charCount, filename, false );
1496            }
1497
1498            // get a new name to use when we rename the file system object
1499            result = UniqueDotName( gScavGlobals, &newName, parentID,
1500                                    ((charCount == 1) ? true : false), false );
1501            if ( result != noErr )
1502                return( noErr );
1503
1504            // we will copy the old and new names to our RepairOrder.  The names will
1505            // look like this:
1506            // 	 1 byte length of old name
1507            //   characters for old name
1508            //   1 byte length of new name
1509            //   characters for new name
1510            myLength = charCount + 1; // bytes needed for old name
1511            myLength += (newName.pstr[0] + 1); // bytes needed for new name
1512            roPtr = AllocMinorRepairOrder( gScavGlobals, myLength );
1513            if ( roPtr == NULL )
1514                return( noErr );
1515
1516            myPtr = (u_char *)&roPtr->name[0];
1517            *myPtr++ = charCount; // copy in length of old name and bump past it
1518            CopyMemory( filename, myPtr, charCount );
1519            myPtr += charCount; // bump past old name
1520            *myPtr++ = newName.pstr[0]; // copy in length of new name and bump past it
1521            CopyMemory( &newName.pstr[1], myPtr, newName.pstr[0] ); // copy in new name
1522            if ( fsckGetVerbosity(gScavGlobals->context) >= kDebugLog ) {
1523               	plog( "\treplacement name is 0x" );
1524                PrintName( newName.pstr[0], &newName.pstr[1], false );
1525            }
1526
1527 			roPtr->type = E_IllegalName;
1528            roPtr->parid = parentID;
1529            gScavGlobals->CatStat |= S_IllName;
1530            return( E_IllegalName );
1531        }
1532    }
1533
1534	return( noErr );
1535}
1536
1537
1538/*------------------------------------------------------------------------------
1539UniqueDotName:  figure out a unique name we can use to rename a file system
1540object that has the illegal name of "." or ".."
1541------------------------------------------------------------------------------*/
1542static OSErr
1543UniqueDotName( 	SGlobPtr GPtr,
1544            	CatalogName * theNewNamePtr,
1545            	UInt32 theParID,
1546                Boolean isSingleDotName,
1547             	Boolean isHFSPlus )
1548{
1549    u_char				newChar;
1550	OSErr 				result;
1551	size_t				nameLen;
1552	UInt16				recSize;
1553	SFCB *				fcbPtr;
1554    u_char *			myPtr;
1555	CatalogRecord		record;
1556    CatalogKey			catKey;
1557    u_char				dotName[] = {'d', 'o', 't', 'd', 'o', 't', 0x0d, 0x00};
1558
1559  	fcbPtr = GPtr->calculatedCatalogFCB;
1560
1561    // create key with new name
1562    if ( isSingleDotName )
1563        myPtr = &dotName[3];
1564    else
1565        myPtr = &dotName[0];
1566
1567	nameLen = strlen((char *) myPtr );
1568    if ( isHFSPlus )
1569    {
1570        int		i;
1571        theNewNamePtr->ustr.length = nameLen;
1572        for ( i = 0; i < theNewNamePtr->ustr.length; i++ )
1573            theNewNamePtr->ustr.unicode[ i ] = (u_int16_t) *(myPtr + i);
1574    }
1575    else
1576    {
1577        theNewNamePtr->pstr[0] = nameLen;
1578        memcpy( &theNewNamePtr->pstr[1], myPtr, nameLen );
1579    }
1580
1581    // if the name is already in use we will try appending ascii characters
1582    // from '0' (0x30) up to '~' (0x7E)
1583    for ( newChar = 0x30; newChar < 0x7F; newChar++ )
1584    {
1585        // make sure new name isn't already there
1586        BuildCatalogKey( theParID, theNewNamePtr, isHFSPlus, &catKey );
1587        result = SearchBTreeRecord( fcbPtr, &catKey, kNoHint, NULL, &record, &recSize, NULL );
1588        if ( result != noErr )
1589            return( noErr );
1590
1591        // new name is already there, try another
1592        if ( isHFSPlus )
1593        {
1594            theNewNamePtr->ustr.unicode[ nameLen ] = (u_int16_t) newChar;
1595            theNewNamePtr->ustr.length = nameLen + 1;
1596        }
1597        else
1598        {
1599            theNewNamePtr->pstr[ 0 ] = nameLen + 1;
1600            theNewNamePtr->pstr[ nameLen + 1 ] = newChar;
1601        }
1602    }
1603
1604    return( -1 );
1605
1606} /* UniqueDotName */
1607
1608/* Function: RecordBadAllocation
1609 *
1610 * Description:
1611 * Record a repair to adjust a file or extended attribute's allocation size.
1612 * This could also trigger a truncation if the new block count isn't large
1613 * enough to cover the current LEOF.
1614 *
1615 * Note that it stores different values and prints different error message
1616 * for file and extended attribute.
1617 * For files -
1618 *	E_PEOF, parentID, filename, forkType (kDataFork/kRsrcFork).
1619 *	Prints filename.
1620 * For extended attributes -
1621 *	E_PEOAttr, fileID, attribute name, forkType (kEAData).
1622 *	Prints attribute name and filename.  Since the attribute name is
1623 *  passed as parameter, it needs to lookup the filename.
1624 *
1625 * Input:
1626 *	For files -
1627 *		parID		- parent ID of file
1628 *		filename	- name of the file
1629 *		forkType	- type of fork (kDataFork/kRsrcFork)
1630 *	For extended attributes -
1631 *		parID		- fileID for attribute
1632 *		filename	- name of the attribute
1633 *		forkType	- kEAData
1634 *	Common inputs -
1635 *		oldBlkCnt 	- Incorrect block count
1636 *		newBlkCnt	- Correct block count
1637 * Output:
1638 *	On success, zero.
1639 *	On failure, non-zero.
1640 *		R_NoMem - out of memory
1641 *		E_PEOF	- Bad allocation error on plain HFS volume
1642 */
1643int
1644RecordBadAllocation(UInt32 parID, unsigned char * filename, UInt32 forkType, UInt32 oldBlkCnt, UInt32 newBlkCnt)
1645{
1646	RepairOrderPtr 			p;
1647	char 					goodstr[16];
1648	char 					badstr[16];
1649	size_t 					n;
1650	Boolean 				isHFSPlus;
1651	int 					result;
1652	char 					*real_filename;
1653	unsigned int			filenamelen;
1654
1655	isHFSPlus = VolumeObjectIsHFSPlus( );
1656	if (forkType == kEAData) {
1657		/* Print attribute name and filename for extended attribute */
1658		filenamelen = NAME_MAX * 3;
1659		real_filename = malloc(filenamelen);
1660		if (!real_filename) {
1661			return (R_NoMem);
1662		}
1663
1664		/* Get the name of the file */
1665		result = GetFileNamePathByID(gScavGlobals, parID, NULL, NULL,
1666					real_filename, &filenamelen, NULL);
1667		if (result) {
1668			/* If error while looking up filename, default to print file ID */
1669			sprintf(real_filename, "id = %u", parID);
1670		}
1671
1672		fsckPrint(gScavGlobals->context, E_PEOAttr, filename, real_filename);
1673		free(real_filename);
1674	} else {
1675		fsckPrint(gScavGlobals->context, E_PEOF, filename);
1676	}
1677	sprintf(goodstr, "%d", newBlkCnt);
1678	sprintf(badstr, "%d", oldBlkCnt);
1679	fsckPrint(gScavGlobals->context, E_BadValue, goodstr, badstr);
1680
1681	/* Only HFS+ is repaired here */
1682	if ( !isHFSPlus )
1683		return (E_PEOF);
1684
1685	n = strlen((char *)filename);
1686	p = AllocMinorRepairOrder(gScavGlobals, n + 1);
1687	if (p == NULL)
1688		return (R_NoMem);
1689
1690	if (forkType == kEAData) {
1691		p->type = E_PEOAttr;
1692	} else {
1693		p->type = E_PEOF;
1694	}
1695	p->forkType = forkType;
1696	p->incorrect = oldBlkCnt;
1697	p->correct = newBlkCnt;
1698	p->hint = 0;
1699	p->parid = parID;
1700	p->name[0] = n;  /* pascal string */
1701	CopyMemory(filename, &p->name[1], n);
1702
1703	gScavGlobals->CatStat |= S_FileAllocation;
1704	return (0);
1705}
1706
1707/* Function: RecordTruncation
1708 *
1709 * Description:
1710 * Record a repair to trucate a file's logical size.
1711 *
1712 * Note that it stores different error values and prints
1713 * different error message for file and extended attribute.
1714 * For files -
1715 *	E_LEOF, parentID, filename, forkType (kDataFork/kRsrcFork).
1716 *	Prints filename.
1717 * For extended attributes -
1718 *	E_LEOAttr, fileID, attribute name, forkType (kEAData).
1719 *	Prints attribute name and filename.  Since the attribute name is
1720 *  passed as parameter, it needs to lookup the filename.
1721 *
1722 * Input:
1723 *	For files -
1724 *		parID		- parent ID of file
1725 *		filename	- name of the file
1726 *		forkType	- type of fork (kDataFork/kRsrcFork)
1727 *	For extended attributes -
1728 *		parID		- fileID for attribute
1729 *		filename	- name of the attribute
1730 *		forkType	- kEAData
1731 *	Common inputs -
1732 *		oldSize 	- Incorrect logical size
1733 *		newSize		- Correct logical size
1734 * Output:
1735 *	On success, zero.
1736 *	On failure, non-zero.
1737 *		R_NoMem - out of memory
1738 *		E_LEOF	- Truncation error on plain HFS volume
1739 */
1740int
1741RecordTruncation(UInt32 parID, unsigned char * filename, UInt32 forkType, UInt64 oldSize, UInt64 newSize)
1742{
1743	RepairOrderPtr	 		p;
1744	char 					oldSizeStr[48];
1745	char 					newSizeStr[48];
1746	size_t 					n;
1747	Boolean 				isHFSPlus;
1748	int 					result;
1749	char 					*real_filename;
1750	unsigned int			filenamelen;
1751
1752	isHFSPlus = VolumeObjectIsHFSPlus( );
1753	if (forkType == kEAData) {
1754		/* Print attribute name and filename for extended attribute */
1755		filenamelen = NAME_MAX * 3;
1756		real_filename = malloc(filenamelen);
1757		if (!real_filename) {
1758			return (R_NoMem);
1759		}
1760
1761		/* Get the name of the file */
1762		result = GetFileNamePathByID(gScavGlobals, parID, NULL, NULL,
1763					real_filename, &filenamelen, NULL);
1764		if (result) {
1765			/* If error while looking up filename, default to print file ID */
1766			sprintf(real_filename, "id = %u", parID);
1767		}
1768
1769		fsckPrint(gScavGlobals->context, E_LEOAttr, filename, real_filename);
1770		free(real_filename);
1771	} else {
1772		fsckPrint(gScavGlobals->context, E_LEOF, filename);
1773	}
1774	sprintf(oldSizeStr, "%qd", oldSize);
1775	sprintf(newSizeStr, "%qd", newSize);
1776	fsckPrint(gScavGlobals->context, E_BadValue, newSizeStr, oldSizeStr);
1777
1778	/* Only HFS+ is repaired here */
1779	if ( !isHFSPlus )
1780		return (E_LEOF);
1781
1782	n = strlen((char *)filename);
1783	p = AllocMinorRepairOrder(gScavGlobals, n + 1);
1784	if (p == NULL)
1785		return (R_NoMem);
1786
1787	if (forkType == kEAData) {
1788		p->type = E_LEOAttr;
1789	} else {
1790		p->type = E_LEOF;
1791	}
1792	p->forkType = forkType;
1793	p->incorrect = oldSize;
1794	p->correct = newSize;
1795	p->hint = 0;
1796	p->parid = parID;
1797	p->name[0] = n;  /* pascal string */
1798	CopyMemory(filename, &p->name[1], n);
1799
1800	gScavGlobals->CatStat |= S_FileAllocation;
1801	return (0);
1802}
1803
1804
1805/*
1806 * CaptureMissingThread
1807 *
1808 * Capture info for a missing thread record so it
1809 * can be repaired later.  The next key is saved
1810 * so that the Catalog Hierarchy check can proceed.
1811 * The thread PID/NAME are initialized during the
1812 * Catalog Hierarchy check phase.
1813 */
1814static int
1815CaptureMissingThread(UInt32 threadID, const HFSPlusCatalogKey *nextKey)
1816{
1817	MissingThread 			*mtp;
1818	Boolean 				isHFSPlus;
1819
1820	isHFSPlus = VolumeObjectIsHFSPlus( );
1821
1822	fsckPrint(gScavGlobals->context, E_NoThd, threadID);
1823
1824	/* Only HFS+ missing threads are repaired here */
1825	if ( !isHFSPlus)
1826		return (E_NoThd);
1827
1828	mtp = (MissingThread *) AllocateClearMemory(sizeof(MissingThread));
1829	if (mtp == NULL)
1830		return (R_NoMem);
1831
1832	/* add it to the list of missing threads */
1833	mtp->link = gScavGlobals->missingThreadList;
1834	gScavGlobals->missingThreadList = mtp;
1835
1836	mtp->threadID = threadID;
1837	CopyMemory(nextKey, &mtp->nextKey, nextKey->keyLength + 2);
1838
1839	if (gScavGlobals->RepLevel == repairLevelNoProblemsFound)
1840		gScavGlobals->RepLevel = repairLevelVolumeRecoverable;
1841
1842	gScavGlobals->CatStat |= S_MissingThread;
1843	return (noErr);
1844}
1845
1846
1847/*
1848 FixDecomps.  Originally written by Peter Edberg for use in fsck_hfs.
1849
1850 If inFilename needs updating and the function was able to do this without
1851 overflowing the 255-character limit, it returns 1 (true) and outFIlename
1852 contains the update file. If inFilename did not need updating, or an update
1853 would overflow the limit, the function returns 0 (false) and the contents of
1854 outFilename are undefined.
1855
1856Function implementation:
1857
1858Characters that don't require any special handling have combining class 0 and do
1859not begin a decomposition sequence (of 1-3 characters) that needs updating. For
1860these characters, the function just copies them from inFilename to outFilename
1861and sets the pointer outNameCombSeqPtr to NULL (when this pointer is not NULL,
1862it points to the beginning of a sequence of combining marks that continues up to
1863the current character; if the current character is combining, it may need to be
1864reordered into that sequence). The copying operation in cheap, and postponing it
1865until we know the filename needs modification would make the code much more
1866complicated.
1867
1868This copying operation may be invoked from many places in the code, some deeply
1869nested - any time the code determines that the current character needs no
1870special handling. For this reason it has a label (CopyBaseChar) and is located
1871at the end of the character processing loop; various places in the code use goto
1872statements to jump to it (this is a situation where they are justified).
1873
1874The main function loop has 4 sections.
1875
1876First, it quickly determines if the high 12 bits of the character indicate that
1877it is in a range that has neither nonzero combining class nor any decomposition
1878sequences that need updating. If so, the code jumps straight to CopyBaseChar.
1879
1880Second, the code determines if the character is part of a sequence that needs
1881updating. It checks if the current character has a corresponding action in the
1882replaceData array. If so, depending on the action, it may need to check for
1883additional matching characters in inFilename. If the sequence of 1-3 characters
1884is successfully matched, then a replacement sequence of 1-3 characters is
1885inserted at the corresponding position in outFilename. While this replacement
1886sequence is being inserted, each character must be checked to see if it has
1887nonzero combining class and needs reordering (some replacement sequences consist
1888entirely of combining characters and may interact with combining characters in
1889the filename before the updated sequence).
1890
1891Third, the code handles characters whose high-order 12 bits indicated that some
1892action was required, but were not part of sequences that needed updating (these
1893may include characters that were examined in the second part but were part of
1894sequences that did not completely match, so there are also goto fallthroughs to
1895this code - labeled CheckCombClass - from the second part). These characters
1896have to be checked for nonzero combining class; if so, they are reordered as
1897necessary. Each time a new nonzero class character is encountered, it is added
1898to outFIlename at the correct point in any active combining character sequence
1899(with other characters in the sequence moved as necessary), so the sequence
1900pointed to by outNameCombSeqPtr is always in correct order up to the current
1901character.
1902
1903Finally, the fourth part has the default handlers to just copy characters to
1904outFilename.
1905
1906 */
1907static Boolean
1908FixDecomps(	u_int16_t charCount, const u_int16_t *inFilename, HFSUniStr255 *outFilename )
1909{
1910    // input filename: address of curr input char,
1911	const u_int16_t *	inNamePtr		= inFilename;
1912    // and of last input char.
1913	const u_int16_t *	inNameLastPtr	= &inFilename[charCount - 1];
1914    // output filename buffer: addr of next output char,
1915	u_int16_t *	outNamePtr			= outFilename->unicode;
1916    // and of last possible output char.
1917	u_int16_t *	outNameLastPtr		= &outFilename->unicode[kHFSPlusMaxFileNameChars - 1];
1918	u_int16_t *	outNameCombSeqPtr	= NULL;	// if non-NULL, start of output combining seq we are processing.
1919	u_int32_t	maxClassValueInSeq	= 0;
1920	Boolean		didModifyName		= 0;
1921
1922	while (inNamePtr <= inNameLastPtr) {
1923		u_int16_t	shiftUniChar;	// this must be 16 bits for the kShiftUniCharOffset wraparound to work
1924		int32_t		rangeIndex;
1925		u_int32_t	shiftUniCharLo;
1926		u_int32_t	replDataIndex;
1927		u_int32_t	currCharClass;
1928
1929		shiftUniChar = *inNamePtr + kShiftUniCharOffset;
1930		if ( shiftUniChar >= kShiftUniCharLimit )
1931			goto CopyBaseChar;
1932		rangeIndex = classAndReplIndex[shiftUniChar >> kLoFieldBitSize];
1933		if ( rangeIndex < 0 )
1934			goto CopyBaseChar;
1935		shiftUniCharLo = shiftUniChar & kLoFieldMask;
1936		replDataIndex = replaceRanges[rangeIndex][shiftUniCharLo];
1937
1938		if ( replDataIndex > 0 ) {
1939			// we have a possible substitution (replDataIndex != 0)
1940			const u_int16_t *	replDataPtr;
1941			u_int32_t			action;
1942			u_int32_t			copyCount	= 0;
1943
1944			replDataPtr = &replaceData[replDataIndex];
1945			action = *replDataPtr++;
1946			switch (action) {
1947				case kReplaceCurWithTwo:
1948				case kReplaceCurWithThree:
1949					inNamePtr++;
1950					copyCount = (action == kReplaceCurWithTwo)? 2: 3;
1951					break;
1952				// the next 3 cases can have a first char or replacement char with nonzero combining class
1953				case kIfNextOneMatchesReplaceAllWithOne:
1954				case kIfNextOneMatchesReplaceAllWithTwo:
1955					if (inNamePtr + 1 <= inNameLastPtr && *(inNamePtr + 1) == *replDataPtr++) {
1956						inNamePtr += 2;
1957						copyCount = (action == kIfNextOneMatchesReplaceAllWithOne)? 1: 2;
1958					} else {
1959						// No substitution; check for comb class & copy char
1960						goto CheckCombClass;
1961					}
1962					break;
1963				case kIfNextTwoMatchReplaceAllWithOne:
1964					if ( inNamePtr + 2 <= inNameLastPtr &&
1965                         *(inNamePtr + 1) == *replDataPtr++ &&
1966                         *(inNamePtr + 2) == *replDataPtr++)
1967                    {
1968						inNamePtr += 3;
1969						copyCount = 1;
1970					} else {
1971						// No substitution; check for comb class & copy char
1972						goto CheckCombClass;
1973					}
1974					break;
1975			}
1976
1977			// now copy copyCount chars (1-3) from replDataPtr to output, checking for comb class etc.
1978			if (outNamePtr + copyCount - 1 > outNameLastPtr) {
1979				didModifyName = 0;
1980				break;
1981			}
1982			while (copyCount-- > 0) {
1983				currCharClass = 0;
1984				shiftUniChar = *replDataPtr + kShiftUniCharOffset;
1985				if ( shiftUniChar < kShiftUniCharLimit ) {
1986					rangeIndex = classAndReplIndex[shiftUniChar >> kLoFieldBitSize];
1987					if (rangeIndex >= 0) {
1988						shiftUniCharLo = shiftUniChar & kLoFieldMask;
1989						currCharClass = combClassRanges[rangeIndex][shiftUniCharLo];
1990					}
1991				}
1992				// This part is similar to CheckCombClass below, which has more detailed
1993				// comments; see them for info.
1994				if ( currCharClass == 0 ) {
1995					outNameCombSeqPtr = NULL;
1996					*outNamePtr++ = *replDataPtr++;
1997				} else if ( outNameCombSeqPtr == NULL ) {
1998					outNameCombSeqPtr = outNamePtr;
1999					maxClassValueInSeq = currCharClass;
2000					*outNamePtr++ = *replDataPtr++;
2001				} else if ( currCharClass >= maxClassValueInSeq ) {
2002					// Sequence is already in correct order with current char,
2003					// just update maxClassValueInSeq
2004					maxClassValueInSeq = currCharClass;
2005					*outNamePtr++ = *replDataPtr++;
2006				} else if ( outNamePtr - outNameCombSeqPtr == 1) {
2007					// Here we know we need to reorder.
2008					// If the sequence is two chars, just interchange them
2009					*outNamePtr++ = *outNameCombSeqPtr;
2010					*outNameCombSeqPtr = *replDataPtr++;
2011				} else {
2012					// General reordering case for three or more chars.
2013					u_int16_t *	outNameCombCharPtr;
2014					u_int32_t	combCharClass;
2015
2016					outNameCombCharPtr = outNamePtr++;
2017					while (outNameCombCharPtr > outNameCombSeqPtr) {
2018						shiftUniChar = *(outNameCombCharPtr - 1) + kShiftUniCharOffset;
2019						rangeIndex = classAndReplIndex[shiftUniChar >> kLoFieldBitSize];
2020						shiftUniCharLo = shiftUniChar & kLoFieldMask;
2021						combCharClass = combClassRanges[rangeIndex][shiftUniCharLo];
2022						if (combCharClass <= currCharClass)
2023							break;
2024						*outNameCombCharPtr = *(outNameCombCharPtr - 1);
2025						outNameCombCharPtr--;
2026					}
2027					*outNameCombCharPtr = *replDataPtr++;
2028				}
2029			}
2030			didModifyName = 1;
2031			continue;
2032		} /* end of replDataIndex > 0 */
2033
2034	CheckCombClass:
2035		// check for combining class
2036		currCharClass = combClassRanges[rangeIndex][shiftUniCharLo];
2037		if ( currCharClass == 0 ) {
2038			goto CopyBaseChar;
2039		}
2040		if ( outNameCombSeqPtr == NULL ) {
2041			// The current char is the first of a possible sequence of chars
2042			// with nonzero combining class. Initialize sequence stuff, then
2043			// just copy char to output.
2044			outNameCombSeqPtr = outNamePtr;
2045			maxClassValueInSeq = currCharClass;
2046			goto CopyChar;
2047		}
2048		if ( currCharClass >= maxClassValueInSeq ) {
2049			// The sequence of chars with nonzero combining class is already
2050			// in correct order through the current char; just update the max
2051			// class value found in the sequence.
2052			maxClassValueInSeq = currCharClass;
2053			goto CopyChar;
2054		}
2055
2056		// This char is at least the second in a sequence of chars with
2057		// nonzero combining class in the output buffer; outNameCombSeqPtr
2058		// points to the first in the sequence. Need to put the current
2059		// char into the correct place in the sequence (previous chars in
2060		// the sequence are already in correct order, but the current char
2061		// is out of place).
2062
2063		// First make sure there is room for the new char
2064		if (outNamePtr > outNameLastPtr) {
2065			didModifyName = 0;
2066			break;
2067		}
2068
2069		if (outNamePtr - outNameCombSeqPtr == 1) {
2070			// If the sequence is two chars, just interchange them
2071			*outNamePtr++ = *outNameCombSeqPtr;
2072			*outNameCombSeqPtr = *inNamePtr++;
2073		} else {
2074			// General case: Starting at previous end of sequence, move chars to
2075			// next position in string as long as their class is higher than current
2076			// char; insert current char where we stop. We could cache the
2077			// combining classes instead of re-determining them, but having multiple
2078			// combining marks is rare enough that it wouldn't be worth the overhead.
2079			// At least we don't have to recheck shiftUniChar < kShiftUniCharLimit,
2080			// rangeIndex != 0, etc.)
2081			u_int16_t *	outNameCombCharPtr;
2082			u_int32_t	combCharClass;
2083
2084			outNameCombCharPtr = outNamePtr++;
2085			while (outNameCombCharPtr > outNameCombSeqPtr) {
2086				shiftUniChar = *(outNameCombCharPtr - 1) + kShiftUniCharOffset;
2087				rangeIndex = classAndReplIndex[shiftUniChar >> kLoFieldBitSize];
2088				shiftUniCharLo = shiftUniChar & kLoFieldMask;
2089				combCharClass = combClassRanges[rangeIndex][shiftUniCharLo];
2090				if (combCharClass <= currCharClass)
2091					break;
2092				*outNameCombCharPtr = *(outNameCombCharPtr - 1);
2093				outNameCombCharPtr--;
2094			}
2095			*outNameCombCharPtr = *inNamePtr++;
2096		}
2097		didModifyName = 1;
2098		continue;
2099
2100	CopyBaseChar:
2101		outNameCombSeqPtr = NULL;
2102	CopyChar:
2103		// nothing special happens with this char, just copy to output
2104		if (outNamePtr <= outNameLastPtr) {
2105			*outNamePtr++ = *inNamePtr++;
2106		} else {
2107			didModifyName = 0;
2108			break;
2109		}
2110	} /* end of while( inNamePtr <= inNameLastPtr ) */
2111
2112	if (didModifyName) {
2113		outFilename->length = outNamePtr - outFilename->unicode;
2114	}
2115	return didModifyName;
2116
2117} /* FixDecomps */
2118
2119