]> git.saurik.com Git - apple/hfs.git/blob - fsck_hfs/dfalib/dirhardlink.c
hfs-556.41.1.tar.gz
[apple/hfs.git] / fsck_hfs / dfalib / dirhardlink.c
1 /*
2 * Copyright (c) 2007-2008 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 "SRuntime.h"
26 #include <sys/stat.h>
27 #include <ctype.h>
28
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
34 * iterations.
35 */
36 OSErr GetCatalogRecordByID(SGlobPtr GPtr, UInt32 file_id, Boolean isHFSPlus, CatalogKey *key, CatalogRecord *rec, uint16_t *recsize)
37 {
38 int retval = 0;
39 SFCB *fcb;
40 BTreeControlBlock *btcb;
41 FSBufferDescriptor buf_desc;
42 BTreeIterator search_iterator;
43 BTreeIterator result_iterator;
44 uint32_t thread_key_parentID = 0;
45
46 fcb = GPtr->calculatedCatalogFCB;
47 btcb = (BTreeControlBlock *)fcb->fcbBtree;
48
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);
55
56 BuildCatalogKey(file_id, NULL, isHFSPlus, (CatalogKey *)&(search_iterator.key));
57 retval = BTSearchRecord(fcb, &search_iterator, kInvalidMRUCacheKey,
58 &buf_desc, recsize, &result_iterator);
59 if (retval) {
60 goto out;
61 }
62
63 /* Check if really we found a thread record */
64 if (isHFSPlus) {
65 if ((rec->recordType != kHFSPlusFolderThreadRecord) &&
66 (rec->recordType != kHFSPlusFileThreadRecord)) {
67 retval = ENOENT;
68 goto out;
69 }
70 } else {
71 if ((rec->recordType != kHFSFolderThreadRecord) &&
72 (rec->recordType != kHFSFileThreadRecord)) {
73 retval = ENOENT;
74 goto out;
75 }
76 }
77
78 if (isHFSPlus) {
79 thread_key_parentID = ((CatalogKey *)&(result_iterator.key))->hfsPlus.parentID;
80 }
81
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);
88
89 if (isHFSPlus) {
90 BuildCatalogKey(rec->hfsPlusThread.parentID,
91 (CatalogName *)&(rec->hfsPlusThread.nodeName),
92 isHFSPlus, (CatalogKey *)&(search_iterator.key));
93 } else {
94 BuildCatalogKey(rec->hfsThread.parentID,
95 (CatalogName *)&(rec->hfsThread.nodeName),
96 isHFSPlus, (CatalogKey *)&(search_iterator.key));
97 }
98 retval = BTSearchRecord(fcb, &search_iterator, kInvalidMRUCacheKey,
99 &buf_desc, recsize, &result_iterator);
100 if (retval) {
101 goto out;
102 }
103
104 bcopy(&(result_iterator.key), key, CalcKeySize(btcb, &(result_iterator.key)));
105
106 if (isHFSPlus) {
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.
111 */
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);
117 }
118 GPtr->CBTStat |= S_Orphan;
119 }
120 }
121
122 out:
123 return retval;
124 }
125
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)
128 {
129 RepairOrderPtr p;
130
131 RcdError (gptr, E_BadPermPrivDir);
132 p = AllocMinorRepairOrder(gptr, 0);
133 if (p == NULL) {
134 return ENOMEM;
135 }
136
137 p->type = E_BadPermPrivDir;
138 p->parid = cnid;
139 gptr->CatStat |= S_LinkErrRepair;
140
141 return 0;
142 }
143
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)
147 {
148 RepairOrderPtr p;
149 char str1[12];
150 char str2[12];
151
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);
156
157 p = AllocMinorRepairOrder(gptr, 0);
158 if (p == NULL) {
159 return ENOMEM;
160 }
161
162 p->type = isdir ? E_DirLinkBadFlags : E_FileLinkBadFlags;
163 p->correct = correct;
164 p->incorrect = incorrect;
165 p->parid = link_id;
166
167 gptr->CatStat |= S_LinkErrRepair;
168
169 return 0;
170 }
171
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.
177 */
178 int record_inode_badflags(SGlobPtr gptr, uint32_t inode_id, Boolean isdir,
179 uint32_t incorrect, uint32_t correct, Boolean check_duplicates)
180 {
181 RepairOrderPtr p;
182 char str1[12];
183 char str2[12];
184
185 p = AllocMinorRepairOrder(gptr, 0);
186 if (p == NULL) {
187 return ENOMEM;
188 }
189
190 p->type = isdir ? E_DirInodeBadFlags : E_FileInodeBadFlags;
191 p->correct = correct;
192 p->incorrect = incorrect;
193 p->parid = inode_id;
194
195 gptr->CatStat |= S_LinkErrRepair;
196
197 if ((check_duplicates != 0) &&
198 (IsDuplicateRepairOrder(gptr, p) == 1)) {
199 DeleteRepairOrder(gptr, p);
200 } else {
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);
205 }
206
207 return 0;
208 }
209
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)
214 {
215 char str1[12];
216 char str2[12];
217
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);
222
223 gptr->CatStat |= S_LinkErrNoRepair;
224
225 return 0;
226 }
227
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)
232 {
233 fsckPrint(gptr->context, E_DirInodeBadName, inode_id);
234 fsckPrint(gptr->context, E_BadValue, correct, incorrect);
235
236 gptr->CatStat |= S_LinkErrNoRepair;
237
238 return 0;
239 }
240
241 /* Record corruption for incorrect number of directory hard links and
242 * directory inode, and invalid list of directory hard links
243 */
244 void record_link_badchain(SGlobPtr gptr, Boolean isdir)
245 {
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;
251 }
252 }
253
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.
259 */
260 int record_dirlink_badownerflags(SGlobPtr gptr, uint32_t file_id,
261 uint8_t incorrect, uint8_t correct, int check_duplicates)
262 {
263 RepairOrderPtr p;
264 char str1[12];
265 char str2[12];
266
267 p = AllocMinorRepairOrder(gptr, 0);
268 if (p == NULL) {
269 return ENOMEM;
270 }
271
272 p->type = E_DirHardLinkOwnerFlags;
273 p->correct = correct;
274 p->incorrect = incorrect;
275 p->parid = file_id;
276
277 gptr->CatStat |= S_LinkErrRepair;
278
279 if ((check_duplicates != 0) &&
280 (IsDuplicateRepairOrder(gptr, p) == 1)) {
281 DeleteRepairOrder(gptr, p);
282 } else {
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);
287 }
288
289 return 0;
290 }
291
292 /* Record minor repair for invalid finderInfo for directory hard links */
293 int record_link_badfinderinfo(SGlobPtr gptr, uint32_t file_id, Boolean isdir)
294 {
295 RepairOrderPtr p;
296
297 p = AllocMinorRepairOrder(gptr, 0);
298 if (p == NULL) {
299 return ENOMEM;
300 }
301
302 p->type = isdir ? E_DirHardLinkFinderInfo : E_FileHardLinkFinderInfo;
303 p->parid = file_id;
304
305 gptr->CatStat |= (isdir ? S_DirHardLinkChain : S_FileHardLinkChain);
306
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
311 * repair order.
312 */
313 if (IsDuplicateRepairOrder(gptr, p) == 1) {
314 DeleteRepairOrder(gptr, p);
315 } else {
316 fsckPrint(gptr->context, p->type, file_id);
317 }
318
319 return 0;
320 }
321
322 /* Record minor repair for invalid flags in one of the parent directories
323 * of a directory hard link.
324 */
325 static int record_parent_badflags(SGlobPtr gptr, uint32_t dir_id,
326 uint32_t incorrect, uint32_t correct)
327 {
328 RepairOrderPtr p;
329 char str1[12];
330 char str2[12];
331
332 p = AllocMinorRepairOrder(gptr, 0);
333 if (p == NULL) {
334 return ENOMEM;
335 }
336
337 p->type = E_DirLinkAncestorFlags;
338 p->correct = correct;
339 p->incorrect = incorrect;
340 p->parid = dir_id;
341
342 gptr->CatStat |= S_LinkErrRepair;
343
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.
349 */
350 if (IsDuplicateRepairOrder(gptr, p) == 1) {
351 DeleteRepairOrder(gptr, p);
352 } else {
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);
357 }
358
359 return 0;
360 }
361
362 /* Look up the ".HFS+ Private Directory Data\xd" directory */
363 static int priv_dir_lookup(SGlobPtr gptr, CatalogKey *key, CatalogRecord *rec)
364 {
365 int i;
366 int retval;
367 char *dirname = HFSPLUS_DIR_METADATA_FOLDER;
368 CatalogName cat_dirname;
369 uint16_t recsize;
370 uint32_t hint;
371
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];
376 }
377 BuildCatalogKey(kHFSRootFolderID, &cat_dirname, true, key);
378 retval = SearchBTreeRecord (gptr->calculatedCatalogFCB, key, kNoHint,
379 NULL, rec, &recsize, &hint);
380 return retval;
381 }
382
383 /* This function initializes the directory hard link check by looking up
384 * private directory that stores directory inodes.
385 */
386 int dirhardlink_init(SGlobPtr gptr)
387 {
388 int retval = 0;
389 CatalogRecord rec;
390 CatalogKey key;
391
392 /* Check if the volume is HFS+. */
393 if (VolumeObjectIsHFSPlus() == false) {
394 goto out;
395 }
396
397 /* Look up the private metadata directory */
398 retval = priv_dir_lookup(gptr, &key, &rec);
399 if (retval == 0) {
400 gptr->dirlink_priv_dir_id = rec.hfsPlusFolder.folderID;
401 gptr->dirlink_priv_dir_valence = rec.hfsPlusFolder.valence;
402 } else {
403 gptr->dirlink_priv_dir_id = 0;
404 gptr->dirlink_priv_dir_valence = 0;
405 }
406
407 retval = 0;
408
409 out:
410 return retval;
411 }
412
413 /* Check the private directory for directory hard links */
414 static void dirlink_priv_dir_check(SGlobPtr gptr, HFSPlusCatalogFolder *rec,
415 HFSPlusCatalogKey *key)
416 {
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);
423 }
424 }
425
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.
432 */
433 int get_first_link_id(SGlobPtr gptr, CatalogRecord *inode_rec, uint32_t inode_id,
434 Boolean isdir, uint32_t *first_link_id)
435 {
436 int retval = 0;
437 int i;
438 BTreeIterator iterator;
439 FSBufferDescriptor bt_data;
440 HFSPlusAttrData *rec;
441 HFSPlusAttrKey *key;
442 u_int8_t attrdata[FIRST_LINK_XATTR_REC_SIZE];
443 size_t unicode_bytes = 0;
444
445 bzero(&iterator, sizeof(iterator));
446
447 if (isdir) {
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;
455 key->pad = 0;
456 key->fileID = inode_id;
457 key->startBlock = 0;
458
459 rec = (HFSPlusAttrData *)&attrdata[0];
460 bt_data.bufferAddress = rec;
461 bt_data.itemSize = sizeof(attrdata);
462 bt_data.itemCount = 1;
463
464 retval = BTSearchRecord(gptr->calculatedAttributesFCB, &iterator, kNoHint,
465 &bt_data, NULL, NULL);
466 if (retval == 0) {
467 unsigned long link_id;
468
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);
473 }
474 retval = ENOENT;
475 goto out;
476 }
477
478 /* Attribute data should be null terminated, attrSize includes
479 * size of the attribute data including the null termination.
480 */
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);
484 }
485 retval = ENOENT;
486 goto out;
487 }
488
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);
494 }
495 retval = ENOENT;
496 goto out;
497 }
498 }
499
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);
504 }
505 *first_link_id = 0;
506 retval = ENOENT;
507 goto out;
508 }
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);
513 }
514 *first_link_id = 0;
515 retval = ENOENT;
516 goto out;
517 }
518 }
519 } else {
520 *first_link_id = 0;
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);
527 }
528 *first_link_id = 0;
529 retval = ENOENT;
530 goto out;
531 }
532 } else {
533 CatalogRecord rec;
534 CatalogKey key;
535 uint16_t recsize;
536
537 /* No record or bad record provided, look it up */
538 retval = GetCatalogRecordByID(gptr, inode_id, true, &key, &rec, &recsize);
539 if (retval == 0) {
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);
545 }
546 *first_link_id = 0;
547 retval = ENOENT;
548 }
549 } else {
550 *first_link_id = 0;
551 retval = ENOENT;
552 }
553 }
554 }
555
556 out:
557 return retval;
558 }
559
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.
564 */
565 void hardlink_add_bucket(PrimeBuckets *bucket, uint32_t inode_id,
566 uint32_t cur_link_id)
567 {
568 uint64_t num;
569
570 num = ((uint64_t)inode_id << 32) | cur_link_id;
571
572 add_prime_bucket_uint64(bucket, num);
573 }
574
575 /* Structure to store the directory hard link IDs found during doubly linked
576 * list traversal in inode_check()
577 */
578 struct link_list {
579 uint32_t link_id;
580 struct link_list *next;
581 };
582
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.
586 *
587 * Returns -
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.
592 */
593 int inode_check(SGlobPtr gptr, PrimeBuckets *bucket,
594 CatalogRecord *rec, CatalogKey *key, Boolean isdir)
595 {
596 int retval = 0;
597 uint32_t inode_id;
598 uint32_t cur_link_id;
599 uint32_t prev_link_id;
600 uint32_t count;
601 uint32_t linkCount;
602 char calc_name[32];
603 char found_name[NAME_MAX];
604 size_t calc_len;
605 size_t found_len;
606 CatalogKey linkkey;
607 CatalogRecord linkrec;
608 uint16_t recsize;
609 int flags;
610 uint32_t parentid;
611 uint32_t link_ref_num = 0;
612
613 struct link_list *head = NULL;
614 struct link_list *cur;
615
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';
619
620 if (isdir) {
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;
625 } else {
626 unsigned long ref_num;
627
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);
636 }
637 retval = 1;
638 goto out;
639 }
640 link_ref_num = (uint32_t)ref_num;
641 }
642
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);
646 }
647
648 /* Compare the names for directory inode only because the names
649 * of file inodes can have random number suffixed.
650 */
651 if (isdir) {
652 (void) snprintf(calc_name, sizeof(calc_name), "%s%u", HFS_DIRINODE_PREFIX, inode_id);
653 calc_len = strlen(calc_name);
654
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,
658 calc_name);
659 }
660 }
661
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);
667 }
668 retval = 1;
669 goto out;
670 }
671
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.
677 */
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);
682 } else {
683 filelink_hash_inode(link_ref_num, linkCount);
684 retval = 0;
685 goto out;
686 }
687 }
688
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);
691 if (retval) {
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);
695 }
696 goto out;
697 }
698
699 /* Check doubly linked list of hard links that point to this inode */
700 prev_link_id = 0;
701 count = 0;
702
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);
707 if (retval) {
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);
711 }
712 goto out;
713 }
714
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);
720 }
721 retval = 1;
722 goto out;
723 }
724
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);
730 }
731 retval = 1;
732 goto out;
733 }
734
735 if (isdir) {
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);
745 }
746 }
747
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);
753 }
754 retval = 1;
755 goto out;
756 }
757
758 } else {
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);
767 }
768 }
769
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);
775 }
776 retval = 1;
777 goto out;
778 }
779 }
780
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.
785 */
786 if (isdir) {
787 hardlink_add_bucket(bucket, inode_id, cur_link_id);
788 } else {
789 hardlink_add_bucket(bucket, link_ref_num, cur_link_id);
790 }
791
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);
797 }
798 retval = 1;
799 goto out;
800 }
801
802 /* Check if we saw this directory hard link previously */
803 cur = head;
804 while (cur) {
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);
808 }
809 record_link_badchain(gptr, isdir);
810 retval = 1;
811 goto out;
812 }
813 cur = cur->next;
814 }
815
816 /* Add the new unique directory hard link to our list */
817 cur = malloc(sizeof(struct link_list));
818 if (!cur) {
819 retval = ENOMEM;
820 goto out;
821 }
822 cur->link_id = cur_link_id;
823 cur->next = head;
824 head = cur;
825
826 count++;
827 prev_link_id = cur_link_id;
828 cur_link_id = linkrec.hfsPlusFile.hl_nextLinkID;
829 }
830
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);
836 }
837 retval = 1;
838 goto out;
839 }
840
841 out:
842 /* Free memory used for checking duplicates in the doubly linked list */
843 while(head) {
844 cur = head;
845 head = head->next;
846 free(cur);
847 }
848
849 return retval;
850 }
851
852
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
858 * directories.
859 */
860 static void check_dirlink_ancestors(SGlobPtr gptr, uint32_t dir_id)
861 {
862 int retval = 0;
863 CatalogRecord rec;
864 CatalogKey key;
865 uint16_t recsize;
866
867 while ((dir_id != kHFSRootFolderID) && (dir_id != gptr->dirlink_priv_dir_id)) {
868 retval = GetCatalogRecordByID(gptr, dir_id, true, &key, &rec, &recsize);
869 if (retval != 0) {
870 break;
871 }
872
873 if (rec.recordType != kHFSPlusFolderRecord) {
874 break;
875 }
876
877 if ((rec.hfsPlusFolder.flags & kHFSHasChildLinkMask) == 0) {
878 (void) record_parent_badflags(gptr, dir_id,
879 rec.hfsPlusFolder.flags,
880 rec.hfsPlusFolder.flags | kHFSHasChildLinkMask);
881 }
882
883 dir_id = key.hfsPlus.parentID;
884 }
885
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.
892 */
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;
896 }
897
898 return;
899 }
900
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.
906 *
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.
911 */
912 static void dirlink_check(SGlobPtr gptr, PrimeBuckets *bucket,
913 HFSPlusCatalogFile *rec, HFSPlusCatalogKey *key, Boolean isdir)
914 {
915 /* Add this directory hard link and corresponding inode number pair
916 * to prime buckets
917 */
918 #if DEBUG_HARDLINKCHECK
919 if (fsckGetVerbosity(gptr->context) >= kDebugLog)
920 plog("link_check: adding <%u, %u>\n", rec->hl_linkReference, rec->fileID);
921 #endif
922
923 hardlink_add_bucket(bucket, rec->hl_linkReference, rec->fileID);
924
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);
930 }
931
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);
937 }
938
939 /* XXX - Check resource fork/alias data */
940
941 /* Check if all the parent directories have the kHFSHasChildLinkBit set */
942 check_dirlink_ancestors(gptr, key->parentID);
943 }
944
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.
951 *
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.
958 *
959 * Returns -
960 * zero - on successfully determining if the next child record exists
961 * or not.
962 * non-zero - error, like during btree lookup, etc.
963 */
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)
967 {
968 int retval;
969 SFCB *fcb;
970 int return_next_rec = true;
971 BTreeIterator iterator;
972 FSBufferDescriptor buf_desc;
973 uint16_t recsize;
974 CatalogRecord rec;
975 CatalogKey *key;
976
977 *child_inode_id = 0;
978 *child_catalog_id = 0;
979 *is_dirinode = false;
980
981 fcb = gptr->calculatedCatalogFCB;
982 key = (CatalogKey *)&iterator.key;
983
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.
990 */
991 if (cur_child_catalog_id == 0) {
992 iterate_parent:
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.
996 */
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,
1004 &iterator);
1005 if ((retval != 0) && (retval != btNotFound)) {
1006 goto out;
1007 }
1008 } else {
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);
1018 if (retval) {
1019 return_next_rec = false;
1020 goto iterate_parent;
1021 }
1022
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;
1028 }
1029
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);
1041 if (retval) {
1042 return_next_rec = false;
1043 goto iterate_parent;
1044 }
1045 }
1046
1047 /* Lookup the next record */
1048 retval = BTIterateRecord(fcb, kBTreeNextRecord, &iterator, &buf_desc,
1049 &recsize);
1050 while (retval == 0) {
1051 /* Not the same parent anymore, stop the search */
1052 if (key->hfsPlus.parentID != parent_id) {
1053 break;
1054 }
1055
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.
1060 */
1061 if (return_next_rec) {
1062 if (rec.hfsPlusFolder.flags & kHFSHasLinkChainMask) {
1063 *is_dirinode = true;
1064 }
1065 *child_inode_id = rec.hfsPlusFolder.folderID;
1066 *child_catalog_id = rec.hfsPlusFolder.folderID;
1067 break;
1068 }
1069 /* If the current record is the current child, we
1070 * have to return the next child record.
1071 */
1072 if (rec.hfsPlusFolder.folderID == cur_child_catalog_id) {
1073 return_next_rec = true;
1074 }
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
1081 * check.
1082 */
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.
1090 */
1091 if (return_next_rec) {
1092 *child_inode_id = rec.hfsPlusFile.hl_linkReference;
1093 *child_catalog_id = rec.hfsPlusFile.fileID;
1094 *is_dirinode = true;
1095 break;
1096 }
1097 /* If the current record is the current child,
1098 * we have to return the next child record.
1099 */
1100 if (rec.hfsPlusFile.fileID == cur_child_catalog_id) {
1101 return_next_rec = true;
1102 }
1103 }
1104 }
1105
1106 /* Lookup the next record */
1107 retval = BTIterateRecord(fcb, kBTreeNextRecord, &iterator,
1108 &buf_desc, &recsize);
1109 }
1110
1111 if (retval == btNotFound) {
1112 retval = 0;
1113 }
1114
1115 out:
1116 return retval;
1117 }
1118
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).
1124 *
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.
1127 */
1128 struct dfs_id {
1129 uint32_t inode_id;
1130 uint32_t catalog_id;
1131 };
1132
1133 struct dfs_stack {
1134 uint32_t depth;
1135 struct dfs_id *idptr;
1136 };
1137
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.
1142 */
1143 #define DIRLINK_DEFAULT_DFS_MAX_DEPTH PATH_MAX/2
1144
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,
1147 * return zero.
1148 */
1149 static int check_loops(struct dfs_stack *dfs, struct dfs_id id)
1150 {
1151 int retval = 0;
1152 int i;
1153
1154 for (i = 0; i < dfs->depth; i++) {
1155 if (dfs->idptr[i].inode_id == id.inode_id) {
1156 retval = 1;
1157 break;
1158 }
1159 }
1160
1161 return retval;
1162 }
1163
1164 static void print_dfs(struct dfs_stack *dfs)
1165 {
1166 int i;
1167
1168 plog ("\t");
1169 for (i = 0; i < dfs->depth; i++) {
1170 plog ("(%u,%u) ", dfs->idptr[i].inode_id, dfs->idptr[i].catalog_id);
1171 }
1172 plog ("\n");
1173 }
1174
1175 /* Store information about visited directory inodes such that we do not
1176 * reenter the directory multiple times while following directory hard links.
1177 */
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 */
1183 };
1184
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.
1187 */
1188 static void mark_dirinode_visited(uint32_t dirinode_id, struct visited_dirinode *visited)
1189 {
1190 if (visited->list == NULL) {
1191 return;
1192 }
1193
1194 if (visited->offset >= visited->size) {
1195 visited->offset = 0;
1196 visited->wrapped = true;
1197 }
1198 visited->list[visited->offset] = dirinode_id;
1199 visited->offset++;
1200 }
1201
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)
1204 {
1205 int is_visited = false;
1206 uint32_t end_offset;
1207 uint32_t off;
1208
1209 if (visited->list == NULL) {
1210 return is_visited;
1211 }
1212
1213 /* If the list had wrapped, search the entire list */
1214 if (visited->wrapped == true) {
1215 end_offset = visited->size;
1216 } else {
1217 end_offset = visited->offset;
1218 }
1219
1220 for (off = 0; off < end_offset; off++) {
1221 if (visited->list[off] == dirinode_id) {
1222 is_visited = true;
1223 break;
1224 }
1225 }
1226
1227 return is_visited;
1228 }
1229
1230 /* Check if there are any loops in the directory hierarchy.
1231 *
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.
1246 *
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.
1249 *
1250 * Returns -
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.
1254 */
1255 static int check_hierarchy_loops(SGlobPtr gptr)
1256 {
1257 int retval = 0;
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;
1265
1266 #define DFS_PUSH(dfsid) \
1267 { \
1268 dfs.idptr[dfs.depth].inode_id = dfsid.inode_id; \
1269 dfs.idptr[dfs.depth].catalog_id = dfsid.catalog_id; \
1270 dfs.depth++; \
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) { \
1274 break; \
1275 } else { \
1276 dfs.idptr = tptr; \
1277 max_alloc_depth += DIRLINK_DEFAULT_DFS_MAX_DEPTH; \
1278 } \
1279 } \
1280 }
1281
1282 #define DFS_POP(dfsid) \
1283 { \
1284 dfs.depth--; \
1285 dfsid.inode_id = dfs.idptr[dfs.depth].inode_id; \
1286 dfsid.catalog_id = dfs.idptr[dfs.depth].catalog_id; \
1287 }
1288
1289 #define DFS_PEEK(dfsid) \
1290 { \
1291 dfsid.inode_id = dfs.idptr[dfs.depth-1].inode_id; \
1292 dfsid.catalog_id = dfs.idptr[dfs.depth-1].catalog_id; \
1293 }
1294
1295 /* Initialize the traversal stack */
1296 dfs.idptr = malloc(max_alloc_depth * sizeof(struct dfs_id));
1297 if (!dfs.idptr) {
1298 return ENOMEM;
1299 }
1300 dfs.depth = 0;
1301
1302 /* Initialize unknown child IDs which are used when a directory is
1303 * seen for the first time.
1304 */
1305 unknown_child.inode_id = unknown_child.catalog_id = 0;
1306
1307 /* Allocate visited list for total number of directory inodes seen */
1308 if (gptr->calculated_dirinodes) {
1309 visited.size = gptr->calculated_dirinodes;
1310 } else {
1311 visited.size = 1024;
1312 }
1313
1314 /* If visited list allocation failed, perform search without cache */
1315 visited.list = malloc(visited.size * sizeof(uint32_t));
1316 if (visited.list == NULL) {
1317 visited.size = 0;
1318 if (fsckGetVerbosity(gptr->context) >= kDebugLog) {
1319 plog ("\tcheck_loops: Allocation failed for visited list\n");
1320 }
1321 }
1322 visited.offset = 0;
1323 visited.wrapped = false;
1324
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;
1328 } else {
1329 parent.inode_id = parent.catalog_id = kHFSRootFolderID;
1330 }
1331
1332 /* Initialize the first parent and its first unknown child */
1333 do {
1334 DFS_PUSH(parent);
1335 DFS_PUSH(unknown_child);
1336 } while (0);
1337
1338 while (dfs.depth > 1) {
1339 DFS_POP(child);
1340 DFS_PEEK(parent);
1341 retval = find_next_child_dir(gptr, parent.inode_id,
1342 child.catalog_id, &(child.inode_id),
1343 &(child.catalog_id), &is_dirinode);
1344 if (retval) {
1345 break;
1346 }
1347
1348 if (child.inode_id) {
1349 retval = check_loops(&dfs, child);
1350 if (retval) {
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);
1354 print_dfs(&dfs);
1355 }
1356 gptr->CatStat |= S_LinkErrNoRepair;
1357 retval = E_DirLoop;
1358 break;
1359 }
1360
1361 /* Push the current child on traversal stack */
1362 DFS_PUSH(child);
1363
1364 /* Traverse down directory inode only if it was not
1365 * visited previously and mark it visited.
1366 */
1367 if (is_dirinode == true) {
1368 if (is_dirinode_visited(child.inode_id, &visited)) {
1369 continue;
1370 } else {
1371 mark_dirinode_visited(child.inode_id, &visited);
1372 }
1373 }
1374
1375 /* Push unknown child to traverse down the child directory */
1376 DFS_PUSH(unknown_child);
1377 }
1378 }
1379
1380 if (dfs.depth >= max_alloc_depth) {
1381 fsckPrint(gptr->context, E_DirHardLinkNesting);
1382 if (fsckGetVerbosity(gptr->context) >= kDebugLog) {
1383 print_dfs(&dfs);
1384 }
1385 gptr->CatStat |= S_LinkErrNoRepair;
1386 retval = E_DirHardLinkNesting;
1387 }
1388
1389 if (dfs.idptr) {
1390 free(dfs.idptr);
1391 }
1392 if (visited.list) {
1393 free(visited.list);
1394 }
1395 return retval;
1396 }
1397
1398 /* This function traverses the entire catalog btree, and checks all
1399 * directory inodes and directory hard links found.
1400 *
1401 * Returns zero if the check is successful, and non-zero if an error was
1402 * encountered during verification.
1403 */
1404 int dirhardlink_check(SGlobPtr gptr)
1405 {
1406 int retval = 0;
1407 uint16_t selcode;
1408 uint32_t hint;
1409
1410 CatalogRecord catrec;
1411 CatalogKey catkey;
1412 uint16_t recsize;
1413
1414 PrimeBuckets *inode_view = NULL;
1415 PrimeBuckets *dirlink_view = NULL;
1416
1417 /* Check if the volume is HFS+ */
1418 if (VolumeObjectIsHFSPlus() == false) {
1419 goto out;
1420 }
1421
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)) {
1426 goto out;
1427 }
1428
1429 fsckPrint(gptr->context, hfsMultiLinkDirCheck);
1430
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);
1433 }
1434
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.
1438 */
1439 if (gptr->dirlink_priv_dir_id == 0) {
1440 fsckPrint(gptr->context, E_MissingPrivDir);
1441 gptr->CatStat |= S_LinkErrNoRepair;
1442 }
1443
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.
1451 */
1452 inode_view = (PrimeBuckets *)calloc(1, sizeof(PrimeBuckets));
1453 if (!inode_view) {
1454 retval = ENOMEM;
1455 goto out;
1456 }
1457
1458 dirlink_view = (PrimeBuckets *)calloc(1, sizeof(PrimeBuckets));
1459 if (!dirlink_view) {
1460 retval = ENOMEM;
1461 goto out;
1462 }
1463
1464 /* Traverse the catalog btree from the first record */
1465 selcode = 0x8001;
1466 retval = GetBTreeRecord(gptr->calculatedCatalogFCB, selcode, &catkey,
1467 &catrec, &recsize, &hint);
1468 if (retval != 0) {
1469 goto out;
1470 }
1471
1472 /* Set code to get the next record */
1473 selcode = 1;
1474 do {
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));
1480 }
1481
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,
1486 &catrec,
1487 &catkey,
1488 true);
1489 if (retval) {
1490 /* If the corruption detected requires
1491 * knowledge of all associated directory
1492 * hard links for repair, stop the
1493 * catalog btree traversal
1494 */
1495 retval = 0;
1496 break;
1497 }
1498 }
1499 } else
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
1506 * check.
1507 */
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);
1514 }
1515 }
1516
1517 retval = GetBTreeRecord(gptr->calculatedCatalogFCB, 1,
1518 &catkey, &catrec, &recsize, &hint);
1519 } while (retval == noErr);
1520
1521 if (retval == btNotFound) {
1522 retval = 0;
1523 } else if (retval != 0) {
1524 goto out;
1525 }
1526
1527 /* Compare the two prime number buckets only the if catalog traversal did
1528 * not detect incorrect number of directory hard links corruption.
1529 */
1530 if ((gptr->CatStat & S_DirHardLinkChain) == 0) {
1531 retval = compare_prime_buckets(inode_view, dirlink_view);
1532 if (retval) {
1533 record_link_badchain(gptr, true);
1534 if (fsckGetVerbosity(gptr->context) >= kDebugLog) {
1535 plog ("\tdirlink prime buckets do not match\n");
1536 }
1537 retval = 0;
1538 }
1539 }
1540
1541 /* Check if there are any loops in the directory hierarchy */
1542 retval = check_hierarchy_loops(gptr);
1543 if (retval) {
1544 retval = 0;
1545 goto out;
1546 }
1547
1548 out:
1549 if (inode_view) {
1550 free (inode_view);
1551 }
1552 if (dirlink_view) {
1553 free (dirlink_view);
1554 }
1555
1556 return retval;
1557 }
1558