]>
Commit | Line | Data |
---|---|---|
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 |