]> git.saurik.com Git - hfs.git/blob - fsck_hfs/dfalib/dirhardlink.c
hfs-226.1.1.tar.gz
[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 /* 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);
471 }
472 retval = ENOENT;
473 goto out;
474 }
475
476 /* Attribute data should be null terminated, attrSize includes
477 * size of the attribute data including the null termination.
478 */
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);
482 }
483 retval = ENOENT;
484 goto out;
485 }
486
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);
492 }
493 retval = ENOENT;
494 goto out;
495 }
496 }
497
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);
502 }
503 *first_link_id = 0;
504 retval = ENOENT;
505 goto out;
506 }
507 }
508 } else {
509 *first_link_id = 0;
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);
516 }
517 *first_link_id = 0;
518 retval = ENOENT;
519 goto out;
520 }
521 } else {
522 CatalogRecord rec;
523 CatalogKey key;
524 uint16_t recsize;
525
526 /* No record or bad record provided, look it up */
527 retval = GetCatalogRecordByID(gptr, inode_id, true, &key, &rec, &recsize);
528 if (retval == 0) {
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);
534 }
535 *first_link_id = 0;
536 retval = ENOENT;
537 }
538 } else {
539 *first_link_id = 0;
540 retval = ENOENT;
541 }
542 }
543 }
544
545 out:
546 return retval;
547 }
548
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.
553 */
554 void hardlink_add_bucket(PrimeBuckets *bucket, uint32_t inode_id,
555 uint32_t cur_link_id)
556 {
557 uint64_t num;
558
559 num = ((uint64_t)inode_id << 32) | cur_link_id;
560
561 add_prime_bucket_uint64(bucket, num);
562 }
563
564 /* Structure to store the directory hard link IDs found during doubly linked
565 * list traversal in inode_check()
566 */
567 struct link_list {
568 uint32_t link_id;
569 struct link_list *next;
570 };
571
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.
575 *
576 * Returns -
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.
581 */
582 int inode_check(SGlobPtr gptr, PrimeBuckets *bucket,
583 CatalogRecord *rec, CatalogKey *key, Boolean isdir)
584 {
585 int retval = 0;
586 uint32_t inode_id;
587 uint32_t cur_link_id;
588 uint32_t prev_link_id;
589 uint32_t count;
590 uint32_t linkCount;
591 char calc_name[32];
592 char found_name[NAME_MAX];
593 size_t calc_len;
594 size_t found_len;
595 CatalogKey linkkey;
596 CatalogRecord linkrec;
597 uint16_t recsize;
598 int flags;
599 uint32_t parentid;
600 uint32_t link_ref_num = 0;
601
602 struct link_list *head = NULL;
603 struct link_list *cur;
604
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';
608
609 if (isdir) {
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;
614 } else {
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);
620 }
621
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);
625 }
626
627 /* Compare the names for directory inode only because the names
628 * of file inodes can have random number suffixed.
629 */
630 if (isdir) {
631 (void) snprintf(calc_name, sizeof(calc_name), "%s%u", HFS_DIRINODE_PREFIX, inode_id);
632 calc_len = strlen(calc_name);
633
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,
637 calc_name);
638 }
639 }
640
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);
646 }
647 retval = 1;
648 goto out;
649 }
650
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.
656 */
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);
661 } else {
662 filelink_hash_inode(link_ref_num, linkCount);
663 retval = 0;
664 goto out;
665 }
666 }
667
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);
670 if (retval) {
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);
674 }
675 goto out;
676 }
677
678 /* Check doubly linked list of hard links that point to this inode */
679 prev_link_id = 0;
680 count = 0;
681
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);
686 if (retval) {
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);
690 }
691 goto out;
692 }
693
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);
699 }
700 retval = 1;
701 goto out;
702 }
703
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);
709 }
710 retval = 1;
711 goto out;
712 }
713
714 if (isdir) {
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);
724 }
725 }
726
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);
732 }
733 retval = 1;
734 goto out;
735 }
736
737 } else {
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);
746 }
747 }
748
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);
754 }
755 retval = 1;
756 goto out;
757 }
758 }
759
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.
764 */
765 if (isdir) {
766 hardlink_add_bucket(bucket, inode_id, cur_link_id);
767 } else {
768 hardlink_add_bucket(bucket, link_ref_num, cur_link_id);
769 }
770
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);
776 }
777 retval = 1;
778 goto out;
779 }
780
781 /* Check if we saw this directory hard link previously */
782 cur = head;
783 while (cur) {
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);
787 }
788 record_link_badchain(gptr, isdir);
789 retval = 1;
790 goto out;
791 }
792 cur = cur->next;
793 }
794
795 /* Add the new unique directory hard link to our list */
796 cur = malloc(sizeof(struct link_list));
797 if (!cur) {
798 retval = ENOMEM;
799 goto out;
800 }
801 cur->link_id = cur_link_id;
802 cur->next = head;
803 head = cur;
804
805 count++;
806 prev_link_id = cur_link_id;
807 cur_link_id = linkrec.hfsPlusFile.hl_nextLinkID;
808 }
809
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);
815 }
816 retval = 1;
817 goto out;
818 }
819
820 out:
821 /* Free memory used for checking duplicates in the doubly linked list */
822 while(head) {
823 cur = head;
824 head = head->next;
825 free(cur);
826 }
827
828 return retval;
829 }
830
831
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
837 * directories.
838 */
839 static void check_dirlink_ancestors(SGlobPtr gptr, uint32_t dir_id)
840 {
841 int retval = 0;
842 CatalogRecord rec;
843 CatalogKey key;
844 uint16_t recsize;
845
846 while ((dir_id != kHFSRootFolderID) && (dir_id != gptr->dirlink_priv_dir_id)) {
847 retval = GetCatalogRecordByID(gptr, dir_id, true, &key, &rec, &recsize);
848 if (retval != 0) {
849 break;
850 }
851
852 if (rec.recordType != kHFSPlusFolderRecord) {
853 break;
854 }
855
856 if ((rec.hfsPlusFolder.flags & kHFSHasChildLinkMask) == 0) {
857 (void) record_parent_badflags(gptr, dir_id,
858 rec.hfsPlusFolder.flags,
859 rec.hfsPlusFolder.flags | kHFSHasChildLinkMask);
860 }
861
862 dir_id = key.hfsPlus.parentID;
863 }
864
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.
871 */
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;
875 }
876
877 return;
878 }
879
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.
885 *
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.
890 */
891 static void dirlink_check(SGlobPtr gptr, PrimeBuckets *bucket,
892 HFSPlusCatalogFile *rec, HFSPlusCatalogKey *key, Boolean isdir)
893 {
894 /* Add this directory hard link and corresponding inode number pair
895 * to prime buckets
896 */
897 #if DEBUG_HARDLINKCHECK
898 if (fsckGetVerbosity(gptr->context) >= kDebugLog)
899 plog("link_check: adding <%u, %u>\n", rec->hl_linkReference, rec->fileID);
900 #endif
901
902 hardlink_add_bucket(bucket, rec->hl_linkReference, rec->fileID);
903
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);
909 }
910
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);
916 }
917
918 /* XXX - Check resource fork/alias data */
919
920 /* Check if all the parent directories have the kHFSHasChildLinkBit set */
921 check_dirlink_ancestors(gptr, key->parentID);
922 }
923
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.
930 *
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.
937 *
938 * Returns -
939 * zero - on successfully determining if the next child record exists
940 * or not.
941 * non-zero - error, like during btree lookup, etc.
942 */
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)
946 {
947 int retval;
948 SFCB *fcb;
949 int return_next_rec = true;
950 BTreeIterator iterator;
951 FSBufferDescriptor buf_desc;
952 uint16_t recsize;
953 CatalogRecord rec;
954 CatalogKey *key;
955
956 *child_inode_id = 0;
957 *child_catalog_id = 0;
958 *is_dirinode = false;
959
960 fcb = gptr->calculatedCatalogFCB;
961 key = (CatalogKey *)&iterator.key;
962
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.
969 */
970 if (cur_child_catalog_id == 0) {
971 iterate_parent:
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.
975 */
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,
983 &iterator);
984 if ((retval != 0) && (retval != btNotFound)) {
985 goto out;
986 }
987 } else {
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);
997 if (retval) {
998 return_next_rec = false;
999 goto iterate_parent;
1000 }
1001
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;
1007 }
1008
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);
1020 if (retval) {
1021 return_next_rec = false;
1022 goto iterate_parent;
1023 }
1024 }
1025
1026 /* Lookup the next record */
1027 retval = BTIterateRecord(fcb, kBTreeNextRecord, &iterator, &buf_desc,
1028 &recsize);
1029 while (retval == 0) {
1030 /* Not the same parent anymore, stop the search */
1031 if (key->hfsPlus.parentID != parent_id) {
1032 break;
1033 }
1034
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.
1039 */
1040 if (return_next_rec) {
1041 if (rec.hfsPlusFolder.flags & kHFSHasLinkChainMask) {
1042 *is_dirinode = true;
1043 }
1044 *child_inode_id = rec.hfsPlusFolder.folderID;
1045 *child_catalog_id = rec.hfsPlusFolder.folderID;
1046 break;
1047 }
1048 /* If the current record is the current child, we
1049 * have to return the next child record.
1050 */
1051 if (rec.hfsPlusFolder.folderID == cur_child_catalog_id) {
1052 return_next_rec = true;
1053 }
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
1060 * check.
1061 */
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.
1069 */
1070 if (return_next_rec) {
1071 *child_inode_id = rec.hfsPlusFile.hl_linkReference;
1072 *child_catalog_id = rec.hfsPlusFile.fileID;
1073 *is_dirinode = true;
1074 break;
1075 }
1076 /* If the current record is the current child,
1077 * we have to return the next child record.
1078 */
1079 if (rec.hfsPlusFile.fileID == cur_child_catalog_id) {
1080 return_next_rec = true;
1081 }
1082 }
1083 }
1084
1085 /* Lookup the next record */
1086 retval = BTIterateRecord(fcb, kBTreeNextRecord, &iterator,
1087 &buf_desc, &recsize);
1088 }
1089
1090 if (retval == btNotFound) {
1091 retval = 0;
1092 }
1093
1094 out:
1095 return retval;
1096 }
1097
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).
1103 *
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.
1106 */
1107 struct dfs_id {
1108 uint32_t inode_id;
1109 uint32_t catalog_id;
1110 };
1111
1112 struct dfs_stack {
1113 uint32_t depth;
1114 struct dfs_id *idptr;
1115 };
1116
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.
1121 */
1122 #define DIRLINK_DEFAULT_DFS_MAX_DEPTH PATH_MAX/2
1123
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,
1126 * return zero.
1127 */
1128 static int check_loops(struct dfs_stack *dfs, struct dfs_id id)
1129 {
1130 int retval = 0;
1131 int i;
1132
1133 for (i = 0; i < dfs->depth; i++) {
1134 if (dfs->idptr[i].inode_id == id.inode_id) {
1135 retval = 1;
1136 break;
1137 }
1138 }
1139
1140 return retval;
1141 }
1142
1143 static void print_dfs(struct dfs_stack *dfs)
1144 {
1145 int i;
1146
1147 plog ("\t");
1148 for (i = 0; i < dfs->depth; i++) {
1149 plog ("(%u,%u) ", dfs->idptr[i].inode_id, dfs->idptr[i].catalog_id);
1150 }
1151 plog ("\n");
1152 }
1153
1154 /* Store information about visited directory inodes such that we do not
1155 * reenter the directory multiple times while following directory hard links.
1156 */
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 */
1162 };
1163
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.
1166 */
1167 static void mark_dirinode_visited(uint32_t dirinode_id, struct visited_dirinode *visited)
1168 {
1169 if (visited->list == NULL) {
1170 return;
1171 }
1172
1173 if (visited->offset >= visited->size) {
1174 visited->offset = 0;
1175 visited->wrapped = true;
1176 }
1177 visited->list[visited->offset] = dirinode_id;
1178 visited->offset++;
1179 }
1180
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)
1183 {
1184 int is_visited = false;
1185 uint32_t end_offset;
1186 uint32_t off;
1187
1188 if (visited->list == NULL) {
1189 return is_visited;
1190 }
1191
1192 /* If the list had wrapped, search the entire list */
1193 if (visited->wrapped == true) {
1194 end_offset = visited->size;
1195 } else {
1196 end_offset = visited->offset;
1197 }
1198
1199 for (off = 0; off < end_offset; off++) {
1200 if (visited->list[off] == dirinode_id) {
1201 is_visited = true;
1202 break;
1203 }
1204 }
1205
1206 return is_visited;
1207 }
1208
1209 /* Check if there are any loops in the directory hierarchy.
1210 *
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.
1225 *
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.
1228 *
1229 * Returns -
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.
1233 */
1234 static int check_hierarchy_loops(SGlobPtr gptr)
1235 {
1236 int retval = 0;
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;
1244
1245 #define DFS_PUSH(dfsid) \
1246 { \
1247 dfs.idptr[dfs.depth].inode_id = dfsid.inode_id; \
1248 dfs.idptr[dfs.depth].catalog_id = dfsid.catalog_id; \
1249 dfs.depth++; \
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) { \
1253 break; \
1254 } else { \
1255 dfs.idptr = tptr; \
1256 max_alloc_depth += DIRLINK_DEFAULT_DFS_MAX_DEPTH; \
1257 } \
1258 } \
1259 }
1260
1261 #define DFS_POP(dfsid) \
1262 { \
1263 dfs.depth--; \
1264 dfsid.inode_id = dfs.idptr[dfs.depth].inode_id; \
1265 dfsid.catalog_id = dfs.idptr[dfs.depth].catalog_id; \
1266 }
1267
1268 #define DFS_PEEK(dfsid) \
1269 { \
1270 dfsid.inode_id = dfs.idptr[dfs.depth-1].inode_id; \
1271 dfsid.catalog_id = dfs.idptr[dfs.depth-1].catalog_id; \
1272 }
1273
1274 /* Initialize the traversal stack */
1275 dfs.idptr = malloc(max_alloc_depth * sizeof(struct dfs_id));
1276 if (!dfs.idptr) {
1277 return ENOMEM;
1278 }
1279 dfs.depth = 0;
1280
1281 /* Initialize unknown child IDs which are used when a directory is
1282 * seen for the first time.
1283 */
1284 unknown_child.inode_id = unknown_child.catalog_id = 0;
1285
1286 /* Allocate visited list for total number of directory inodes seen */
1287 if (gptr->calculated_dirinodes) {
1288 visited.size = gptr->calculated_dirinodes;
1289 } else {
1290 visited.size = 1024;
1291 }
1292
1293 /* If visited list allocation failed, perform search without cache */
1294 visited.list = malloc(visited.size * sizeof(uint32_t));
1295 if (visited.list == NULL) {
1296 visited.size = 0;
1297 if (fsckGetVerbosity(gptr->context) >= kDebugLog) {
1298 plog ("\tcheck_loops: Allocation failed for visited list\n");
1299 }
1300 }
1301 visited.offset = 0;
1302 visited.wrapped = false;
1303
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;
1307 } else {
1308 parent.inode_id = parent.catalog_id = kHFSRootFolderID;
1309 }
1310
1311 /* Initialize the first parent and its first unknown child */
1312 do {
1313 DFS_PUSH(parent);
1314 DFS_PUSH(unknown_child);
1315 } while (0);
1316
1317 while (dfs.depth > 1) {
1318 DFS_POP(child);
1319 DFS_PEEK(parent);
1320 retval = find_next_child_dir(gptr, parent.inode_id,
1321 child.catalog_id, &(child.inode_id),
1322 &(child.catalog_id), &is_dirinode);
1323 if (retval) {
1324 break;
1325 }
1326
1327 if (child.inode_id) {
1328 retval = check_loops(&dfs, child);
1329 if (retval) {
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);
1333 print_dfs(&dfs);
1334 }
1335 gptr->CatStat |= S_LinkErrNoRepair;
1336 retval = E_DirLoop;
1337 break;
1338 }
1339
1340 /* Push the current child on traversal stack */
1341 DFS_PUSH(child);
1342
1343 /* Traverse down directory inode only if it was not
1344 * visited previously and mark it visited.
1345 */
1346 if (is_dirinode == true) {
1347 if (is_dirinode_visited(child.inode_id, &visited)) {
1348 continue;
1349 } else {
1350 mark_dirinode_visited(child.inode_id, &visited);
1351 }
1352 }
1353
1354 /* Push unknown child to traverse down the child directory */
1355 DFS_PUSH(unknown_child);
1356 }
1357 }
1358
1359 if (dfs.depth >= max_alloc_depth) {
1360 fsckPrint(gptr->context, E_DirHardLinkNesting);
1361 if (fsckGetVerbosity(gptr->context) >= kDebugLog) {
1362 print_dfs(&dfs);
1363 }
1364 gptr->CatStat |= S_LinkErrNoRepair;
1365 retval = E_DirHardLinkNesting;
1366 }
1367
1368 if (dfs.idptr) {
1369 free(dfs.idptr);
1370 }
1371 if (visited.list) {
1372 free(visited.list);
1373 }
1374 return retval;
1375 }
1376
1377 /* This function traverses the entire catalog btree, and checks all
1378 * directory inodes and directory hard links found.
1379 *
1380 * Returns zero if the check is successful, and non-zero if an error was
1381 * encountered during verification.
1382 */
1383 int dirhardlink_check(SGlobPtr gptr)
1384 {
1385 int retval = 0;
1386 uint16_t selcode;
1387 uint32_t hint;
1388
1389 CatalogRecord catrec;
1390 CatalogKey catkey;
1391 uint16_t recsize;
1392
1393 PrimeBuckets *inode_view = NULL;
1394 PrimeBuckets *dirlink_view = NULL;
1395
1396 /* Check if the volume is HFS+ */
1397 if (VolumeObjectIsHFSPlus() == false) {
1398 goto out;
1399 }
1400
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)) {
1405 goto out;
1406 }
1407
1408 fsckPrint(gptr->context, hfsMultiLinkDirCheck);
1409
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);
1412 }
1413
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.
1417 */
1418 if (gptr->dirlink_priv_dir_id == 0) {
1419 fsckPrint(gptr->context, E_MissingPrivDir);
1420 gptr->CatStat |= S_LinkErrNoRepair;
1421 }
1422
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.
1430 */
1431 inode_view = (PrimeBuckets *)calloc(1, sizeof(PrimeBuckets));
1432 if (!inode_view) {
1433 retval = ENOMEM;
1434 goto out;
1435 }
1436
1437 dirlink_view = (PrimeBuckets *)calloc(1, sizeof(PrimeBuckets));
1438 if (!dirlink_view) {
1439 retval = ENOMEM;
1440 goto out;
1441 }
1442
1443 /* Traverse the catalog btree from the first record */
1444 selcode = 0x8001;
1445 retval = GetBTreeRecord(gptr->calculatedCatalogFCB, selcode, &catkey,
1446 &catrec, &recsize, &hint);
1447 if (retval != 0) {
1448 goto out;
1449 }
1450
1451 /* Set code to get the next record */
1452 selcode = 1;
1453 do {
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));
1459 }
1460
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,
1465 &catrec,
1466 &catkey,
1467 true);
1468 if (retval) {
1469 /* If the corruption detected requires
1470 * knowledge of all associated directory
1471 * hard links for repair, stop the
1472 * catalog btree traversal
1473 */
1474 retval = 0;
1475 break;
1476 }
1477 }
1478 } else
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
1485 * check.
1486 */
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);
1493 }
1494 }
1495
1496 retval = GetBTreeRecord(gptr->calculatedCatalogFCB, 1,
1497 &catkey, &catrec, &recsize, &hint);
1498 } while (retval == noErr);
1499
1500 if (retval == btNotFound) {
1501 retval = 0;
1502 } else if (retval != 0) {
1503 goto out;
1504 }
1505
1506 /* Compare the two prime number buckets only the if catalog traversal did
1507 * not detect incorrect number of directory hard links corruption.
1508 */
1509 if ((gptr->CatStat & S_DirHardLinkChain) == 0) {
1510 retval = compare_prime_buckets(inode_view, dirlink_view);
1511 if (retval) {
1512 record_link_badchain(gptr, true);
1513 if (fsckGetVerbosity(gptr->context) >= kDebugLog) {
1514 plog ("\tdirlink prime buckets do not match\n");
1515 }
1516 retval = 0;
1517 }
1518 }
1519
1520 /* Check if there are any loops in the directory hierarchy */
1521 retval = check_hierarchy_loops(gptr);
1522 if (retval) {
1523 retval = 0;
1524 goto out;
1525 }
1526
1527 out:
1528 if (inode_view) {
1529 free (inode_view);
1530 }
1531 if (dirlink_view) {
1532 free (dirlink_view);
1533 }
1534
1535 return retval;
1536 }
1537