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 /* Attribute should be an inline attribute */
468 if (rec
->recordType
!= kHFSPlusAttrInlineData
) {
469 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
470 plog ("\tfirst link EA is not inline for dirinode=%u (found=0x%x)\n", inode_id
, rec
->recordType
);
476 /* Attribute data should be null terminated, attrSize includes
477 * size of the attribute data including the null termination.
479 if (rec
->attrData
[rec
->attrSize
-1] != '\0') {
480 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
481 plog ("\tfirst link EA attrData is not NULL terminated for dirinode=%u\n", inode_id
);
487 /* All characters are numbers in the attribute data */
488 for (i
= 0; i
< rec
->attrSize
-1; i
++) {
489 if (isdigit(rec
->attrData
[i
]) == 0) {
490 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
491 plog ("\tfirst link EA attrData contains non-digit 0x%x for dirinode=%u\n", rec
->attrData
[i
], inode_id
);
498 *first_link_id
= strtoul((char *)&rec
->attrData
[0], NULL
, 10);
499 if (*first_link_id
< kHFSFirstUserCatalogNodeID
) {
500 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
501 plog ("\tfirst link ID=%u is < 16 for dirinode=%u\n", *first_link_id
, inode_id
);
510 if ((inode_rec
!= NULL
) &&
511 (inode_rec
->recordType
== kHFSPlusFileRecord
)) {
512 *first_link_id
= inode_rec
->hfsPlusFile
.hl_firstLinkID
;
513 if (*first_link_id
< kHFSFirstUserCatalogNodeID
) {
514 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
515 plog("\tfirst link ID=%u is < 16 for fileinode=%u\n", *first_link_id
, inode_id
);
526 /* No record or bad record provided, look it up */
527 retval
= GetCatalogRecordByID(gptr
, inode_id
, true, &key
, &rec
, &recsize
);
529 *first_link_id
= rec
.hfsPlusFile
.hl_firstLinkID
;
530 if (rec
.recordType
!= kHFSPlusFileRecord
||
531 *first_link_id
< kHFSFirstUserCatalogNodeID
) {
532 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
533 plog("\tfirst link ID=%u is < 16 for fileinode=%u\n", *first_link_id
, inode_id
);
549 /* Adds the directory inode, and directory hard link pair to the
550 * prime remainder bucket provided. This is based on Chinese Remainder
551 * Theorem, and the buckets are later compared to find if the directory
552 * hard link chains for all directory inodes are valid.
554 void hardlink_add_bucket(PrimeBuckets
*bucket
, uint32_t inode_id
,
555 uint32_t cur_link_id
)
559 num
= ((uint64_t)inode_id
<< 32) | cur_link_id
;
561 add_prime_bucket_uint64(bucket
, num
);
564 /* Structure to store the directory hard link IDs found during doubly linked
565 * list traversal in inode_check()
569 struct link_list
*next
;
572 /* Verifies the inode record. Validates if the flags are set
573 * correctly, parent is the private metadata directory, first link ID
574 * is stored correctly, and the doubly linked * list of hard links is valid.
577 * zero - if no corruption is detected, or the corruption detected is
578 * such that a repair order can be created.
579 * non-zero - if the corruption detected requires complete knowledge of
580 * all the related directory hard links to suggest repair.
582 int inode_check(SGlobPtr gptr
, PrimeBuckets
*bucket
,
583 CatalogRecord
*rec
, CatalogKey
*key
, Boolean isdir
)
587 uint32_t cur_link_id
;
588 uint32_t prev_link_id
;
592 char found_name
[NAME_MAX
];
596 CatalogRecord linkrec
;
600 uint32_t link_ref_num
= 0;
602 struct link_list
*head
= NULL
;
603 struct link_list
*cur
;
605 (void) utf_encodestr(key
->hfsPlus
.nodeName
.unicode
, key
->hfsPlus
.nodeName
.length
* 2,
606 (unsigned char *)found_name
, &found_len
, NAME_MAX
);
607 found_name
[found_len
] = '\0';
610 inode_id
= rec
->hfsPlusFolder
.folderID
;
611 flags
= rec
->hfsPlusFolder
.flags
;
612 linkCount
= rec
->hfsPlusFolder
.bsdInfo
.special
.linkCount
;
613 parentid
= gptr
->dirlink_priv_dir_id
;
615 inode_id
= rec
->hfsPlusFile
.fileID
;
616 flags
= rec
->hfsPlusFile
.flags
;
617 linkCount
= rec
->hfsPlusFile
.bsdInfo
.special
.linkCount
;
618 parentid
= gptr
->filelink_priv_dir_id
;
619 link_ref_num
= strtoul(&found_name
[strlen(HFS_INODE_PREFIX
)], NULL
, 10);
622 /* inode should only reside in its corresponding private directory */
623 if ((parentid
!= 0) && (key
->hfsPlus
.parentID
!= parentid
)) {
624 (void) record_inode_badparent(gptr
, inode_id
, isdir
, key
->hfsPlus
.parentID
, parentid
);
627 /* Compare the names for directory inode only because the names
628 * of file inodes can have random number suffixed.
631 (void) snprintf(calc_name
, sizeof(calc_name
), "%s%u", HFS_DIRINODE_PREFIX
, inode_id
);
632 calc_len
= strlen(calc_name
);
634 if ((found_len
!= calc_len
) ||
635 (strncmp(calc_name
, found_name
, calc_len
) != 0)) {
636 (void) record_inode_badname(gptr
, inode_id
, found_name
,
641 /* At least one hard link should always point at an inode. */
642 if (linkCount
== 0) {
643 record_link_badchain(gptr
, isdir
);
644 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
645 plog ("\tlinkCount=0 for dirinode=%u\n", inode_id
);
651 /* A directory inode should always have kHFSHasLinkChainBit
652 * set. A file inode created on pre-Leopard OS does not have
653 * kHFSHasLinkChainBit set and firstLinkID is zero. Therefore
654 * ignore such file inodes from CRT check and instead add the
655 * the inode to hash used for checking link count.
657 if ((flags
& kHFSHasLinkChainMask
) == 0) {
658 if ((isdir
) || (!isdir
&& (rec
->hfsPlusFile
.hl_firstLinkID
!= 0))) {
659 (void) record_inode_badflags(gptr
, inode_id
, isdir
,
660 flags
, flags
| kHFSHasLinkChainMask
, false);
662 filelink_hash_inode(link_ref_num
, linkCount
);
668 /* Lookup the ID of first link from the extended attribute */
669 retval
= get_first_link_id(gptr
, rec
, inode_id
, isdir
, &cur_link_id
);
671 record_link_badchain(gptr
, isdir
);
672 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
673 plog ("\tError getting first link ID for inode=%u\n", inode_id
);
678 /* Check doubly linked list of hard links that point to this inode */
682 while (cur_link_id
!= 0) {
683 /* Lookup the current directory link record */
684 retval
= GetCatalogRecordByID(gptr
, cur_link_id
, true,
685 &linkkey
, &linkrec
, &recsize
);
687 record_link_badchain(gptr
, isdir
);
688 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
689 plog ("\tError getting link=%u for inode=%u\n", cur_link_id
, inode_id
);
694 /* Hard link is a file record */
695 if (linkrec
.recordType
!= kHFSPlusFileRecord
) {
696 record_link_badchain(gptr
, isdir
);
697 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
698 plog ("\tIncorrect record type for link=%u for inode=%u (expected=2, found=%u)\n", cur_link_id
, inode_id
, linkrec
.recordType
);
704 /* Hard link should have hard link bit set */
705 if ((linkrec
.hfsPlusFile
.flags
& kHFSHasLinkChainMask
) == 0) {
706 (void) record_link_badchain(gptr
, isdir
);
707 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
708 plog ("\tIncorrect flag for link=%u for inode=%u (found=0x%x)\n", cur_link_id
, inode_id
, linkrec
.hfsPlusFile
.flags
);
715 /* Check if the hard link has correct finder info */
716 if ((linkrec
.hfsPlusFile
.userInfo
.fdType
!= kHFSAliasType
) ||
717 (linkrec
.hfsPlusFile
.userInfo
.fdCreator
!= kHFSAliasCreator
) ||
718 ((linkrec
.hfsPlusFile
.userInfo
.fdFlags
& kIsAlias
) == 0)) {
719 record_link_badfinderinfo(gptr
, linkrec
.hfsPlusFile
.fileID
, isdir
);
720 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
721 plog("\tdirlink: fdType = 0x%08lx, fdCreator = 0x%08lx\n",
722 (unsigned long)linkrec
.hfsPlusFile
.userInfo
.fdType
,
723 (unsigned long)linkrec
.hfsPlusFile
.userInfo
.fdCreator
);
727 /* Check if hard link points to the current inode */
728 if (linkrec
.hfsPlusFile
.hl_linkReference
!= inode_id
) {
729 record_link_badchain(gptr
, isdir
);
730 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
731 plog ("\tIncorrect dirinode ID for dirlink=%u (expected=%u, found=%u)\n", cur_link_id
, inode_id
, linkrec
.hfsPlusFile
.hl_linkReference
);
738 /* Check if the hard link has correct finder info */
739 if ((linkrec
.hfsPlusFile
.userInfo
.fdType
!= kHardLinkFileType
) ||
740 (linkrec
.hfsPlusFile
.userInfo
.fdCreator
!= kHFSPlusCreator
)) {
741 record_link_badfinderinfo(gptr
, linkrec
.hfsPlusFile
.fileID
, isdir
);
742 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
743 plog("\tfilelink: fdType = 0x%08lx, fdCreator = 0x%08lx\n",
744 (unsigned long)linkrec
.hfsPlusFile
.userInfo
.fdType
,
745 (unsigned long)linkrec
.hfsPlusFile
.userInfo
.fdCreator
);
749 /* Check if hard link has correct link reference number */
750 if (linkrec
.hfsPlusFile
.hl_linkReference
!= link_ref_num
) {
751 record_link_badchain(gptr
, isdir
);
752 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
753 plog ("\tIncorrect link reference number for filelink=%u (expected=%u, found=%u)\n", cur_link_id
, inode_id
, linkrec
.hfsPlusFile
.hl_linkReference
);
760 /* For directory hard links, add the directory inode ID and
761 * the current link ID pair to the prime bucket. For file
762 * hard links, add the link reference number and current
763 * link ID pair to the prime bucket.
766 hardlink_add_bucket(bucket
, inode_id
, cur_link_id
);
768 hardlink_add_bucket(bucket
, link_ref_num
, cur_link_id
);
771 /* Check the previous directory hard link */
772 if (prev_link_id
!= linkrec
.hfsPlusFile
.hl_prevLinkID
) {
773 record_link_badchain(gptr
, isdir
);
774 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
775 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
);
781 /* Check if we saw this directory hard link previously */
784 if (cur
->link_id
== cur_link_id
) {
785 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
786 plog ("\tDuplicate link=%u found in list for inode=%u\n", cur_link_id
, inode_id
);
788 record_link_badchain(gptr
, isdir
);
795 /* Add the new unique directory hard link to our list */
796 cur
= malloc(sizeof(struct link_list
));
801 cur
->link_id
= cur_link_id
;
806 prev_link_id
= cur_link_id
;
807 cur_link_id
= linkrec
.hfsPlusFile
.hl_nextLinkID
;
810 /* If the entire chain looks good, match the link count */
811 if (linkCount
!= count
) {
812 record_link_badchain(gptr
, isdir
);
813 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
814 plog ("\tIncorrect linkCount for inode=%u (expected=%u, found=%u)\n", inode_id
, count
, linkCount
);
821 /* Free memory used for checking duplicates in the doubly linked list */
832 /* Check if the parent ancestors starting at the given directory has
833 * the kHFSHasChildLinkBit set. This bit indicates that a descendant of
834 * this directory is a directory hard link. Note that the root folder
835 * and the "private directory data" directory does not have this bit
836 * set, and the check stops as soon as we encounter one of these
839 static void check_dirlink_ancestors(SGlobPtr gptr
, uint32_t dir_id
)
846 while ((dir_id
!= kHFSRootFolderID
) && (dir_id
!= gptr
->dirlink_priv_dir_id
)) {
847 retval
= GetCatalogRecordByID(gptr
, dir_id
, true, &key
, &rec
, &recsize
);
852 if (rec
.recordType
!= kHFSPlusFolderRecord
) {
856 if ((rec
.hfsPlusFolder
.flags
& kHFSHasChildLinkMask
) == 0) {
857 (void) record_parent_badflags(gptr
, dir_id
,
858 rec
.hfsPlusFolder
.flags
,
859 rec
.hfsPlusFolder
.flags
| kHFSHasChildLinkMask
);
862 dir_id
= key
.hfsPlus
.parentID
;
865 /* If there was any problem in looking up parent directory,
866 * the catalog check should have also detected the problem.
867 * But there are cases which are not detected like names in
868 * thread record and file/folder record key do not match.
869 * Therefore force repair for incorrect number of thread
870 * records if lookup fails.
872 if ((dir_id
!= kHFSRootFolderID
) && (dir_id
!= gptr
->dirlink_priv_dir_id
)) {
873 fsckPrint(gptr
->context
, E_BadParentHierarchy
, dir_id
);
874 gptr
->CBTStat
|= S_Orphan
;
880 /* Verifies the directory hard link record. Validates if the flags are set
881 * correctly, the finderInfo fields are correct, and if the parent hierarchy
882 * till the root folder (except the root folder) has the kHFSHasChildLinkBit
883 * set correctly. This function also add the directory inode, and the
884 * directory hard link pair to the prime buckets for comparison later.
886 * This function does not verify the first and the next directory hard link
887 * pointers in the doubly linked list because the check is already done
888 * in directory inode check (inode_check()) . Any orphan directory
889 * hard link will also be detected later by the prime bucket comparison.
891 static void dirlink_check(SGlobPtr gptr
, PrimeBuckets
*bucket
,
892 HFSPlusCatalogFile
*rec
, HFSPlusCatalogKey
*key
, Boolean isdir
)
894 /* Add this directory hard link and corresponding inode number pair
897 #if DEBUG_HARDLINKCHECK
898 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
)
899 plog("link_check: adding <%u, %u>\n", rec
->hl_linkReference
, rec
->fileID
);
902 hardlink_add_bucket(bucket
, rec
->hl_linkReference
, rec
->fileID
);
904 /* Check if the directory hard link has UF_IMMUTABLE bit set */
905 if ((rec
->bsdInfo
.ownerFlags
& UF_IMMUTABLE
) == 0) {
906 record_dirlink_badownerflags(gptr
, rec
->fileID
,
907 rec
->bsdInfo
.ownerFlags
,
908 rec
->bsdInfo
.ownerFlags
| UF_IMMUTABLE
, false);
911 /* Check Finder Info */
912 if ((rec
->userInfo
.fdType
!= kHFSAliasType
) ||
913 (rec
->userInfo
.fdCreator
!= kHFSAliasCreator
) ||
914 ((rec
->userInfo
.fdFlags
& kIsAlias
) == 0)) {
915 record_link_badfinderinfo(gptr
, rec
->fileID
, isdir
);
918 /* XXX - Check resource fork/alias data */
920 /* Check if all the parent directories have the kHFSHasChildLinkBit set */
921 check_dirlink_ancestors(gptr
, key
->parentID
);
924 /* Searches the next child directory record to return given the parent ID
925 * and the current child ID. If the current child ID is zero, this is the
926 * first time we are looking up this directory, therefore return the
927 * first child directory or directory hard link found. If child ID is
928 * non-zero, return the first child directory or directory hard
929 * link found after the current child record.
931 * For normal directories, the folder ID is returned as the new child inode_id
932 * and catalog_id. For directory hard links, the inode_id of the directory
933 * inode is returned in the inode_id, and the fileID of the directory hard link
934 * is returned in the catalog_id. If the inode_id returned corresponds to a
935 * directory inode, is_dirinode is set to true. If no child record is found,
936 * or an error occurred on btree traversal, these values are zero.
939 * zero - on successfully determining if the next child record exists
941 * non-zero - error, like during btree lookup, etc.
943 static int find_next_child_dir(SGlobPtr gptr
, uint32_t parent_id
,
944 uint32_t cur_child_catalog_id
, uint32_t *child_inode_id
,
945 uint32_t *child_catalog_id
, uint32_t *is_dirinode
)
949 int return_next_rec
= true;
950 BTreeIterator iterator
;
951 FSBufferDescriptor buf_desc
;
957 *child_catalog_id
= 0;
958 *is_dirinode
= false;
960 fcb
= gptr
->calculatedCatalogFCB
;
961 key
= (CatalogKey
*)&iterator
.key
;
963 /* If no child record for this parent has been looked up previously,
964 * return the first child record found. Otherwise lookup the
965 * catalog record for the last child ID provided and return the
966 * next valid child ID. If the lookup of the last child failed,
967 * fall back to iterating all child records for given parent
968 * directory and returning next child found after given child ID.
970 if (cur_child_catalog_id
== 0) {
972 /* Lookup catalog record with key containing given parent ID and NULL
973 * name. This will place iterator just before the first child record
974 * for this directory.
976 bzero(&iterator
, sizeof(iterator
));
977 bzero(&buf_desc
, sizeof(buf_desc
));
978 buf_desc
.bufferAddress
= &rec
;
979 buf_desc
.itemCount
= 1;
980 buf_desc
.itemSize
= sizeof(rec
);
981 BuildCatalogKey(parent_id
, NULL
, true, key
);
982 retval
= BTSearchRecord(fcb
, &iterator
, kNoHint
, &buf_desc
, &recsize
,
984 if ((retval
!= 0) && (retval
!= btNotFound
)) {
988 /* Lookup the thread record for the last child seen */
989 bzero(&iterator
, sizeof(iterator
));
990 bzero(&buf_desc
, sizeof(buf_desc
));
991 buf_desc
.bufferAddress
= &rec
;
992 buf_desc
.itemCount
= 1;
993 buf_desc
.itemSize
= sizeof(rec
);
994 BuildCatalogKey(cur_child_catalog_id
, NULL
, true, key
);
995 retval
= BTSearchRecord(fcb
, &iterator
, kNoHint
, &buf_desc
,
996 &recsize
, &iterator
);
998 return_next_rec
= false;
1002 /* Check if really we found a thread record */
1003 if ((rec
.recordType
!= kHFSPlusFolderThreadRecord
) &&
1004 (rec
.recordType
!= kHFSPlusFileThreadRecord
)) {
1005 return_next_rec
= false;
1006 goto iterate_parent
;
1009 /* Lookup the corresponding file/folder record */
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(rec
.hfsPlusThread
.parentID
,
1016 (CatalogName
*)&(rec
.hfsPlusThread
.nodeName
),
1017 true, (CatalogKey
*)&(iterator
.key
));
1018 retval
= BTSearchRecord(fcb
, &iterator
, kInvalidMRUCacheKey
,
1019 &buf_desc
, &recsize
, &iterator
);
1021 return_next_rec
= false;
1022 goto iterate_parent
;
1026 /* Lookup the next record */
1027 retval
= BTIterateRecord(fcb
, kBTreeNextRecord
, &iterator
, &buf_desc
,
1029 while (retval
== 0) {
1030 /* Not the same parent anymore, stop the search */
1031 if (key
->hfsPlus
.parentID
!= parent_id
) {
1035 if (rec
.recordType
== kHFSPlusFolderRecord
) {
1036 /* Found a catalog folder record, and if we are
1037 * supposed to return the next record found, return
1038 * this catalog folder.
1040 if (return_next_rec
) {
1041 if (rec
.hfsPlusFolder
.flags
& kHFSHasLinkChainMask
) {
1042 *is_dirinode
= true;
1044 *child_inode_id
= rec
.hfsPlusFolder
.folderID
;
1045 *child_catalog_id
= rec
.hfsPlusFolder
.folderID
;
1048 /* If the current record is the current child, we
1049 * have to return the next child record.
1051 if (rec
.hfsPlusFolder
.folderID
== cur_child_catalog_id
) {
1052 return_next_rec
= true;
1054 } else if (rec
.recordType
== kHFSPlusFileRecord
) {
1055 /* Check if the hard link bit is set with correct
1056 * alias type/creator. If the parent is private
1057 * metadata directory for file hard links, this
1058 * is a hard link inode for an alias, and not
1059 * directory hard link. Skip this file from our
1062 if ((rec
.hfsPlusFile
.flags
& kHFSHasLinkChainMask
) &&
1063 (rec
.hfsPlusFile
.userInfo
.fdType
== kHFSAliasType
) &&
1064 (rec
.hfsPlusFile
.userInfo
.fdCreator
== kHFSAliasCreator
) &&
1065 (key
->hfsPlus
.parentID
!= gptr
->filelink_priv_dir_id
)) {
1066 /* Found a directory hard link, and if we are
1067 * supposed to return the next record found,
1068 * then return this directory hard link.
1070 if (return_next_rec
) {
1071 *child_inode_id
= rec
.hfsPlusFile
.hl_linkReference
;
1072 *child_catalog_id
= rec
.hfsPlusFile
.fileID
;
1073 *is_dirinode
= true;
1076 /* If the current record is the current child,
1077 * we have to return the next child record.
1079 if (rec
.hfsPlusFile
.fileID
== cur_child_catalog_id
) {
1080 return_next_rec
= true;
1085 /* Lookup the next record */
1086 retval
= BTIterateRecord(fcb
, kBTreeNextRecord
, &iterator
,
1087 &buf_desc
, &recsize
);
1090 if (retval
== btNotFound
) {
1098 /* In-memory state for depth first traversal for finding loops in
1099 * directory hierarchy. inode_id is the user visible ID of the given
1100 * directory or directory hard link, and catalog_id is the inode ID for
1101 * normal directories, and the directory hard link ID (file ID of the
1102 * directory hard link record).
1104 * The inode_id is used for checking loops in the hierarchy, whereas
1105 * the catalog_id is used to maintain state for depth first traversal.
1109 uint32_t catalog_id
;
1114 struct dfs_id
*idptr
;
1117 /* Assuming that the name of a directory is single byte, the maximum depth
1118 * of a directory hierarchy that can accommodate in PATH_MAX will be
1119 * PATH_MAX/2. Note that catalog hierarchy check puts limitation of 100
1120 * on the maximum depth of a directory hierarchy.
1122 #define DIRLINK_DEFAULT_DFS_MAX_DEPTH PATH_MAX/2
1124 /* Check if the current directory exists in the current traversal path.
1125 * If yes, loops in directory exists and return non-zero value. If not,
1128 static int check_loops(struct dfs_stack
*dfs
, struct dfs_id id
)
1133 for (i
= 0; i
< dfs
->depth
; i
++) {
1134 if (dfs
->idptr
[i
].inode_id
== id
.inode_id
) {
1143 static void print_dfs(struct dfs_stack
*dfs
)
1148 for (i
= 0; i
< dfs
->depth
; i
++) {
1149 plog ("(%u,%u) ", dfs
->idptr
[i
].inode_id
, dfs
->idptr
[i
].catalog_id
);
1154 /* Store information about visited directory inodes such that we do not
1155 * reenter the directory multiple times while following directory hard links.
1157 struct visited_dirinode
{
1158 uint32_t *list
; /* Pointer to array of IDs */
1159 uint32_t size
; /* Maximum number of entries in the array */
1160 uint32_t offset
; /* Offset where next ID will be added */
1161 uint32_t wrapped
; /* Boolean, true if list wraps around */
1164 /* Add the given dirinode_id to the list of visited nodes. If all the slots
1165 * in visited list are used, wrap around and add the new ID.
1167 static void mark_dirinode_visited(uint32_t dirinode_id
, struct visited_dirinode
*visited
)
1169 if (visited
->list
== NULL
) {
1173 if (visited
->offset
>= visited
->size
) {
1174 visited
->offset
= 0;
1175 visited
->wrapped
= true;
1177 visited
->list
[visited
->offset
] = dirinode_id
;
1181 /* Check if given directory inode exists in the visited list or not */
1182 static int is_dirinode_visited(uint32_t dirinode_id
, struct visited_dirinode
*visited
)
1184 int is_visited
= false;
1185 uint32_t end_offset
;
1188 if (visited
->list
== NULL
) {
1192 /* If the list had wrapped, search the entire list */
1193 if (visited
->wrapped
== true) {
1194 end_offset
= visited
->size
;
1196 end_offset
= visited
->offset
;
1199 for (off
= 0; off
< end_offset
; off
++) {
1200 if (visited
->list
[off
] == dirinode_id
) {
1209 /* Check if there are any loops in the directory hierarchy.
1211 * This function performs a depth first traversal of directories as they
1212 * will be visible to the user. If the lookup of private metadata directory
1213 * succeeded in dirlink_init(), the traversal starts from the private
1214 * metadata directory. Otherwise it starts at the root folder. It stores
1215 * the current depth first traversal state, and looks up catalog records as
1216 * required. The current traversal state consists of two IDs, the user
1217 * visible ID or inode_id, and the on-disk ID or catalog_id. For normal
1218 * directories, the user visible ID is same as the on-disk ID, but for
1219 * directory hard links, the user visible ID is the inode ID, and the
1220 * on-disk ID is the file ID of the directory hard link. This function
1221 * stores the list of visited directory inode ID and checks the list before
1222 * traversing down the directory inode hierarchy. After traversing down a
1223 * directory inode and checking that is valid, it adds the directory inode
1224 * ID to the visited list.
1226 * The inode_id is used for checking loops in the hierarchy, whereas
1227 * the catalog_id is used to maintain state for depth first traversal.
1230 * zero - if the check was performed successfully, and no loops exist
1231 * in the directory hierarchy.
1232 * non-zero - on error, or if loops were detected in directory hierarchy.
1234 static int check_hierarchy_loops(SGlobPtr gptr
)
1237 struct dfs_stack dfs
;
1238 struct dfs_id unknown_child
;
1239 struct dfs_id child
;
1240 struct dfs_id parent
;
1241 struct visited_dirinode visited
;
1242 size_t max_alloc_depth
= DIRLINK_DEFAULT_DFS_MAX_DEPTH
;
1243 uint32_t is_dirinode
;
1245 #define DFS_PUSH(dfsid) \
1247 dfs.idptr[dfs.depth].inode_id = dfsid.inode_id; \
1248 dfs.idptr[dfs.depth].catalog_id = dfsid.catalog_id; \
1250 if (dfs.depth == max_alloc_depth) { \
1251 void *tptr = realloc(dfs.idptr, (max_alloc_depth + DIRLINK_DEFAULT_DFS_MAX_DEPTH) * sizeof(struct dfs_id)); \
1252 if (tptr == NULL) { \
1256 max_alloc_depth += DIRLINK_DEFAULT_DFS_MAX_DEPTH; \
1261 #define DFS_POP(dfsid) \
1264 dfsid.inode_id = dfs.idptr[dfs.depth].inode_id; \
1265 dfsid.catalog_id = dfs.idptr[dfs.depth].catalog_id; \
1268 #define DFS_PEEK(dfsid) \
1270 dfsid.inode_id = dfs.idptr[dfs.depth-1].inode_id; \
1271 dfsid.catalog_id = dfs.idptr[dfs.depth-1].catalog_id; \
1274 /* Initialize the traversal stack */
1275 dfs
.idptr
= malloc(max_alloc_depth
* sizeof(struct dfs_id
));
1281 /* Initialize unknown child IDs which are used when a directory is
1282 * seen for the first time.
1284 unknown_child
.inode_id
= unknown_child
.catalog_id
= 0;
1286 /* Allocate visited list for total number of directory inodes seen */
1287 if (gptr
->calculated_dirinodes
) {
1288 visited
.size
= gptr
->calculated_dirinodes
;
1290 visited
.size
= 1024;
1293 /* If visited list allocation failed, perform search without cache */
1294 visited
.list
= malloc(visited
.size
* sizeof(uint32_t));
1295 if (visited
.list
== NULL
) {
1297 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
1298 plog ("\tcheck_loops: Allocation failed for visited list\n");
1302 visited
.wrapped
= false;
1304 /* Set the starting directory for traversal */
1305 if (gptr
->dirlink_priv_dir_id
) {
1306 parent
.inode_id
= parent
.catalog_id
= gptr
->dirlink_priv_dir_id
;
1308 parent
.inode_id
= parent
.catalog_id
= kHFSRootFolderID
;
1311 /* Initialize the first parent and its first unknown child */
1314 DFS_PUSH(unknown_child
);
1317 while (dfs
.depth
> 1) {
1320 retval
= find_next_child_dir(gptr
, parent
.inode_id
,
1321 child
.catalog_id
, &(child
.inode_id
),
1322 &(child
.catalog_id
), &is_dirinode
);
1327 if (child
.inode_id
) {
1328 retval
= check_loops(&dfs
, child
);
1330 fsckPrint(gptr
->context
, E_DirLoop
);
1331 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
1332 plog ("\tDetected when adding (%u,%u) to following traversal stack -\n", child
.inode_id
, child
.catalog_id
);
1335 gptr
->CatStat
|= S_LinkErrNoRepair
;
1340 /* Push the current child on traversal stack */
1343 /* Traverse down directory inode only if it was not
1344 * visited previously and mark it visited.
1346 if (is_dirinode
== true) {
1347 if (is_dirinode_visited(child
.inode_id
, &visited
)) {
1350 mark_dirinode_visited(child
.inode_id
, &visited
);
1354 /* Push unknown child to traverse down the child directory */
1355 DFS_PUSH(unknown_child
);
1359 if (dfs
.depth
>= max_alloc_depth
) {
1360 fsckPrint(gptr
->context
, E_DirHardLinkNesting
);
1361 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
1364 gptr
->CatStat
|= S_LinkErrNoRepair
;
1365 retval
= E_DirHardLinkNesting
;
1377 /* This function traverses the entire catalog btree, and checks all
1378 * directory inodes and directory hard links found.
1380 * Returns zero if the check is successful, and non-zero if an error was
1381 * encountered during verification.
1383 int dirhardlink_check(SGlobPtr gptr
)
1389 CatalogRecord catrec
;
1393 PrimeBuckets
*inode_view
= NULL
;
1394 PrimeBuckets
*dirlink_view
= NULL
;
1396 /* Check if the volume is HFS+ */
1397 if (VolumeObjectIsHFSPlus() == false) {
1401 /* Shortcut out if no directory hard links exists on the disk */
1402 if ((gptr
->dirlink_priv_dir_valence
== 0) &&
1403 (gptr
->calculated_dirlinks
== 0) &&
1404 (gptr
->calculated_dirinodes
== 0)) {
1408 fsckPrint(gptr
->context
, hfsMultiLinkDirCheck
);
1410 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
1411 plog ("\tprivdir_valence=%u, calc_dirlinks=%u, calc_dirinode=%u\n", gptr
->dirlink_priv_dir_valence
, gptr
->calculated_dirlinks
, gptr
->calculated_dirinodes
);
1414 /* If lookup of private directory failed and the volume has
1415 * some directory hard links and directory inodes, we will need
1416 * to create the private directory for directory hard links.
1418 if (gptr
->dirlink_priv_dir_id
== 0) {
1419 fsckPrint(gptr
->context
, E_MissingPrivDir
);
1420 gptr
->CatStat
|= S_LinkErrNoRepair
;
1423 /* Initialize the two prime number buckets, both buckets keep track
1424 * of inode ID and corresponding directory hard link ID. The first
1425 * bucket is filled when traversing the directory hard link doubly
1426 * linked list from the directory inode, and the second bucket is
1427 * filled when btree traversal encounters directory hard links.
1428 * This method quickly allows us to check if the mapping of all
1429 * inodes and directory hard links is same, and no orphans exists.
1431 inode_view
= (PrimeBuckets
*)calloc(1, sizeof(PrimeBuckets
));
1437 dirlink_view
= (PrimeBuckets
*)calloc(1, sizeof(PrimeBuckets
));
1438 if (!dirlink_view
) {
1443 /* Traverse the catalog btree from the first record */
1445 retval
= GetBTreeRecord(gptr
->calculatedCatalogFCB
, selcode
, &catkey
,
1446 &catrec
, &recsize
, &hint
);
1451 /* Set code to get the next record */
1454 if (catrec
.hfsPlusFolder
.recordType
== kHFSPlusFolderRecord
) {
1455 /* Check directory hard link private metadata directory */
1456 if (catrec
.hfsPlusFolder
.folderID
== gptr
->dirlink_priv_dir_id
) {
1457 dirlink_priv_dir_check(gptr
,
1458 &(catrec
.hfsPlusFolder
), &(catkey
.hfsPlus
));
1461 /* Check directory inode */
1462 if ((catrec
.hfsPlusFolder
.flags
& kHFSHasLinkChainMask
) ||
1463 (catkey
.hfsPlus
.parentID
== gptr
->dirlink_priv_dir_id
)) {
1464 retval
= inode_check(gptr
, inode_view
,
1469 /* If the corruption detected requires
1470 * knowledge of all associated directory
1471 * hard links for repair, stop the
1472 * catalog btree traversal
1479 if (catrec
.recordType
== kHFSPlusFileRecord
) {
1480 /* Check if the hard link bit is set with correct
1481 * alias type/creator. If the parent is private
1482 * metadata directory for file hard links, this
1483 * is a hard link inode for an alias, and not
1484 * directory hard link. Skip this file from our
1487 if ((catrec
.hfsPlusFile
.flags
& kHFSHasLinkChainMask
) &&
1488 (catrec
.hfsPlusFile
.userInfo
.fdType
== kHFSAliasType
) &&
1489 (catrec
.hfsPlusFile
.userInfo
.fdCreator
== kHFSAliasCreator
) &&
1490 (catkey
.hfsPlus
.parentID
!= gptr
->filelink_priv_dir_id
)) {
1491 dirlink_check(gptr
, dirlink_view
,
1492 &(catrec
.hfsPlusFile
), &(catkey
.hfsPlus
), true);
1496 retval
= GetBTreeRecord(gptr
->calculatedCatalogFCB
, 1,
1497 &catkey
, &catrec
, &recsize
, &hint
);
1498 } while (retval
== noErr
);
1500 if (retval
== btNotFound
) {
1502 } else if (retval
!= 0) {
1506 /* Compare the two prime number buckets only the if catalog traversal did
1507 * not detect incorrect number of directory hard links corruption.
1509 if ((gptr
->CatStat
& S_DirHardLinkChain
) == 0) {
1510 retval
= compare_prime_buckets(inode_view
, dirlink_view
);
1512 record_link_badchain(gptr
, true);
1513 if (fsckGetVerbosity(gptr
->context
) >= kDebugLog
) {
1514 plog ("\tdirlink prime buckets do not match\n");
1520 /* Check if there are any loops in the directory hierarchy */
1521 retval
= check_hierarchy_loops(gptr
);
1532 free (dirlink_view
);