2 * Copyright (c) 2007-2008 Apple Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
24 #include "Scavenger.h"
29 /* Looks up a catalog file/folder record for given file/folder ID.
30 * The functionality of this routine is same as GetCatalogRecord() in
31 * dfalib/SRepair.c, but this implementation is better because it does not
32 * change the lastIterator stored in the catalog BTreeControlBlock.
33 * Therefore this function does not interfere with other catalog btree
36 OSErr
GetCatalogRecordByID(SGlobPtr GPtr
, UInt32 file_id
, Boolean isHFSPlus
, CatalogKey
*key
, CatalogRecord
*rec
, uint16_t *recsize
)
40 BTreeControlBlock
*btcb
;
41 FSBufferDescriptor buf_desc
;
42 BTreeIterator search_iterator
;
43 BTreeIterator result_iterator
;
44 uint32_t thread_key_parentID
= 0;
46 fcb
= GPtr
->calculatedCatalogFCB
;
47 btcb
= (BTreeControlBlock
*)fcb
->fcbBtree
;
49 /* Lookup the thread record with given file/folderID */
50 bzero(&buf_desc
, sizeof(buf_desc
));
51 bzero(&search_iterator
, sizeof(search_iterator
));
52 buf_desc
.bufferAddress
= rec
;
53 buf_desc
.itemCount
= 1;
54 buf_desc
.itemSize
= sizeof(CatalogRecord
);
56 BuildCatalogKey(file_id
, NULL
, isHFSPlus
, (CatalogKey
*)&(search_iterator
.key
));
57 retval
= BTSearchRecord(fcb
, &search_iterator
, kInvalidMRUCacheKey
,
58 &buf_desc
, recsize
, &result_iterator
);
63 /* Check if really we found a thread record */
65 if ((rec
->recordType
!= kHFSPlusFolderThreadRecord
) &&
66 (rec
->recordType
!= kHFSPlusFileThreadRecord
)) {
71 if ((rec
->recordType
!= kHFSFolderThreadRecord
) &&
72 (rec
->recordType
!= kHFSFileThreadRecord
)) {
79 thread_key_parentID
= ((CatalogKey
*)&(result_iterator
.key
))->hfsPlus
.parentID
;
82 /* Lookup the corresponding file/folder record */
83 bzero(&buf_desc
, sizeof(buf_desc
));
84 bzero(&search_iterator
, sizeof(search_iterator
));
85 buf_desc
.bufferAddress
= rec
;
86 buf_desc
.itemCount
= 1;
87 buf_desc
.itemSize
= sizeof(CatalogRecord
);
90 BuildCatalogKey(rec
->hfsPlusThread
.parentID
,
91 (CatalogName
*)&(rec
->hfsPlusThread
.nodeName
),
92 isHFSPlus
, (CatalogKey
*)&(search_iterator
.key
));
94 BuildCatalogKey(rec
->hfsThread
.parentID
,
95 (CatalogName
*)&(rec
->hfsThread
.nodeName
),
96 isHFSPlus
, (CatalogKey
*)&(search_iterator
.key
));
98 retval
= BTSearchRecord(fcb
, &search_iterator
, kInvalidMRUCacheKey
,
99 &buf_desc
, recsize
, &result_iterator
);
104 bcopy(&(result_iterator
.key
), key
, CalcKeySize(btcb
, &(result_iterator
.key
)));
107 /* For catalog file or folder record, the parentID in the thread
108 * record's key should be equal to the fileID in the file/folder
109 * record --- which is equal to the ID of the file/folder record
110 * that is being looked up. If not, mark the volume for repair.
112 if (thread_key_parentID
!= rec
->hfsPlusFile
.fileID
) {
113 RcdError(GPtr
, E_IncorrectNumThdRcd
);
114 if (fsckGetVerbosity(GPtr
->context
) >= kDebugLog
) {
115 plog("\t%s: fileID=%u, thread.key.parentID=%u, record.fileID=%u\n",
116 __FUNCTION__
, file_id
, thread_key_parentID
, rec
->hfsPlusFile
.fileID
);
118 GPtr
->CBTStat
|= S_Orphan
;
126 /* Record minor repair order for invalid permissions for directory hardlink priv dir */
127 static int record_privdir_bad_perm(SGlobPtr gptr
, uint32_t cnid
)
131 RcdError (gptr
, E_BadPermPrivDir
);
132 p
= AllocMinorRepairOrder(gptr
, 0);
137 p
->type
= E_BadPermPrivDir
;
139 gptr
->CatStat
|= S_LinkErrRepair
;
144 /* Record minor repair order for invalid flags for file/directory hard links */
145 int record_link_badflags(SGlobPtr gptr
, uint32_t link_id
, Boolean isdir
,
146 uint32_t incorrect
, uint32_t correct
)
152 fsckPrint(gptr
->context
, isdir
? E_DirLinkBadFlags
: E_FileLinkBadFlags
, link_id
);
153 snprintf(str1
, sizeof(str1
), "0x%x", correct
);
154 snprintf(str2
, sizeof(str2
), "0x%x", incorrect
);
155 fsckPrint(gptr
->context
, E_BadValue
, str1
, str2
);
157 p
= AllocMinorRepairOrder(gptr
, 0);
162 p
->type
= isdir
? E_DirLinkBadFlags
: E_FileLinkBadFlags
;
163 p
->correct
= correct
;
164 p
->incorrect
= incorrect
;
167 gptr
->CatStat
|= S_LinkErrRepair
;
172 /* Record minor repair order for invalid flags for file/directory inode
173 * If a corruption is recorded during verification, do not check for
174 * duplicates as none should exist. If this corruption is recorded
175 * during repair, check for duplicates because before early termination
176 * of verification we might have seen this corruption.
178 int record_inode_badflags(SGlobPtr gptr
, uint32_t inode_id
, Boolean isdir
,
179 uint32_t incorrect
, uint32_t correct
, Boolean check_duplicates
)
185 p
= AllocMinorRepairOrder(gptr
, 0);
190 p
->type
= isdir
? E_DirInodeBadFlags
: E_FileInodeBadFlags
;
191 p
->correct
= correct
;
192 p
->incorrect
= incorrect
;
195 gptr
->CatStat
|= S_LinkErrRepair
;
197 if ((check_duplicates
!= 0) &&
198 (IsDuplicateRepairOrder(gptr
, p
) == 1)) {
199 DeleteRepairOrder(gptr
, p
);
201 fsckPrint(gptr
->context
, isdir
? E_DirInodeBadFlags
: E_FileInodeBadFlags
, inode_id
);
202 snprintf(str1
, sizeof(str1
), "0x%x", correct
);
203 snprintf(str2
, sizeof(str2
), "0x%x", incorrect
);
204 fsckPrint(gptr
->context
, E_BadValue
, str1
, str2
);
210 /* Record minor repair order for invalid parent of directory/file inode */
211 /* XXX -- not repaired yet (file or directory) */
212 static int record_inode_badparent(SGlobPtr gptr
, uint32_t inode_id
, Boolean isdir
,
213 uint32_t incorrect
, uint32_t correct
)
218 fsckPrint(gptr
->context
, isdir
? E_DirInodeBadParent
: E_FileInodeBadParent
, inode_id
);
219 snprintf(str1
, sizeof(str1
), "%u", correct
);
220 snprintf(str2
, sizeof(str2
), "%u", incorrect
);
221 fsckPrint(gptr
->context
, E_BadValue
, str1
, str2
);
223 gptr
->CatStat
|= S_LinkErrNoRepair
;
228 /* Record minor repair order for invalid name of directory inode */
229 /* XXX - not repaired yet (file or directory) */
230 static int record_inode_badname(SGlobPtr gptr
, uint32_t inode_id
,
231 char *incorrect
, char *correct
)
233 fsckPrint(gptr
->context
, E_DirInodeBadName
, inode_id
);
234 fsckPrint(gptr
->context
, E_BadValue
, correct
, incorrect
);
236 gptr
->CatStat
|= S_LinkErrNoRepair
;
241 /* Record corruption for incorrect number of directory hard links and
242 * directory inode, and invalid list of directory hard links
244 void record_link_badchain(SGlobPtr gptr
, Boolean isdir
)
246 int fval
= (isdir
? S_DirHardLinkChain
: S_FileHardLinkChain
);
247 int err
= (isdir
? E_DirHardLinkChain
: E_FileHardLinkChain
);
248 if ((gptr
->CatStat
& fval
) == 0) {
249 fsckPrint(gptr
->context
, err
);
250 gptr
->CatStat
|= fval
;
254 /* Record minor repair for invalid ownerflags for directory hard links.
255 * If corruption is recorded during verification, do not check for
256 * duplicates as none should exist. If this corruption is recorded
257 * during repair, check for duplicates because before early termination
258 * of verification, we might have seen this corruption.
260 int record_dirlink_badownerflags(SGlobPtr gptr
, uint32_t file_id
,
261 uint8_t incorrect
, uint8_t correct
, int check_duplicates
)
267 p
= AllocMinorRepairOrder(gptr
, 0);
272 p
->type
= E_DirHardLinkOwnerFlags
;
273 p
->correct
= correct
;
274 p
->incorrect
= incorrect
;
277 gptr
->CatStat
|= S_LinkErrRepair
;
279 if ((check_duplicates
!= 0) &&
280 (IsDuplicateRepairOrder(gptr
, p
) == 1)) {
281 DeleteRepairOrder(gptr
, p
);
283 fsckPrint(gptr
->context
, E_DirHardLinkOwnerFlags
, file_id
);
284 snprintf(str1
, sizeof(str1
), "0x%x", correct
);
285 snprintf(str2
, sizeof(str2
), "0x%x", incorrect
);
286 fsckPrint(gptr
->context
, E_BadValue
, str1
, str2
);
292 /* Record minor repair for invalid finderInfo for directory hard links */
293 int record_link_badfinderinfo(SGlobPtr gptr
, uint32_t file_id
, Boolean isdir
)
297 p
= AllocMinorRepairOrder(gptr
, 0);
302 p
->type
= isdir
? E_DirHardLinkFinderInfo
: E_FileHardLinkFinderInfo
;
305 gptr
->CatStat
|= (isdir
? S_DirHardLinkChain
: S_FileHardLinkChain
);
307 /* Recording this corruption is being called from both
308 * inode_check() and dirlink_check(). It is possible that
309 * the error we are adding is a duplicate error. Check for
310 * duplicates, and if any duplicates are found delete the new
313 if (IsDuplicateRepairOrder(gptr
, p
) == 1) {
314 DeleteRepairOrder(gptr
, p
);
316 fsckPrint(gptr
->context
, p
->type
, file_id
);
322 /* Record minor repair for invalid flags in one of the parent directories
323 * of a directory hard link.
325 static int record_parent_badflags(SGlobPtr gptr
, uint32_t dir_id
,
326 uint32_t incorrect
, uint32_t correct
)
332 p
= AllocMinorRepairOrder(gptr
, 0);
337 p
->type
= E_DirLinkAncestorFlags
;
338 p
->correct
= correct
;
339 p
->incorrect
= incorrect
;
342 gptr
->CatStat
|= S_LinkErrRepair
;
344 /* This corruption is logged when traversing ancestors of all
345 * directory hard links. Therefore common corrupt ancestors of
346 * directory hard link will result in duplicate repair orders.
347 * Check for duplicates, and if any duplicates are found delete
348 * the new repair order.
350 if (IsDuplicateRepairOrder(gptr
, p
) == 1) {
351 DeleteRepairOrder(gptr
, p
);
353 fsckPrint(gptr
->context
, E_DirLinkAncestorFlags
, dir_id
);
354 snprintf(str1
, sizeof(str1
), "0x%x", correct
);
355 snprintf(str2
, sizeof(str2
), "0x%x", incorrect
);
356 fsckPrint(gptr
->context
, E_BadValue
, str1
, str2
);
362 /* Look up the ".HFS+ Private Directory Data\xd" directory */
363 static int priv_dir_lookup(SGlobPtr gptr
, CatalogKey
*key
, CatalogRecord
*rec
)
367 char *dirname
= HFSPLUS_DIR_METADATA_FOLDER
;
368 CatalogName cat_dirname
;
372 /* Look up the catalog btree record for the private metadata directory */
373 cat_dirname
.ustr
.length
= strlen(dirname
);
374 for (i
= 0; i
< cat_dirname
.ustr
.length
; i
++) {
375 cat_dirname
.ustr
.unicode
[i
] = (u_int16_t
) dirname
[i
];
377 BuildCatalogKey(kHFSRootFolderID
, &cat_dirname
, true, key
);
378 retval
= SearchBTreeRecord (gptr
->calculatedCatalogFCB
, key
, kNoHint
,
379 NULL
, rec
, &recsize
, &hint
);
383 /* This function initializes the directory hard link check by looking up
384 * private directory that stores directory inodes.
386 int dirhardlink_init(SGlobPtr gptr
)
392 /* Check if the volume is HFS+. */
393 if (VolumeObjectIsHFSPlus() == false) {
397 /* Look up the private metadata directory */
398 retval
= priv_dir_lookup(gptr
, &key
, &rec
);
400 gptr
->dirlink_priv_dir_id
= rec
.hfsPlusFolder
.folderID
;
401 gptr
->dirlink_priv_dir_valence
= rec
.hfsPlusFolder
.valence
;
403 gptr
->dirlink_priv_dir_id
= 0;
404 gptr
->dirlink_priv_dir_valence
= 0;
413 /* Check the private directory for directory hard links */
414 static void dirlink_priv_dir_check(SGlobPtr gptr
, HFSPlusCatalogFolder
*rec
,
415 HFSPlusCatalogKey
*key
)
417 /* ownerFlags should have UF_IMMUTABLE and UF_HIDDEN set, and
418 fileMode should have S_ISVTX set */
419 if (((rec
->bsdInfo
.ownerFlags
& UF_IMMUTABLE
) == 0) ||
420 //(((rec->bsdInfo.adminFlags << 16) & UF_HIDDEN) == 0) ||
421 ((rec
->bsdInfo
.fileMode
& S_ISVTX
) == 0)) {
422 record_privdir_bad_perm(gptr
, rec
->folderID
);
426 /* Get the first link ID information for a hard link inode.
427 * For directory inodes, we get it from the extended attribute
428 * of the directory inode; for files, we get it from hl_firstLinkID
429 * Returns - zero if the lookup succeeded with the first link ID
430 * in the pointer provided, and non-zero if the extended attribute
431 * does not exist, or any other error encountered during lookup.
433 int get_first_link_id(SGlobPtr gptr
, CatalogRecord
*inode_rec
, uint32_t inode_id
,
434 Boolean isdir
, uint32_t *first_link_id
)
438 BTreeIterator iterator
;
439 FSBufferDescriptor bt_data
;
440 HFSPlusAttrData
*rec
;
442 u_int8_t attrdata
[FIRST_LINK_XATTR_REC_SIZE
];
443 size_t unicode_bytes
= 0;
445 bzero(&iterator
, sizeof(iterator
));
448 /* Create key for the required attribute */
449 key
= (HFSPlusAttrKey
*)&iterator
.key
;
450 utf_decodestr((unsigned char *)FIRST_LINK_XATTR_NAME
,
451 strlen(FIRST_LINK_XATTR_NAME
), key
->attrName
,
452 &unicode_bytes
, sizeof(key
->attrName
));
453 key
->attrNameLen
= unicode_bytes
/ sizeof(UniChar
);
454 key
->keyLength
= kHFSPlusAttrKeyMinimumLength
+ unicode_bytes
;
456 key
->fileID
= inode_id
;
459 rec
= (HFSPlusAttrData
*)&attrdata
[0];
460 bt_data
.bufferAddress
= rec
;
461 bt_data
.itemSize
= sizeof(attrdata
);
462 bt_data
.itemCount
= 1;
464 retval
= BTSearchRecord(gptr
->calculatedAttributesFCB
, &iterator
, kNoHint
,
465 &bt_data
, NULL
, NULL
);
467 unsigned long link_id
;
469 /* Attribute should be an inline attribute */
470 if (rec
->recordType
!= kHFSPlusAttrInlineData
) {
471 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
472 plog ("\tfirst link EA is not inline for dirinode=%u (found=0x%x)\n", inode_id
, rec
->recordType
);
478 /* Attribute data should be null terminated, attrSize includes
479 * size of the attribute data including the null termination.
481 if (rec
->attrData
[rec
->attrSize
-1] != '\0') {
482 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
483 plog ("\tfirst link EA attrData is not NULL terminated for dirinode=%u\n", inode_id
);
489 /* All characters are numbers in the attribute data */
490 for (i
= 0; i
< rec
->attrSize
-1; i
++) {
491 if (isdigit(rec
->attrData
[i
]) == 0) {
492 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
493 plog ("\tfirst link EA attrData contains non-digit 0x%x for dirinode=%u\n", rec
->attrData
[i
], inode_id
);
500 link_id
= strtoul((char *)&rec
->attrData
[0], NULL
, 10);
501 if (link_id
> UINT32_MAX
) {
502 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
503 plog ("\tfirst link ID=%lu is > UINT32_MAX for dirinode=%u\n", link_id
, inode_id
);
509 *first_link_id
= (uint32_t)link_id
;
510 if (*first_link_id
< kHFSFirstUserCatalogNodeID
) {
511 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
512 plog ("\tfirst link ID=%u is < 16 for dirinode=%u\n", *first_link_id
, inode_id
);
521 if ((inode_rec
!= NULL
) &&
522 (inode_rec
->recordType
== kHFSPlusFileRecord
)) {
523 *first_link_id
= inode_rec
->hfsPlusFile
.hl_firstLinkID
;
524 if (*first_link_id
< kHFSFirstUserCatalogNodeID
) {
525 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
526 plog("\tfirst link ID=%u is < 16 for fileinode=%u\n", *first_link_id
, inode_id
);
537 /* No record or bad record provided, look it up */
538 retval
= GetCatalogRecordByID(gptr
, inode_id
, true, &key
, &rec
, &recsize
);
540 *first_link_id
= rec
.hfsPlusFile
.hl_firstLinkID
;
541 if (rec
.recordType
!= kHFSPlusFileRecord
||
542 *first_link_id
< kHFSFirstUserCatalogNodeID
) {
543 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
544 plog("\tfirst link ID=%u is < 16 for fileinode=%u\n", *first_link_id
, inode_id
);
560 /* Adds the directory inode, and directory hard link pair to the
561 * prime remainder bucket provided. This is based on Chinese Remainder
562 * Theorem, and the buckets are later compared to find if the directory
563 * hard link chains for all directory inodes are valid.
565 void hardlink_add_bucket(PrimeBuckets
*bucket
, uint32_t inode_id
,
566 uint32_t cur_link_id
)
570 num
= ((uint64_t)inode_id
<< 32) | cur_link_id
;
572 add_prime_bucket_uint64(bucket
, num
);
575 /* Structure to store the directory hard link IDs found during doubly linked
576 * list traversal in inode_check()
580 struct link_list
*next
;
583 /* Verifies the inode record. Validates if the flags are set
584 * correctly, parent is the private metadata directory, first link ID
585 * is stored correctly, and the doubly linked * list of hard links is valid.
588 * zero - if no corruption is detected, or the corruption detected is
589 * such that a repair order can be created.
590 * non-zero - if the corruption detected requires complete knowledge of
591 * all the related directory hard links to suggest repair.
593 int inode_check(SGlobPtr gptr
, PrimeBuckets
*bucket
,
594 CatalogRecord
*rec
, CatalogKey
*key
, Boolean isdir
)
598 uint32_t cur_link_id
;
599 uint32_t prev_link_id
;
603 char found_name
[NAME_MAX
];
607 CatalogRecord linkrec
;
611 uint32_t link_ref_num
= 0;
613 struct link_list
*head
= NULL
;
614 struct link_list
*cur
;
616 (void) utf_encodestr(key
->hfsPlus
.nodeName
.unicode
, key
->hfsPlus
.nodeName
.length
* 2,
617 (unsigned char *)found_name
, &found_len
, NAME_MAX
);
618 found_name
[found_len
] = '\0';
621 inode_id
= rec
->hfsPlusFolder
.folderID
;
622 flags
= rec
->hfsPlusFolder
.flags
;
623 linkCount
= rec
->hfsPlusFolder
.bsdInfo
.special
.linkCount
;
624 parentid
= gptr
->dirlink_priv_dir_id
;
626 unsigned long ref_num
;
628 inode_id
= rec
->hfsPlusFile
.fileID
;
629 flags
= rec
->hfsPlusFile
.flags
;
630 linkCount
= rec
->hfsPlusFile
.bsdInfo
.special
.linkCount
;
631 parentid
= gptr
->filelink_priv_dir_id
;
632 ref_num
= strtoul(&found_name
[strlen(HFS_INODE_PREFIX
)], NULL
, 10);
633 if (ref_num
> UINT32_MAX
) {
634 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
635 plog ("\tlink reference num=%lu is > UINT32_MAX for inode=%u\n", ref_num
, inode_id
);
640 link_ref_num
= (uint32_t)ref_num
;
643 /* inode should only reside in its corresponding private directory */
644 if ((parentid
!= 0) && (key
->hfsPlus
.parentID
!= parentid
)) {
645 (void) record_inode_badparent(gptr
, inode_id
, isdir
, key
->hfsPlus
.parentID
, parentid
);
648 /* Compare the names for directory inode only because the names
649 * of file inodes can have random number suffixed.
652 (void) snprintf(calc_name
, sizeof(calc_name
), "%s%u", HFS_DIRINODE_PREFIX
, inode_id
);
653 calc_len
= strlen(calc_name
);
655 if ((found_len
!= calc_len
) ||
656 (strncmp(calc_name
, found_name
, calc_len
) != 0)) {
657 (void) record_inode_badname(gptr
, inode_id
, found_name
,
662 /* At least one hard link should always point at an inode. */
663 if (linkCount
== 0) {
664 record_link_badchain(gptr
, isdir
);
665 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
666 plog ("\tlinkCount=0 for dirinode=%u\n", inode_id
);
672 /* A directory inode should always have kHFSHasLinkChainBit
673 * set. A file inode created on pre-Leopard OS does not have
674 * kHFSHasLinkChainBit set and firstLinkID is zero. Therefore
675 * ignore such file inodes from CRT check and instead add the
676 * the inode to hash used for checking link count.
678 if ((flags
& kHFSHasLinkChainMask
) == 0) {
679 if ((isdir
) || (!isdir
&& (rec
->hfsPlusFile
.hl_firstLinkID
!= 0))) {
680 (void) record_inode_badflags(gptr
, inode_id
, isdir
,
681 flags
, flags
| kHFSHasLinkChainMask
, false);
683 filelink_hash_inode(link_ref_num
, linkCount
);
689 /* Lookup the ID of first link from the extended attribute */
690 retval
= get_first_link_id(gptr
, rec
, inode_id
, isdir
, &cur_link_id
);
692 record_link_badchain(gptr
, isdir
);
693 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
694 plog ("\tError getting first link ID for inode=%u\n", inode_id
);
699 /* Check doubly linked list of hard links that point to this inode */
703 while (cur_link_id
!= 0) {
704 /* Lookup the current directory link record */
705 retval
= GetCatalogRecordByID(gptr
, cur_link_id
, true,
706 &linkkey
, &linkrec
, &recsize
);
708 record_link_badchain(gptr
, isdir
);
709 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
710 plog ("\tError getting link=%u for inode=%u\n", cur_link_id
, inode_id
);
715 /* Hard link is a file record */
716 if (linkrec
.recordType
!= kHFSPlusFileRecord
) {
717 record_link_badchain(gptr
, isdir
);
718 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
719 plog ("\tIncorrect record type for link=%u for inode=%u (expected=2, found=%u)\n", cur_link_id
, inode_id
, linkrec
.recordType
);
725 /* Hard link should have hard link bit set */
726 if ((linkrec
.hfsPlusFile
.flags
& kHFSHasLinkChainMask
) == 0) {
727 (void) record_link_badchain(gptr
, isdir
);
728 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
729 plog ("\tIncorrect flag for link=%u for inode=%u (found=0x%x)\n", cur_link_id
, inode_id
, linkrec
.hfsPlusFile
.flags
);
736 /* Check if the hard link has correct finder info */
737 if ((linkrec
.hfsPlusFile
.userInfo
.fdType
!= kHFSAliasType
) ||
738 (linkrec
.hfsPlusFile
.userInfo
.fdCreator
!= kHFSAliasCreator
) ||
739 ((linkrec
.hfsPlusFile
.userInfo
.fdFlags
& kIsAlias
) == 0)) {
740 record_link_badfinderinfo(gptr
, linkrec
.hfsPlusFile
.fileID
, isdir
);
741 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
742 plog("\tdirlink: fdType = 0x%08lx, fdCreator = 0x%08lx\n",
743 (unsigned long)linkrec
.hfsPlusFile
.userInfo
.fdType
,
744 (unsigned long)linkrec
.hfsPlusFile
.userInfo
.fdCreator
);
748 /* Check if hard link points to the current inode */
749 if (linkrec
.hfsPlusFile
.hl_linkReference
!= inode_id
) {
750 record_link_badchain(gptr
, isdir
);
751 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
752 plog ("\tIncorrect dirinode ID for dirlink=%u (expected=%u, found=%u)\n", cur_link_id
, inode_id
, linkrec
.hfsPlusFile
.hl_linkReference
);
759 /* Check if the hard link has correct finder info */
760 if ((linkrec
.hfsPlusFile
.userInfo
.fdType
!= kHardLinkFileType
) ||
761 (linkrec
.hfsPlusFile
.userInfo
.fdCreator
!= kHFSPlusCreator
)) {
762 record_link_badfinderinfo(gptr
, linkrec
.hfsPlusFile
.fileID
, isdir
);
763 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
764 plog("\tfilelink: fdType = 0x%08lx, fdCreator = 0x%08lx\n",
765 (unsigned long)linkrec
.hfsPlusFile
.userInfo
.fdType
,
766 (unsigned long)linkrec
.hfsPlusFile
.userInfo
.fdCreator
);
770 /* Check if hard link has correct link reference number */
771 if (linkrec
.hfsPlusFile
.hl_linkReference
!= link_ref_num
) {
772 record_link_badchain(gptr
, isdir
);
773 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
774 plog ("\tIncorrect link reference number for filelink=%u (expected=%u, found=%u)\n", cur_link_id
, inode_id
, linkrec
.hfsPlusFile
.hl_linkReference
);
781 /* For directory hard links, add the directory inode ID and
782 * the current link ID pair to the prime bucket. For file
783 * hard links, add the link reference number and current
784 * link ID pair to the prime bucket.
787 hardlink_add_bucket(bucket
, inode_id
, cur_link_id
);
789 hardlink_add_bucket(bucket
, link_ref_num
, cur_link_id
);
792 /* Check the previous directory hard link */
793 if (prev_link_id
!= linkrec
.hfsPlusFile
.hl_prevLinkID
) {
794 record_link_badchain(gptr
, isdir
);
795 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
796 plog ("\tIncorrect prevLinkID for link=%u for inode=%u (expected=%u, found=%u)\n", cur_link_id
, inode_id
, prev_link_id
, linkrec
.hfsPlusFile
.hl_prevLinkID
);
802 /* Check if we saw this directory hard link previously */
805 if (cur
->link_id
== cur_link_id
) {
806 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
807 plog ("\tDuplicate link=%u found in list for inode=%u\n", cur_link_id
, inode_id
);
809 record_link_badchain(gptr
, isdir
);
816 /* Add the new unique directory hard link to our list */
817 cur
= malloc(sizeof(struct link_list
));
822 cur
->link_id
= cur_link_id
;
827 prev_link_id
= cur_link_id
;
828 cur_link_id
= linkrec
.hfsPlusFile
.hl_nextLinkID
;
831 /* If the entire chain looks good, match the link count */
832 if (linkCount
!= count
) {
833 record_link_badchain(gptr
, isdir
);
834 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
835 plog ("\tIncorrect linkCount for inode=%u (expected=%u, found=%u)\n", inode_id
, count
, linkCount
);
842 /* Free memory used for checking duplicates in the doubly linked list */
853 /* Check if the parent ancestors starting at the given directory has
854 * the kHFSHasChildLinkBit set. This bit indicates that a descendant of
855 * this directory is a directory hard link. Note that the root folder
856 * and the "private directory data" directory does not have this bit
857 * set, and the check stops as soon as we encounter one of these
860 static void check_dirlink_ancestors(SGlobPtr gptr
, uint32_t dir_id
)
867 while ((dir_id
!= kHFSRootFolderID
) && (dir_id
!= gptr
->dirlink_priv_dir_id
)) {
868 retval
= GetCatalogRecordByID(gptr
, dir_id
, true, &key
, &rec
, &recsize
);
873 if (rec
.recordType
!= kHFSPlusFolderRecord
) {
877 if ((rec
.hfsPlusFolder
.flags
& kHFSHasChildLinkMask
) == 0) {
878 (void) record_parent_badflags(gptr
, dir_id
,
879 rec
.hfsPlusFolder
.flags
,
880 rec
.hfsPlusFolder
.flags
| kHFSHasChildLinkMask
);
883 dir_id
= key
.hfsPlus
.parentID
;
886 /* If there was any problem in looking up parent directory,
887 * the catalog check should have also detected the problem.
888 * But there are cases which are not detected like names in
889 * thread record and file/folder record key do not match.
890 * Therefore force repair for incorrect number of thread
891 * records if lookup fails.
893 if ((dir_id
!= kHFSRootFolderID
) && (dir_id
!= gptr
->dirlink_priv_dir_id
)) {
894 fsckPrint(gptr
->context
, E_BadParentHierarchy
, dir_id
);
895 gptr
->CBTStat
|= S_Orphan
;
901 /* Verifies the directory hard link record. Validates if the flags are set
902 * correctly, the finderInfo fields are correct, and if the parent hierarchy
903 * till the root folder (except the root folder) has the kHFSHasChildLinkBit
904 * set correctly. This function also add the directory inode, and the
905 * directory hard link pair to the prime buckets for comparison later.
907 * This function does not verify the first and the next directory hard link
908 * pointers in the doubly linked list because the check is already done
909 * in directory inode check (inode_check()) . Any orphan directory
910 * hard link will also be detected later by the prime bucket comparison.
912 static void dirlink_check(SGlobPtr gptr
, PrimeBuckets
*bucket
,
913 HFSPlusCatalogFile
*rec
, HFSPlusCatalogKey
*key
, Boolean isdir
)
915 /* Add this directory hard link and corresponding inode number pair
918 #if DEBUG_HARDLINKCHECK
919 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
)
920 plog("link_check: adding <%u, %u>\n", rec
->hl_linkReference
, rec
->fileID
);
923 hardlink_add_bucket(bucket
, rec
->hl_linkReference
, rec
->fileID
);
925 /* Check if the directory hard link has UF_IMMUTABLE bit set */
926 if ((rec
->bsdInfo
.ownerFlags
& UF_IMMUTABLE
) == 0) {
927 record_dirlink_badownerflags(gptr
, rec
->fileID
,
928 rec
->bsdInfo
.ownerFlags
,
929 rec
->bsdInfo
.ownerFlags
| UF_IMMUTABLE
, false);
932 /* Check Finder Info */
933 if ((rec
->userInfo
.fdType
!= kHFSAliasType
) ||
934 (rec
->userInfo
.fdCreator
!= kHFSAliasCreator
) ||
935 ((rec
->userInfo
.fdFlags
& kIsAlias
) == 0)) {
936 record_link_badfinderinfo(gptr
, rec
->fileID
, isdir
);
939 /* XXX - Check resource fork/alias data */
941 /* Check if all the parent directories have the kHFSHasChildLinkBit set */
942 check_dirlink_ancestors(gptr
, key
->parentID
);
945 /* Searches the next child directory record to return given the parent ID
946 * and the current child ID. If the current child ID is zero, this is the
947 * first time we are looking up this directory, therefore return the
948 * first child directory or directory hard link found. If child ID is
949 * non-zero, return the first child directory or directory hard
950 * link found after the current child record.
952 * For normal directories, the folder ID is returned as the new child inode_id
953 * and catalog_id. For directory hard links, the inode_id of the directory
954 * inode is returned in the inode_id, and the fileID of the directory hard link
955 * is returned in the catalog_id. If the inode_id returned corresponds to a
956 * directory inode, is_dirinode is set to true. If no child record is found,
957 * or an error occurred on btree traversal, these values are zero.
960 * zero - on successfully determining if the next child record exists
962 * non-zero - error, like during btree lookup, etc.
964 static int find_next_child_dir(SGlobPtr gptr
, uint32_t parent_id
,
965 uint32_t cur_child_catalog_id
, uint32_t *child_inode_id
,
966 uint32_t *child_catalog_id
, uint32_t *is_dirinode
)
970 int return_next_rec
= true;
971 BTreeIterator iterator
;
972 FSBufferDescriptor buf_desc
;
978 *child_catalog_id
= 0;
979 *is_dirinode
= false;
981 fcb
= gptr
->calculatedCatalogFCB
;
982 key
= (CatalogKey
*)&iterator
.key
;
984 /* If no child record for this parent has been looked up previously,
985 * return the first child record found. Otherwise lookup the
986 * catalog record for the last child ID provided and return the
987 * next valid child ID. If the lookup of the last child failed,
988 * fall back to iterating all child records for given parent
989 * directory and returning next child found after given child ID.
991 if (cur_child_catalog_id
== 0) {
993 /* Lookup catalog record with key containing given parent ID and NULL
994 * name. This will place iterator just before the first child record
995 * for this directory.
997 bzero(&iterator
, sizeof(iterator
));
998 bzero(&buf_desc
, sizeof(buf_desc
));
999 buf_desc
.bufferAddress
= &rec
;
1000 buf_desc
.itemCount
= 1;
1001 buf_desc
.itemSize
= sizeof(rec
);
1002 BuildCatalogKey(parent_id
, NULL
, true, key
);
1003 retval
= BTSearchRecord(fcb
, &iterator
, kNoHint
, &buf_desc
, &recsize
,
1005 if ((retval
!= 0) && (retval
!= btNotFound
)) {
1009 /* Lookup the thread record for the last child seen */
1010 bzero(&iterator
, sizeof(iterator
));
1011 bzero(&buf_desc
, sizeof(buf_desc
));
1012 buf_desc
.bufferAddress
= &rec
;
1013 buf_desc
.itemCount
= 1;
1014 buf_desc
.itemSize
= sizeof(rec
);
1015 BuildCatalogKey(cur_child_catalog_id
, NULL
, true, key
);
1016 retval
= BTSearchRecord(fcb
, &iterator
, kNoHint
, &buf_desc
,
1017 &recsize
, &iterator
);
1019 return_next_rec
= false;
1020 goto iterate_parent
;
1023 /* Check if really we found a thread record */
1024 if ((rec
.recordType
!= kHFSPlusFolderThreadRecord
) &&
1025 (rec
.recordType
!= kHFSPlusFileThreadRecord
)) {
1026 return_next_rec
= false;
1027 goto iterate_parent
;
1030 /* Lookup the corresponding file/folder record */
1031 bzero(&iterator
, sizeof(iterator
));
1032 bzero(&buf_desc
, sizeof(buf_desc
));
1033 buf_desc
.bufferAddress
= &rec
;
1034 buf_desc
.itemCount
= 1;
1035 buf_desc
.itemSize
= sizeof(rec
);
1036 BuildCatalogKey(rec
.hfsPlusThread
.parentID
,
1037 (CatalogName
*)&(rec
.hfsPlusThread
.nodeName
),
1038 true, (CatalogKey
*)&(iterator
.key
));
1039 retval
= BTSearchRecord(fcb
, &iterator
, kInvalidMRUCacheKey
,
1040 &buf_desc
, &recsize
, &iterator
);
1042 return_next_rec
= false;
1043 goto iterate_parent
;
1047 /* Lookup the next record */
1048 retval
= BTIterateRecord(fcb
, kBTreeNextRecord
, &iterator
, &buf_desc
,
1050 while (retval
== 0) {
1051 /* Not the same parent anymore, stop the search */
1052 if (key
->hfsPlus
.parentID
!= parent_id
) {
1056 if (rec
.recordType
== kHFSPlusFolderRecord
) {
1057 /* Found a catalog folder record, and if we are
1058 * supposed to return the next record found, return
1059 * this catalog folder.
1061 if (return_next_rec
) {
1062 if (rec
.hfsPlusFolder
.flags
& kHFSHasLinkChainMask
) {
1063 *is_dirinode
= true;
1065 *child_inode_id
= rec
.hfsPlusFolder
.folderID
;
1066 *child_catalog_id
= rec
.hfsPlusFolder
.folderID
;
1069 /* If the current record is the current child, we
1070 * have to return the next child record.
1072 if (rec
.hfsPlusFolder
.folderID
== cur_child_catalog_id
) {
1073 return_next_rec
= true;
1075 } else if (rec
.recordType
== kHFSPlusFileRecord
) {
1076 /* Check if the hard link bit is set with correct
1077 * alias type/creator. If the parent is private
1078 * metadata directory for file hard links, this
1079 * is a hard link inode for an alias, and not
1080 * directory hard link. Skip this file from our
1083 if ((rec
.hfsPlusFile
.flags
& kHFSHasLinkChainMask
) &&
1084 (rec
.hfsPlusFile
.userInfo
.fdType
== kHFSAliasType
) &&
1085 (rec
.hfsPlusFile
.userInfo
.fdCreator
== kHFSAliasCreator
) &&
1086 (key
->hfsPlus
.parentID
!= gptr
->filelink_priv_dir_id
)) {
1087 /* Found a directory hard link, and if we are
1088 * supposed to return the next record found,
1089 * then return this directory hard link.
1091 if (return_next_rec
) {
1092 *child_inode_id
= rec
.hfsPlusFile
.hl_linkReference
;
1093 *child_catalog_id
= rec
.hfsPlusFile
.fileID
;
1094 *is_dirinode
= true;
1097 /* If the current record is the current child,
1098 * we have to return the next child record.
1100 if (rec
.hfsPlusFile
.fileID
== cur_child_catalog_id
) {
1101 return_next_rec
= true;
1106 /* Lookup the next record */
1107 retval
= BTIterateRecord(fcb
, kBTreeNextRecord
, &iterator
,
1108 &buf_desc
, &recsize
);
1111 if (retval
== btNotFound
) {
1119 /* In-memory state for depth first traversal for finding loops in
1120 * directory hierarchy. inode_id is the user visible ID of the given
1121 * directory or directory hard link, and catalog_id is the inode ID for
1122 * normal directories, and the directory hard link ID (file ID of the
1123 * directory hard link record).
1125 * The inode_id is used for checking loops in the hierarchy, whereas
1126 * the catalog_id is used to maintain state for depth first traversal.
1130 uint32_t catalog_id
;
1135 struct dfs_id
*idptr
;
1138 /* Assuming that the name of a directory is single byte, the maximum depth
1139 * of a directory hierarchy that can accommodate in PATH_MAX will be
1140 * PATH_MAX/2. Note that catalog hierarchy check puts limitation of 100
1141 * on the maximum depth of a directory hierarchy.
1143 #define DIRLINK_DEFAULT_DFS_MAX_DEPTH PATH_MAX/2
1145 /* Check if the current directory exists in the current traversal path.
1146 * If yes, loops in directory exists and return non-zero value. If not,
1149 static int check_loops(struct dfs_stack
*dfs
, struct dfs_id id
)
1154 for (i
= 0; i
< dfs
->depth
; i
++) {
1155 if (dfs
->idptr
[i
].inode_id
== id
.inode_id
) {
1164 static void print_dfs(struct dfs_stack
*dfs
)
1169 for (i
= 0; i
< dfs
->depth
; i
++) {
1170 plog ("(%u,%u) ", dfs
->idptr
[i
].inode_id
, dfs
->idptr
[i
].catalog_id
);
1175 /* Store information about visited directory inodes such that we do not
1176 * reenter the directory multiple times while following directory hard links.
1178 struct visited_dirinode
{
1179 uint32_t *list
; /* Pointer to array of IDs */
1180 uint32_t size
; /* Maximum number of entries in the array */
1181 uint32_t offset
; /* Offset where next ID will be added */
1182 uint32_t wrapped
; /* Boolean, true if list wraps around */
1185 /* Add the given dirinode_id to the list of visited nodes. If all the slots
1186 * in visited list are used, wrap around and add the new ID.
1188 static void mark_dirinode_visited(uint32_t dirinode_id
, struct visited_dirinode
*visited
)
1190 if (visited
->list
== NULL
) {
1194 if (visited
->offset
>= visited
->size
) {
1195 visited
->offset
= 0;
1196 visited
->wrapped
= true;
1198 visited
->list
[visited
->offset
] = dirinode_id
;
1202 /* Check if given directory inode exists in the visited list or not */
1203 static int is_dirinode_visited(uint32_t dirinode_id
, struct visited_dirinode
*visited
)
1205 int is_visited
= false;
1206 uint32_t end_offset
;
1209 if (visited
->list
== NULL
) {
1213 /* If the list had wrapped, search the entire list */
1214 if (visited
->wrapped
== true) {
1215 end_offset
= visited
->size
;
1217 end_offset
= visited
->offset
;
1220 for (off
= 0; off
< end_offset
; off
++) {
1221 if (visited
->list
[off
] == dirinode_id
) {
1230 /* Check if there are any loops in the directory hierarchy.
1232 * This function performs a depth first traversal of directories as they
1233 * will be visible to the user. If the lookup of private metadata directory
1234 * succeeded in dirlink_init(), the traversal starts from the private
1235 * metadata directory. Otherwise it starts at the root folder. It stores
1236 * the current depth first traversal state, and looks up catalog records as
1237 * required. The current traversal state consists of two IDs, the user
1238 * visible ID or inode_id, and the on-disk ID or catalog_id. For normal
1239 * directories, the user visible ID is same as the on-disk ID, but for
1240 * directory hard links, the user visible ID is the inode ID, and the
1241 * on-disk ID is the file ID of the directory hard link. This function
1242 * stores the list of visited directory inode ID and checks the list before
1243 * traversing down the directory inode hierarchy. After traversing down a
1244 * directory inode and checking that is valid, it adds the directory inode
1245 * ID to the visited list.
1247 * The inode_id is used for checking loops in the hierarchy, whereas
1248 * the catalog_id is used to maintain state for depth first traversal.
1251 * zero - if the check was performed successfully, and no loops exist
1252 * in the directory hierarchy.
1253 * non-zero - on error, or if loops were detected in directory hierarchy.
1255 static int check_hierarchy_loops(SGlobPtr gptr
)
1258 struct dfs_stack dfs
;
1259 struct dfs_id unknown_child
;
1260 struct dfs_id child
;
1261 struct dfs_id parent
;
1262 struct visited_dirinode visited
;
1263 size_t max_alloc_depth
= DIRLINK_DEFAULT_DFS_MAX_DEPTH
;
1264 uint32_t is_dirinode
;
1266 #define DFS_PUSH(dfsid) \
1268 dfs.idptr[dfs.depth].inode_id = dfsid.inode_id; \
1269 dfs.idptr[dfs.depth].catalog_id = dfsid.catalog_id; \
1271 if (dfs.depth == max_alloc_depth) { \
1272 void *tptr = realloc(dfs.idptr, (max_alloc_depth + DIRLINK_DEFAULT_DFS_MAX_DEPTH) * sizeof(struct dfs_id)); \
1273 if (tptr == NULL) { \
1277 max_alloc_depth += DIRLINK_DEFAULT_DFS_MAX_DEPTH; \
1282 #define DFS_POP(dfsid) \
1285 dfsid.inode_id = dfs.idptr[dfs.depth].inode_id; \
1286 dfsid.catalog_id = dfs.idptr[dfs.depth].catalog_id; \
1289 #define DFS_PEEK(dfsid) \
1291 dfsid.inode_id = dfs.idptr[dfs.depth-1].inode_id; \
1292 dfsid.catalog_id = dfs.idptr[dfs.depth-1].catalog_id; \
1295 /* Initialize the traversal stack */
1296 dfs
.idptr
= malloc(max_alloc_depth
* sizeof(struct dfs_id
));
1302 /* Initialize unknown child IDs which are used when a directory is
1303 * seen for the first time.
1305 unknown_child
.inode_id
= unknown_child
.catalog_id
= 0;
1307 /* Allocate visited list for total number of directory inodes seen */
1308 if (gptr
->calculated_dirinodes
) {
1309 visited
.size
= gptr
->calculated_dirinodes
;
1311 visited
.size
= 1024;
1314 /* If visited list allocation failed, perform search without cache */
1315 visited
.list
= malloc(visited
.size
* sizeof(uint32_t));
1316 if (visited
.list
== NULL
) {
1318 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
1319 plog ("\tcheck_loops: Allocation failed for visited list\n");
1323 visited
.wrapped
= false;
1325 /* Set the starting directory for traversal */
1326 if (gptr
->dirlink_priv_dir_id
) {
1327 parent
.inode_id
= parent
.catalog_id
= gptr
->dirlink_priv_dir_id
;
1329 parent
.inode_id
= parent
.catalog_id
= kHFSRootFolderID
;
1332 /* Initialize the first parent and its first unknown child */
1335 DFS_PUSH(unknown_child
);
1338 while (dfs
.depth
> 1) {
1341 retval
= find_next_child_dir(gptr
, parent
.inode_id
,
1342 child
.catalog_id
, &(child
.inode_id
),
1343 &(child
.catalog_id
), &is_dirinode
);
1348 if (child
.inode_id
) {
1349 retval
= check_loops(&dfs
, child
);
1351 fsckPrint(gptr
->context
, E_DirLoop
);
1352 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
1353 plog ("\tDetected when adding (%u,%u) to following traversal stack -\n", child
.inode_id
, child
.catalog_id
);
1356 gptr
->CatStat
|= S_LinkErrNoRepair
;
1361 /* Push the current child on traversal stack */
1364 /* Traverse down directory inode only if it was not
1365 * visited previously and mark it visited.
1367 if (is_dirinode
== true) {
1368 if (is_dirinode_visited(child
.inode_id
, &visited
)) {
1371 mark_dirinode_visited(child
.inode_id
, &visited
);
1375 /* Push unknown child to traverse down the child directory */
1376 DFS_PUSH(unknown_child
);
1380 if (dfs
.depth
>= max_alloc_depth
) {
1381 fsckPrint(gptr
->context
, E_DirHardLinkNesting
);
1382 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
1385 gptr
->CatStat
|= S_LinkErrNoRepair
;
1386 retval
= E_DirHardLinkNesting
;
1398 /* This function traverses the entire catalog btree, and checks all
1399 * directory inodes and directory hard links found.
1401 * Returns zero if the check is successful, and non-zero if an error was
1402 * encountered during verification.
1404 int dirhardlink_check(SGlobPtr gptr
)
1410 CatalogRecord catrec
;
1414 PrimeBuckets
*inode_view
= NULL
;
1415 PrimeBuckets
*dirlink_view
= NULL
;
1417 /* Check if the volume is HFS+ */
1418 if (VolumeObjectIsHFSPlus() == false) {
1422 /* Shortcut out if no directory hard links exists on the disk */
1423 if ((gptr
->dirlink_priv_dir_valence
== 0) &&
1424 (gptr
->calculated_dirlinks
== 0) &&
1425 (gptr
->calculated_dirinodes
== 0)) {
1429 fsckPrint(gptr
->context
, hfsMultiLinkDirCheck
);
1431 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
1432 plog ("\tprivdir_valence=%u, calc_dirlinks=%u, calc_dirinode=%u\n", gptr
->dirlink_priv_dir_valence
, gptr
->calculated_dirlinks
, gptr
->calculated_dirinodes
);
1435 /* If lookup of private directory failed and the volume has
1436 * some directory hard links and directory inodes, we will need
1437 * to create the private directory for directory hard links.
1439 if (gptr
->dirlink_priv_dir_id
== 0) {
1440 fsckPrint(gptr
->context
, E_MissingPrivDir
);
1441 gptr
->CatStat
|= S_LinkErrNoRepair
;
1444 /* Initialize the two prime number buckets, both buckets keep track
1445 * of inode ID and corresponding directory hard link ID. The first
1446 * bucket is filled when traversing the directory hard link doubly
1447 * linked list from the directory inode, and the second bucket is
1448 * filled when btree traversal encounters directory hard links.
1449 * This method quickly allows us to check if the mapping of all
1450 * inodes and directory hard links is same, and no orphans exists.
1452 inode_view
= (PrimeBuckets
*)calloc(1, sizeof(PrimeBuckets
));
1458 dirlink_view
= (PrimeBuckets
*)calloc(1, sizeof(PrimeBuckets
));
1459 if (!dirlink_view
) {
1464 /* Traverse the catalog btree from the first record */
1466 retval
= GetBTreeRecord(gptr
->calculatedCatalogFCB
, selcode
, &catkey
,
1467 &catrec
, &recsize
, &hint
);
1472 /* Set code to get the next record */
1475 if (catrec
.hfsPlusFolder
.recordType
== kHFSPlusFolderRecord
) {
1476 /* Check directory hard link private metadata directory */
1477 if (catrec
.hfsPlusFolder
.folderID
== gptr
->dirlink_priv_dir_id
) {
1478 dirlink_priv_dir_check(gptr
,
1479 &(catrec
.hfsPlusFolder
), &(catkey
.hfsPlus
));
1482 /* Check directory inode */
1483 if ((catrec
.hfsPlusFolder
.flags
& kHFSHasLinkChainMask
) ||
1484 (catkey
.hfsPlus
.parentID
== gptr
->dirlink_priv_dir_id
)) {
1485 retval
= inode_check(gptr
, inode_view
,
1490 /* If the corruption detected requires
1491 * knowledge of all associated directory
1492 * hard links for repair, stop the
1493 * catalog btree traversal
1500 if (catrec
.recordType
== kHFSPlusFileRecord
) {
1501 /* Check if the hard link bit is set with correct
1502 * alias type/creator. If the parent is private
1503 * metadata directory for file hard links, this
1504 * is a hard link inode for an alias, and not
1505 * directory hard link. Skip this file from our
1508 if ((catrec
.hfsPlusFile
.flags
& kHFSHasLinkChainMask
) &&
1509 (catrec
.hfsPlusFile
.userInfo
.fdType
== kHFSAliasType
) &&
1510 (catrec
.hfsPlusFile
.userInfo
.fdCreator
== kHFSAliasCreator
) &&
1511 (catkey
.hfsPlus
.parentID
!= gptr
->filelink_priv_dir_id
)) {
1512 dirlink_check(gptr
, dirlink_view
,
1513 &(catrec
.hfsPlusFile
), &(catkey
.hfsPlus
), true);
1517 retval
= GetBTreeRecord(gptr
->calculatedCatalogFCB
, 1,
1518 &catkey
, &catrec
, &recsize
, &hint
);
1519 } while (retval
== noErr
);
1521 if (retval
== btNotFound
) {
1523 } else if (retval
!= 0) {
1527 /* Compare the two prime number buckets only the if catalog traversal did
1528 * not detect incorrect number of directory hard links corruption.
1530 if ((gptr
->CatStat
& S_DirHardLinkChain
) == 0) {
1531 retval
= compare_prime_buckets(inode_view
, dirlink_view
);
1533 record_link_badchain(gptr
, true);
1534 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
1535 plog ("\tdirlink prime buckets do not match\n");
1541 /* Check if there are any loops in the directory hierarchy */
1542 retval
= check_hierarchy_loops(gptr
);
1553 free (dirlink_view
);