]>
Commit | Line | Data |
---|---|---|
51e135ce A |
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) { | |
927b7b56 A |
467 | unsigned long link_id; |
468 | ||
51e135ce A |
469 | /* Attribute should be an inline attribute */ |
470 | if (rec->recordType != kHFSPlusAttrInlineData) { | |
471 | if (fsckGetVerbosity(gptr->context) >= kDebugLog) { | |
472 | plog ("\tfirst link EA is not inline for dirinode=%u (found=0x%x)\n", inode_id, rec->recordType); | |
473 | } | |
474 | retval = ENOENT; | |
475 | goto out; | |
476 | } | |
477 | ||
478 | /* Attribute data should be null terminated, attrSize includes | |
479 | * size of the attribute data including the null termination. | |
480 | */ | |
481 | if (rec->attrData[rec->attrSize-1] != '\0') { | |
482 | if (fsckGetVerbosity(gptr->context) >= kDebugLog) { | |
483 | plog ("\tfirst link EA attrData is not NULL terminated for dirinode=%u\n", inode_id); | |
484 | } | |
485 | retval = ENOENT; | |
486 | goto out; | |
487 | } | |
488 | ||
489 | /* All characters are numbers in the attribute data */ | |
490 | for (i = 0; i < rec->attrSize-1; i++) { | |
491 | if (isdigit(rec->attrData[i]) == 0) { | |
492 | if (fsckGetVerbosity(gptr->context) >= kDebugLog) { | |
493 | plog ("\tfirst link EA attrData contains non-digit 0x%x for dirinode=%u\n", rec->attrData[i], inode_id); | |
494 | } | |
495 | retval = ENOENT; | |
496 | goto out; | |
497 | } | |
498 | } | |
499 | ||
927b7b56 A |
500 | link_id = strtoul((char *)&rec->attrData[0], NULL, 10); |
501 | if (link_id > UINT32_MAX) { | |
502 | if (fsckGetVerbosity(gptr->context) >= kDebugLog) { | |
503 | plog ("\tfirst link ID=%lu is > UINT32_MAX for dirinode=%u\n", link_id, inode_id); | |
504 | } | |
505 | *first_link_id = 0; | |
506 | retval = ENOENT; | |
507 | goto out; | |
508 | } | |
509 | *first_link_id = (uint32_t)link_id; | |
51e135ce A |
510 | if (*first_link_id < kHFSFirstUserCatalogNodeID) { |
511 | if (fsckGetVerbosity(gptr->context) >= kDebugLog) { | |
512 | plog ("\tfirst link ID=%u is < 16 for dirinode=%u\n", *first_link_id, inode_id); | |
513 | } | |
514 | *first_link_id = 0; | |
515 | retval = ENOENT; | |
516 | goto out; | |
517 | } | |
518 | } | |
519 | } else { | |
520 | *first_link_id = 0; | |
521 | if ((inode_rec != NULL) && | |
522 | (inode_rec->recordType == kHFSPlusFileRecord)) { | |
523 | *first_link_id = inode_rec->hfsPlusFile.hl_firstLinkID; | |
524 | if (*first_link_id < kHFSFirstUserCatalogNodeID) { | |
525 | if (fsckGetVerbosity(gptr->context) >= kDebugLog) { | |
526 | plog("\tfirst link ID=%u is < 16 for fileinode=%u\n", *first_link_id, inode_id); | |
527 | } | |
528 | *first_link_id = 0; | |
529 | retval = ENOENT; | |
530 | goto out; | |
531 | } | |
532 | } else { | |
533 | CatalogRecord rec; | |
534 | CatalogKey key; | |
535 | uint16_t recsize; | |
536 | ||
537 | /* No record or bad record provided, look it up */ | |
538 | retval = GetCatalogRecordByID(gptr, inode_id, true, &key, &rec, &recsize); | |
539 | if (retval == 0) { | |
540 | *first_link_id = rec.hfsPlusFile.hl_firstLinkID; | |
541 | if (rec.recordType != kHFSPlusFileRecord || | |
542 | *first_link_id < kHFSFirstUserCatalogNodeID) { | |
543 | if (fsckGetVerbosity(gptr->context) >= kDebugLog) { | |
544 | plog("\tfirst link ID=%u is < 16 for fileinode=%u\n", *first_link_id, inode_id); | |
545 | } | |
546 | *first_link_id = 0; | |
547 | retval = ENOENT; | |
548 | } | |
549 | } else { | |
550 | *first_link_id = 0; | |
551 | retval = ENOENT; | |
552 | } | |
553 | } | |
554 | } | |
555 | ||
556 | out: | |
557 | return retval; | |
558 | } | |
559 | ||
560 | /* Adds the directory inode, and directory hard link pair to the | |
561 | * prime remainder bucket provided. This is based on Chinese Remainder | |
562 | * Theorem, and the buckets are later compared to find if the directory | |
563 | * hard link chains for all directory inodes are valid. | |
564 | */ | |
565 | void hardlink_add_bucket(PrimeBuckets *bucket, uint32_t inode_id, | |
566 | uint32_t cur_link_id) | |
567 | { | |
568 | uint64_t num; | |
569 | ||
570 | num = ((uint64_t)inode_id << 32) | cur_link_id; | |
571 | ||
572 | add_prime_bucket_uint64(bucket, num); | |
573 | } | |
574 | ||
575 | /* Structure to store the directory hard link IDs found during doubly linked | |
576 | * list traversal in inode_check() | |
577 | */ | |
578 | struct link_list { | |
579 | uint32_t link_id; | |
580 | struct link_list *next; | |
581 | }; | |
582 | ||
583 | /* Verifies the inode record. Validates if the flags are set | |
584 | * correctly, parent is the private metadata directory, first link ID | |
585 | * is stored correctly, and the doubly linked * list of hard links is valid. | |
586 | * | |
587 | * Returns - | |
588 | * zero - if no corruption is detected, or the corruption detected is | |
589 | * such that a repair order can be created. | |
590 | * non-zero - if the corruption detected requires complete knowledge of | |
591 | * all the related directory hard links to suggest repair. | |
592 | */ | |
593 | int inode_check(SGlobPtr gptr, PrimeBuckets *bucket, | |
594 | CatalogRecord *rec, CatalogKey *key, Boolean isdir) | |
595 | { | |
596 | int retval = 0; | |
597 | uint32_t inode_id; | |
598 | uint32_t cur_link_id; | |
599 | uint32_t prev_link_id; | |
600 | uint32_t count; | |
601 | uint32_t linkCount; | |
602 | char calc_name[32]; | |
603 | char found_name[NAME_MAX]; | |
604 | size_t calc_len; | |
605 | size_t found_len; | |
606 | CatalogKey linkkey; | |
607 | CatalogRecord linkrec; | |
608 | uint16_t recsize; | |
609 | int flags; | |
610 | uint32_t parentid; | |
611 | uint32_t link_ref_num = 0; | |
612 | ||
613 | struct link_list *head = NULL; | |
614 | struct link_list *cur; | |
615 | ||
616 | (void) utf_encodestr(key->hfsPlus.nodeName.unicode, key->hfsPlus.nodeName.length * 2, | |
617 | (unsigned char *)found_name, &found_len, NAME_MAX); | |
618 | found_name[found_len] = '\0'; | |
619 | ||
620 | if (isdir) { | |
621 | inode_id = rec->hfsPlusFolder.folderID; | |
622 | flags = rec->hfsPlusFolder.flags; | |
623 | linkCount = rec->hfsPlusFolder.bsdInfo.special.linkCount; | |
624 | parentid = gptr->dirlink_priv_dir_id; | |
625 | } else { | |
927b7b56 A |
626 | unsigned long ref_num; |
627 | ||
51e135ce A |
628 | inode_id = rec->hfsPlusFile.fileID; |
629 | flags = rec->hfsPlusFile.flags; | |
630 | linkCount = rec->hfsPlusFile.bsdInfo.special.linkCount; | |
631 | parentid = gptr->filelink_priv_dir_id; | |
927b7b56 A |
632 | ref_num = strtoul(&found_name[strlen(HFS_INODE_PREFIX)], NULL, 10); |
633 | if (ref_num > UINT32_MAX) { | |
634 | if (fsckGetVerbosity(gptr->context) >= kDebugLog) { | |
635 | plog ("\tlink reference num=%lu is > UINT32_MAX for inode=%u\n", ref_num, inode_id); | |
636 | } | |
637 | retval = 1; | |
638 | goto out; | |
639 | } | |
640 | link_ref_num = (uint32_t)ref_num; | |
51e135ce A |
641 | } |
642 | ||
643 | /* inode should only reside in its corresponding private directory */ | |
644 | if ((parentid != 0) && (key->hfsPlus.parentID != parentid)) { | |
645 | (void) record_inode_badparent(gptr, inode_id, isdir, key->hfsPlus.parentID, parentid); | |
646 | } | |
647 | ||
648 | /* Compare the names for directory inode only because the names | |
649 | * of file inodes can have random number suffixed. | |
650 | */ | |
651 | if (isdir) { | |
652 | (void) snprintf(calc_name, sizeof(calc_name), "%s%u", HFS_DIRINODE_PREFIX, inode_id); | |
653 | calc_len = strlen(calc_name); | |
654 | ||
655 | if ((found_len != calc_len) || | |
656 | (strncmp(calc_name, found_name, calc_len) != 0)) { | |
657 | (void) record_inode_badname(gptr, inode_id, found_name, | |
658 | calc_name); | |
659 | } | |
660 | } | |
661 | ||
662 | /* At least one hard link should always point at an inode. */ | |
663 | if (linkCount == 0) { | |
664 | record_link_badchain(gptr, isdir); | |
665 | if (fsckGetVerbosity(gptr->context) >= kDebugLog) { | |
666 | plog ("\tlinkCount=0 for dirinode=%u\n", inode_id); | |
667 | } | |
668 | retval = 1; | |
669 | goto out; | |
670 | } | |
671 | ||
672 | /* A directory inode should always have kHFSHasLinkChainBit | |
673 | * set. A file inode created on pre-Leopard OS does not have | |
674 | * kHFSHasLinkChainBit set and firstLinkID is zero. Therefore | |
675 | * ignore such file inodes from CRT check and instead add the | |
676 | * the inode to hash used for checking link count. | |
677 | */ | |
678 | if ((flags & kHFSHasLinkChainMask) == 0) { | |
679 | if ((isdir) || (!isdir && (rec->hfsPlusFile.hl_firstLinkID != 0))) { | |
680 | (void) record_inode_badflags(gptr, inode_id, isdir, | |
681 | flags, flags | kHFSHasLinkChainMask, false); | |
682 | } else { | |
683 | filelink_hash_inode(link_ref_num, linkCount); | |
684 | retval = 0; | |
685 | goto out; | |
686 | } | |
687 | } | |
688 | ||
689 | /* Lookup the ID of first link from the extended attribute */ | |
690 | retval = get_first_link_id(gptr, rec, inode_id, isdir, &cur_link_id); | |
691 | if (retval) { | |
692 | record_link_badchain(gptr, isdir); | |
693 | if (fsckGetVerbosity(gptr->context) >= kDebugLog) { | |
694 | plog ("\tError getting first link ID for inode=%u\n", inode_id); | |
695 | } | |
696 | goto out; | |
697 | } | |
698 | ||
699 | /* Check doubly linked list of hard links that point to this inode */ | |
700 | prev_link_id = 0; | |
701 | count = 0; | |
702 | ||
703 | while (cur_link_id != 0) { | |
704 | /* Lookup the current directory link record */ | |
705 | retval = GetCatalogRecordByID(gptr, cur_link_id, true, | |
706 | &linkkey, &linkrec, &recsize); | |
707 | if (retval) { | |
708 | record_link_badchain(gptr, isdir); | |
709 | if (fsckGetVerbosity(gptr->context) >= kDebugLog) { | |
710 | plog ("\tError getting link=%u for inode=%u\n", cur_link_id, inode_id); | |
711 | } | |
712 | goto out; | |
713 | } | |
714 | ||
715 | /* Hard link is a file record */ | |
716 | if (linkrec.recordType != kHFSPlusFileRecord) { | |
717 | record_link_badchain(gptr, isdir); | |
718 | if (fsckGetVerbosity(gptr->context) >= kDebugLog) { | |
719 | plog ("\tIncorrect record type for link=%u for inode=%u (expected=2, found=%u)\n", cur_link_id, inode_id, linkrec.recordType); | |
720 | } | |
721 | retval = 1; | |
722 | goto out; | |
723 | } | |
724 | ||
725 | /* Hard link should have hard link bit set */ | |
726 | if ((linkrec.hfsPlusFile.flags & kHFSHasLinkChainMask) == 0) { | |
727 | (void) record_link_badchain(gptr, isdir); | |
728 | if (fsckGetVerbosity(gptr->context) >= kDebugLog) { | |
729 | plog ("\tIncorrect flag for link=%u for inode=%u (found=0x%x)\n", cur_link_id, inode_id, linkrec.hfsPlusFile.flags); | |
730 | } | |
731 | retval = 1; | |
732 | goto out; | |
733 | } | |
734 | ||
735 | if (isdir) { | |
736 | /* Check if the hard link has correct finder info */ | |
737 | if ((linkrec.hfsPlusFile.userInfo.fdType != kHFSAliasType) || | |
738 | (linkrec.hfsPlusFile.userInfo.fdCreator != kHFSAliasCreator) || | |
739 | ((linkrec.hfsPlusFile.userInfo.fdFlags & kIsAlias) == 0)) { | |
740 | record_link_badfinderinfo(gptr, linkrec.hfsPlusFile.fileID, isdir); | |
741 | if (fsckGetVerbosity(gptr->context) >= kDebugLog) { | |
742 | plog("\tdirlink: fdType = 0x%08lx, fdCreator = 0x%08lx\n", | |
743 | (unsigned long)linkrec.hfsPlusFile.userInfo.fdType, | |
744 | (unsigned long)linkrec.hfsPlusFile.userInfo.fdCreator); | |
745 | } | |
746 | } | |
747 | ||
748 | /* Check if hard link points to the current inode */ | |
749 | if (linkrec.hfsPlusFile.hl_linkReference != inode_id) { | |
750 | record_link_badchain(gptr, isdir); | |
751 | if (fsckGetVerbosity(gptr->context) >= kDebugLog) { | |
752 | plog ("\tIncorrect dirinode ID for dirlink=%u (expected=%u, found=%u)\n", cur_link_id, inode_id, linkrec.hfsPlusFile.hl_linkReference); | |
753 | } | |
754 | retval = 1; | |
755 | goto out; | |
756 | } | |
757 | ||
758 | } else { | |
759 | /* Check if the hard link has correct finder info */ | |
760 | if ((linkrec.hfsPlusFile.userInfo.fdType != kHardLinkFileType) || | |
761 | (linkrec.hfsPlusFile.userInfo.fdCreator != kHFSPlusCreator)) { | |
762 | record_link_badfinderinfo(gptr, linkrec.hfsPlusFile.fileID, isdir); | |
763 | if (fsckGetVerbosity(gptr->context) >= kDebugLog) { | |
764 | plog("\tfilelink: fdType = 0x%08lx, fdCreator = 0x%08lx\n", | |
765 | (unsigned long)linkrec.hfsPlusFile.userInfo.fdType, | |
766 | (unsigned long)linkrec.hfsPlusFile.userInfo.fdCreator); | |
767 | } | |
768 | } | |
769 | ||
770 | /* Check if hard link has correct link reference number */ | |
771 | if (linkrec.hfsPlusFile.hl_linkReference != link_ref_num) { | |
772 | record_link_badchain(gptr, isdir); | |
773 | if (fsckGetVerbosity(gptr->context) >= kDebugLog) { | |
774 | plog ("\tIncorrect link reference number for filelink=%u (expected=%u, found=%u)\n", cur_link_id, inode_id, linkrec.hfsPlusFile.hl_linkReference); | |
775 | } | |
776 | retval = 1; | |
777 | goto out; | |
778 | } | |
779 | } | |
780 | ||
781 | /* For directory hard links, add the directory inode ID and | |
782 | * the current link ID pair to the prime bucket. For file | |
783 | * hard links, add the link reference number and current | |
784 | * link ID pair to the prime bucket. | |
785 | */ | |
786 | if (isdir) { | |
787 | hardlink_add_bucket(bucket, inode_id, cur_link_id); | |
788 | } else { | |
789 | hardlink_add_bucket(bucket, link_ref_num, cur_link_id); | |
790 | } | |
791 | ||
792 | /* Check the previous directory hard link */ | |
793 | if (prev_link_id != linkrec.hfsPlusFile.hl_prevLinkID) { | |
794 | record_link_badchain(gptr, isdir); | |
795 | if (fsckGetVerbosity(gptr->context) >= kDebugLog) { | |
796 | plog ("\tIncorrect prevLinkID for link=%u for inode=%u (expected=%u, found=%u)\n", cur_link_id, inode_id, prev_link_id, linkrec.hfsPlusFile.hl_prevLinkID); | |
797 | } | |
798 | retval = 1; | |
799 | goto out; | |
800 | } | |
801 | ||
802 | /* Check if we saw this directory hard link previously */ | |
803 | cur = head; | |
804 | while (cur) { | |
805 | if (cur->link_id == cur_link_id) { | |
806 | if (fsckGetVerbosity(gptr->context) >= kDebugLog) { | |
807 | plog ("\tDuplicate link=%u found in list for inode=%u\n", cur_link_id, inode_id); | |
808 | } | |
809 | record_link_badchain(gptr, isdir); | |
810 | retval = 1; | |
811 | goto out; | |
812 | } | |
813 | cur = cur->next; | |
814 | } | |
815 | ||
816 | /* Add the new unique directory hard link to our list */ | |
817 | cur = malloc(sizeof(struct link_list)); | |
818 | if (!cur) { | |
819 | retval = ENOMEM; | |
820 | goto out; | |
821 | } | |
822 | cur->link_id = cur_link_id; | |
823 | cur->next = head; | |
824 | head = cur; | |
825 | ||
826 | count++; | |
827 | prev_link_id = cur_link_id; | |
828 | cur_link_id = linkrec.hfsPlusFile.hl_nextLinkID; | |
829 | } | |
830 | ||
831 | /* If the entire chain looks good, match the link count */ | |
832 | if (linkCount != count) { | |
833 | record_link_badchain(gptr, isdir); | |
834 | if (fsckGetVerbosity(gptr->context) >= kDebugLog) { | |
835 | plog ("\tIncorrect linkCount for inode=%u (expected=%u, found=%u)\n", inode_id, count, linkCount); | |
836 | } | |
837 | retval = 1; | |
838 | goto out; | |
839 | } | |
840 | ||
841 | out: | |
842 | /* Free memory used for checking duplicates in the doubly linked list */ | |
843 | while(head) { | |
844 | cur = head; | |
845 | head = head->next; | |
846 | free(cur); | |
847 | } | |
848 | ||
849 | return retval; | |
850 | } | |
851 | ||
852 | ||
853 | /* Check if the parent ancestors starting at the given directory has | |
854 | * the kHFSHasChildLinkBit set. This bit indicates that a descendant of | |
855 | * this directory is a directory hard link. Note that the root folder | |
856 | * and the "private directory data" directory does not have this bit | |
857 | * set, and the check stops as soon as we encounter one of these | |
858 | * directories. | |
859 | */ | |
860 | static void check_dirlink_ancestors(SGlobPtr gptr, uint32_t dir_id) | |
861 | { | |
862 | int retval = 0; | |
863 | CatalogRecord rec; | |
864 | CatalogKey key; | |
865 | uint16_t recsize; | |
866 | ||
867 | while ((dir_id != kHFSRootFolderID) && (dir_id != gptr->dirlink_priv_dir_id)) { | |
868 | retval = GetCatalogRecordByID(gptr, dir_id, true, &key, &rec, &recsize); | |
869 | if (retval != 0) { | |
870 | break; | |
871 | } | |
872 | ||
873 | if (rec.recordType != kHFSPlusFolderRecord) { | |
874 | break; | |
875 | } | |
876 | ||
877 | if ((rec.hfsPlusFolder.flags & kHFSHasChildLinkMask) == 0) { | |
878 | (void) record_parent_badflags(gptr, dir_id, | |
879 | rec.hfsPlusFolder.flags, | |
880 | rec.hfsPlusFolder.flags | kHFSHasChildLinkMask); | |
881 | } | |
882 | ||
883 | dir_id = key.hfsPlus.parentID; | |
884 | } | |
885 | ||
886 | /* If there was any problem in looking up parent directory, | |
887 | * the catalog check should have also detected the problem. | |
888 | * But there are cases which are not detected like names in | |
889 | * thread record and file/folder record key do not match. | |
890 | * Therefore force repair for incorrect number of thread | |
891 | * records if lookup fails. | |
892 | */ | |
893 | if ((dir_id != kHFSRootFolderID) && (dir_id != gptr->dirlink_priv_dir_id)) { | |
894 | fsckPrint(gptr->context, E_BadParentHierarchy, dir_id); | |
895 | gptr->CBTStat |= S_Orphan; | |
896 | } | |
897 | ||
898 | return; | |
899 | } | |
900 | ||
901 | /* Verifies the directory hard link record. Validates if the flags are set | |
902 | * correctly, the finderInfo fields are correct, and if the parent hierarchy | |
903 | * till the root folder (except the root folder) has the kHFSHasChildLinkBit | |
904 | * set correctly. This function also add the directory inode, and the | |
905 | * directory hard link pair to the prime buckets for comparison later. | |
906 | * | |
907 | * This function does not verify the first and the next directory hard link | |
908 | * pointers in the doubly linked list because the check is already done | |
909 | * in directory inode check (inode_check()) . Any orphan directory | |
910 | * hard link will also be detected later by the prime bucket comparison. | |
911 | */ | |
912 | static void dirlink_check(SGlobPtr gptr, PrimeBuckets *bucket, | |
913 | HFSPlusCatalogFile *rec, HFSPlusCatalogKey *key, Boolean isdir) | |
914 | { | |
915 | /* Add this directory hard link and corresponding inode number pair | |
916 | * to prime buckets | |
917 | */ | |
918 | #if DEBUG_HARDLINKCHECK | |
919 | if (fsckGetVerbosity(gptr->context) >= kDebugLog) | |
920 | plog("link_check: adding <%u, %u>\n", rec->hl_linkReference, rec->fileID); | |
921 | #endif | |
922 | ||
923 | hardlink_add_bucket(bucket, rec->hl_linkReference, rec->fileID); | |
924 | ||
925 | /* Check if the directory hard link has UF_IMMUTABLE bit set */ | |
926 | if ((rec->bsdInfo.ownerFlags & UF_IMMUTABLE) == 0) { | |
927 | record_dirlink_badownerflags(gptr, rec->fileID, | |
928 | rec->bsdInfo.ownerFlags, | |
929 | rec->bsdInfo.ownerFlags | UF_IMMUTABLE, false); | |
930 | } | |
931 | ||
932 | /* Check Finder Info */ | |
933 | if ((rec->userInfo.fdType != kHFSAliasType) || | |
934 | (rec->userInfo.fdCreator != kHFSAliasCreator) || | |
935 | ((rec->userInfo.fdFlags & kIsAlias) == 0)) { | |
936 | record_link_badfinderinfo(gptr, rec->fileID, isdir); | |
937 | } | |
938 | ||
939 | /* XXX - Check resource fork/alias data */ | |
940 | ||
941 | /* Check if all the parent directories have the kHFSHasChildLinkBit set */ | |
942 | check_dirlink_ancestors(gptr, key->parentID); | |
943 | } | |
944 | ||
945 | /* Searches the next child directory record to return given the parent ID | |
946 | * and the current child ID. If the current child ID is zero, this is the | |
947 | * first time we are looking up this directory, therefore return the | |
948 | * first child directory or directory hard link found. If child ID is | |
949 | * non-zero, return the first child directory or directory hard | |
950 | * link found after the current child record. | |
951 | * | |
952 | * For normal directories, the folder ID is returned as the new child inode_id | |
953 | * and catalog_id. For directory hard links, the inode_id of the directory | |
954 | * inode is returned in the inode_id, and the fileID of the directory hard link | |
955 | * is returned in the catalog_id. If the inode_id returned corresponds to a | |
956 | * directory inode, is_dirinode is set to true. If no child record is found, | |
957 | * or an error occurred on btree traversal, these values are zero. | |
958 | * | |
959 | * Returns - | |
960 | * zero - on successfully determining if the next child record exists | |
961 | * or not. | |
962 | * non-zero - error, like during btree lookup, etc. | |
963 | */ | |
964 | static int find_next_child_dir(SGlobPtr gptr, uint32_t parent_id, | |
965 | uint32_t cur_child_catalog_id, uint32_t *child_inode_id, | |
966 | uint32_t *child_catalog_id, uint32_t *is_dirinode) | |
967 | { | |
968 | int retval; | |
969 | SFCB *fcb; | |
970 | int return_next_rec = true; | |
971 | BTreeIterator iterator; | |
972 | FSBufferDescriptor buf_desc; | |
973 | uint16_t recsize; | |
974 | CatalogRecord rec; | |
975 | CatalogKey *key; | |
976 | ||
977 | *child_inode_id = 0; | |
978 | *child_catalog_id = 0; | |
979 | *is_dirinode = false; | |
980 | ||
981 | fcb = gptr->calculatedCatalogFCB; | |
982 | key = (CatalogKey *)&iterator.key; | |
983 | ||
984 | /* If no child record for this parent has been looked up previously, | |
985 | * return the first child record found. Otherwise lookup the | |
986 | * catalog record for the last child ID provided and return the | |
987 | * next valid child ID. If the lookup of the last child failed, | |
988 | * fall back to iterating all child records for given parent | |
989 | * directory and returning next child found after given child ID. | |
990 | */ | |
991 | if (cur_child_catalog_id == 0) { | |
992 | iterate_parent: | |
993 | /* Lookup catalog record with key containing given parent ID and NULL | |
994 | * name. This will place iterator just before the first child record | |
995 | * for this directory. | |
996 | */ | |
997 | bzero(&iterator, sizeof(iterator)); | |
998 | bzero(&buf_desc, sizeof(buf_desc)); | |
999 | buf_desc.bufferAddress = &rec; | |
1000 | buf_desc.itemCount = 1; | |
1001 | buf_desc.itemSize = sizeof(rec); | |
1002 | BuildCatalogKey(parent_id, NULL, true, key); | |
1003 | retval = BTSearchRecord(fcb, &iterator, kNoHint, &buf_desc, &recsize, | |
1004 | &iterator); | |
1005 | if ((retval != 0) && (retval != btNotFound)) { | |
1006 | goto out; | |
1007 | } | |
1008 | } else { | |
1009 | /* Lookup the thread record for the last child seen */ | |
1010 | bzero(&iterator, sizeof(iterator)); | |
1011 | bzero(&buf_desc, sizeof(buf_desc)); | |
1012 | buf_desc.bufferAddress = &rec; | |
1013 | buf_desc.itemCount = 1; | |
1014 | buf_desc.itemSize = sizeof(rec); | |
1015 | BuildCatalogKey(cur_child_catalog_id, NULL, true, key); | |
1016 | retval = BTSearchRecord(fcb, &iterator, kNoHint, &buf_desc, | |
1017 | &recsize, &iterator); | |
1018 | if (retval) { | |
1019 | return_next_rec = false; | |
1020 | goto iterate_parent; | |
1021 | } | |
1022 | ||
1023 | /* Check if really we found a thread record */ | |
1024 | if ((rec.recordType != kHFSPlusFolderThreadRecord) && | |
1025 | (rec.recordType != kHFSPlusFileThreadRecord)) { | |
1026 | return_next_rec = false; | |
1027 | goto iterate_parent; | |
1028 | } | |
1029 | ||
1030 | /* Lookup the corresponding file/folder record */ | |
1031 | bzero(&iterator, sizeof(iterator)); | |
1032 | bzero(&buf_desc, sizeof(buf_desc)); | |
1033 | buf_desc.bufferAddress = &rec; | |
1034 | buf_desc.itemCount = 1; | |
1035 | buf_desc.itemSize = sizeof(rec); | |
1036 | BuildCatalogKey(rec.hfsPlusThread.parentID, | |
1037 | (CatalogName *)&(rec.hfsPlusThread.nodeName), | |
1038 | true, (CatalogKey *)&(iterator.key)); | |
1039 | retval = BTSearchRecord(fcb, &iterator, kInvalidMRUCacheKey, | |
1040 | &buf_desc, &recsize, &iterator); | |
1041 | if (retval) { | |
1042 | return_next_rec = false; | |
1043 | goto iterate_parent; | |
1044 | } | |
1045 | } | |
1046 | ||
1047 | /* Lookup the next record */ | |
1048 | retval = BTIterateRecord(fcb, kBTreeNextRecord, &iterator, &buf_desc, | |
1049 | &recsize); | |
1050 | while (retval == 0) { | |
1051 | /* Not the same parent anymore, stop the search */ | |
1052 | if (key->hfsPlus.parentID != parent_id) { | |
1053 | break; | |
1054 | } | |
1055 | ||
1056 | if (rec.recordType == kHFSPlusFolderRecord) { | |
1057 | /* Found a catalog folder record, and if we are | |
1058 | * supposed to return the next record found, return | |
1059 | * this catalog folder. | |
1060 | */ | |
1061 | if (return_next_rec) { | |
1062 | if (rec.hfsPlusFolder.flags & kHFSHasLinkChainMask) { | |
1063 | *is_dirinode = true; | |
1064 | } | |
1065 | *child_inode_id = rec.hfsPlusFolder.folderID; | |
1066 | *child_catalog_id = rec.hfsPlusFolder.folderID; | |
1067 | break; | |
1068 | } | |
1069 | /* If the current record is the current child, we | |
1070 | * have to return the next child record. | |
1071 | */ | |
1072 | if (rec.hfsPlusFolder.folderID == cur_child_catalog_id) { | |
1073 | return_next_rec = true; | |
1074 | } | |
1075 | } else if (rec.recordType == kHFSPlusFileRecord) { | |
1076 | /* Check if the hard link bit is set with correct | |
1077 | * alias type/creator. If the parent is private | |
1078 | * metadata directory for file hard links, this | |
1079 | * is a hard link inode for an alias, and not | |
1080 | * directory hard link. Skip this file from our | |
1081 | * check. | |
1082 | */ | |
1083 | if ((rec.hfsPlusFile.flags & kHFSHasLinkChainMask) && | |
1084 | (rec.hfsPlusFile.userInfo.fdType == kHFSAliasType) && | |
1085 | (rec.hfsPlusFile.userInfo.fdCreator == kHFSAliasCreator) && | |
1086 | (key->hfsPlus.parentID != gptr->filelink_priv_dir_id)) { | |
1087 | /* Found a directory hard link, and if we are | |
1088 | * supposed to return the next record found, | |
1089 | * then return this directory hard link. | |
1090 | */ | |
1091 | if (return_next_rec) { | |
1092 | *child_inode_id = rec.hfsPlusFile.hl_linkReference; | |
1093 | *child_catalog_id = rec.hfsPlusFile.fileID; | |
1094 | *is_dirinode = true; | |
1095 | break; | |
1096 | } | |
1097 | /* If the current record is the current child, | |
1098 | * we have to return the next child record. | |
1099 | */ | |
1100 | if (rec.hfsPlusFile.fileID == cur_child_catalog_id) { | |
1101 | return_next_rec = true; | |
1102 | } | |
1103 | } | |
1104 | } | |
1105 | ||
1106 | /* Lookup the next record */ | |
1107 | retval = BTIterateRecord(fcb, kBTreeNextRecord, &iterator, | |
1108 | &buf_desc, &recsize); | |
1109 | } | |
1110 | ||
1111 | if (retval == btNotFound) { | |
1112 | retval = 0; | |
1113 | } | |
1114 | ||
1115 | out: | |
1116 | return retval; | |
1117 | } | |
1118 | ||
1119 | /* In-memory state for depth first traversal for finding loops in | |
1120 | * directory hierarchy. inode_id is the user visible ID of the given | |
1121 | * directory or directory hard link, and catalog_id is the inode ID for | |
1122 | * normal directories, and the directory hard link ID (file ID of the | |
1123 | * directory hard link record). | |
1124 | * | |
1125 | * The inode_id is used for checking loops in the hierarchy, whereas | |
1126 | * the catalog_id is used to maintain state for depth first traversal. | |
1127 | */ | |
1128 | struct dfs_id { | |
1129 | uint32_t inode_id; | |
1130 | uint32_t catalog_id; | |
1131 | }; | |
1132 | ||
1133 | struct dfs_stack { | |
1134 | uint32_t depth; | |
1135 | struct dfs_id *idptr; | |
1136 | }; | |
1137 | ||
1138 | /* Assuming that the name of a directory is single byte, the maximum depth | |
1139 | * of a directory hierarchy that can accommodate in PATH_MAX will be | |
1140 | * PATH_MAX/2. Note that catalog hierarchy check puts limitation of 100 | |
1141 | * on the maximum depth of a directory hierarchy. | |
1142 | */ | |
1143 | #define DIRLINK_DEFAULT_DFS_MAX_DEPTH PATH_MAX/2 | |
1144 | ||
1145 | /* Check if the current directory exists in the current traversal path. | |
1146 | * If yes, loops in directory exists and return non-zero value. If not, | |
1147 | * return zero. | |
1148 | */ | |
1149 | static int check_loops(struct dfs_stack *dfs, struct dfs_id id) | |
1150 | { | |
1151 | int retval = 0; | |
1152 | int i; | |
1153 | ||
1154 | for (i = 0; i < dfs->depth; i++) { | |
1155 | if (dfs->idptr[i].inode_id == id.inode_id) { | |
1156 | retval = 1; | |
1157 | break; | |
1158 | } | |
1159 | } | |
1160 | ||
1161 | return retval; | |
1162 | } | |
1163 | ||
1164 | static void print_dfs(struct dfs_stack *dfs) | |
1165 | { | |
1166 | int i; | |
1167 | ||
1168 | plog ("\t"); | |
1169 | for (i = 0; i < dfs->depth; i++) { | |
1170 | plog ("(%u,%u) ", dfs->idptr[i].inode_id, dfs->idptr[i].catalog_id); | |
1171 | } | |
1172 | plog ("\n"); | |
1173 | } | |
1174 | ||
1175 | /* Store information about visited directory inodes such that we do not | |
1176 | * reenter the directory multiple times while following directory hard links. | |
1177 | */ | |
1178 | struct visited_dirinode { | |
1179 | uint32_t *list; /* Pointer to array of IDs */ | |
1180 | uint32_t size; /* Maximum number of entries in the array */ | |
1181 | uint32_t offset; /* Offset where next ID will be added */ | |
1182 | uint32_t wrapped; /* Boolean, true if list wraps around */ | |
1183 | }; | |
1184 | ||
1185 | /* Add the given dirinode_id to the list of visited nodes. If all the slots | |
1186 | * in visited list are used, wrap around and add the new ID. | |
1187 | */ | |
1188 | static void mark_dirinode_visited(uint32_t dirinode_id, struct visited_dirinode *visited) | |
1189 | { | |
1190 | if (visited->list == NULL) { | |
1191 | return; | |
1192 | } | |
1193 | ||
1194 | if (visited->offset >= visited->size) { | |
1195 | visited->offset = 0; | |
1196 | visited->wrapped = true; | |
1197 | } | |
1198 | visited->list[visited->offset] = dirinode_id; | |
1199 | visited->offset++; | |
1200 | } | |
1201 | ||
1202 | /* Check if given directory inode exists in the visited list or not */ | |
1203 | static int is_dirinode_visited(uint32_t dirinode_id, struct visited_dirinode *visited) | |
1204 | { | |
1205 | int is_visited = false; | |
1206 | uint32_t end_offset; | |
1207 | uint32_t off; | |
1208 | ||
1209 | if (visited->list == NULL) { | |
1210 | return is_visited; | |
1211 | } | |
1212 | ||
1213 | /* If the list had wrapped, search the entire list */ | |
1214 | if (visited->wrapped == true) { | |
1215 | end_offset = visited->size; | |
1216 | } else { | |
1217 | end_offset = visited->offset; | |
1218 | } | |
1219 | ||
1220 | for (off = 0; off < end_offset; off++) { | |
1221 | if (visited->list[off] == dirinode_id) { | |
1222 | is_visited = true; | |
1223 | break; | |
1224 | } | |
1225 | } | |
1226 | ||
1227 | return is_visited; | |
1228 | } | |
1229 | ||
1230 | /* Check if there are any loops in the directory hierarchy. | |
1231 | * | |
1232 | * This function performs a depth first traversal of directories as they | |
1233 | * will be visible to the user. If the lookup of private metadata directory | |
1234 | * succeeded in dirlink_init(), the traversal starts from the private | |
1235 | * metadata directory. Otherwise it starts at the root folder. It stores | |
1236 | * the current depth first traversal state, and looks up catalog records as | |
1237 | * required. The current traversal state consists of two IDs, the user | |
1238 | * visible ID or inode_id, and the on-disk ID or catalog_id. For normal | |
1239 | * directories, the user visible ID is same as the on-disk ID, but for | |
1240 | * directory hard links, the user visible ID is the inode ID, and the | |
1241 | * on-disk ID is the file ID of the directory hard link. This function | |
1242 | * stores the list of visited directory inode ID and checks the list before | |
1243 | * traversing down the directory inode hierarchy. After traversing down a | |
1244 | * directory inode and checking that is valid, it adds the directory inode | |
1245 | * ID to the visited list. | |
1246 | * | |
1247 | * The inode_id is used for checking loops in the hierarchy, whereas | |
1248 | * the catalog_id is used to maintain state for depth first traversal. | |
1249 | * | |
1250 | * Returns - | |
1251 | * zero - if the check was performed successfully, and no loops exist | |
1252 | * in the directory hierarchy. | |
1253 | * non-zero - on error, or if loops were detected in directory hierarchy. | |
1254 | */ | |
1255 | static int check_hierarchy_loops(SGlobPtr gptr) | |
1256 | { | |
1257 | int retval = 0; | |
1258 | struct dfs_stack dfs; | |
1259 | struct dfs_id unknown_child; | |
1260 | struct dfs_id child; | |
1261 | struct dfs_id parent; | |
1262 | struct visited_dirinode visited; | |
1263 | size_t max_alloc_depth = DIRLINK_DEFAULT_DFS_MAX_DEPTH; | |
1264 | uint32_t is_dirinode; | |
1265 | ||
1266 | #define DFS_PUSH(dfsid) \ | |
1267 | { \ | |
1268 | dfs.idptr[dfs.depth].inode_id = dfsid.inode_id; \ | |
1269 | dfs.idptr[dfs.depth].catalog_id = dfsid.catalog_id; \ | |
1270 | dfs.depth++; \ | |
1271 | if (dfs.depth == max_alloc_depth) { \ | |
1272 | void *tptr = realloc(dfs.idptr, (max_alloc_depth + DIRLINK_DEFAULT_DFS_MAX_DEPTH) * sizeof(struct dfs_id)); \ | |
1273 | if (tptr == NULL) { \ | |
1274 | break; \ | |
1275 | } else { \ | |
1276 | dfs.idptr = tptr; \ | |
1277 | max_alloc_depth += DIRLINK_DEFAULT_DFS_MAX_DEPTH; \ | |
1278 | } \ | |
1279 | } \ | |
1280 | } | |
1281 | ||
1282 | #define DFS_POP(dfsid) \ | |
1283 | { \ | |
1284 | dfs.depth--; \ | |
1285 | dfsid.inode_id = dfs.idptr[dfs.depth].inode_id; \ | |
1286 | dfsid.catalog_id = dfs.idptr[dfs.depth].catalog_id; \ | |
1287 | } | |
1288 | ||
1289 | #define DFS_PEEK(dfsid) \ | |
1290 | { \ | |
1291 | dfsid.inode_id = dfs.idptr[dfs.depth-1].inode_id; \ | |
1292 | dfsid.catalog_id = dfs.idptr[dfs.depth-1].catalog_id; \ | |
1293 | } | |
1294 | ||
1295 | /* Initialize the traversal stack */ | |
1296 | dfs.idptr = malloc(max_alloc_depth * sizeof(struct dfs_id)); | |
1297 | if (!dfs.idptr) { | |
1298 | return ENOMEM; | |
1299 | } | |
1300 | dfs.depth = 0; | |
1301 | ||
1302 | /* Initialize unknown child IDs which are used when a directory is | |
1303 | * seen for the first time. | |
1304 | */ | |
1305 | unknown_child.inode_id = unknown_child.catalog_id = 0; | |
1306 | ||
1307 | /* Allocate visited list for total number of directory inodes seen */ | |
1308 | if (gptr->calculated_dirinodes) { | |
1309 | visited.size = gptr->calculated_dirinodes; | |
1310 | } else { | |
1311 | visited.size = 1024; | |
1312 | } | |
1313 | ||
1314 | /* If visited list allocation failed, perform search without cache */ | |
1315 | visited.list = malloc(visited.size * sizeof(uint32_t)); | |
1316 | if (visited.list == NULL) { | |
1317 | visited.size = 0; | |
1318 | if (fsckGetVerbosity(gptr->context) >= kDebugLog) { | |
1319 | plog ("\tcheck_loops: Allocation failed for visited list\n"); | |
1320 | } | |
1321 | } | |
1322 | visited.offset = 0; | |
1323 | visited.wrapped = false; | |
1324 | ||
1325 | /* Set the starting directory for traversal */ | |
1326 | if (gptr->dirlink_priv_dir_id) { | |
1327 | parent.inode_id = parent.catalog_id = gptr->dirlink_priv_dir_id; | |
1328 | } else { | |
1329 | parent.inode_id = parent.catalog_id = kHFSRootFolderID; | |
1330 | } | |
1331 | ||
1332 | /* Initialize the first parent and its first unknown child */ | |
1333 | do { | |
1334 | DFS_PUSH(parent); | |
1335 | DFS_PUSH(unknown_child); | |
1336 | } while (0); | |
1337 | ||
1338 | while (dfs.depth > 1) { | |
1339 | DFS_POP(child); | |
1340 | DFS_PEEK(parent); | |
1341 | retval = find_next_child_dir(gptr, parent.inode_id, | |
1342 | child.catalog_id, &(child.inode_id), | |
1343 | &(child.catalog_id), &is_dirinode); | |
1344 | if (retval) { | |
1345 | break; | |
1346 | } | |
1347 | ||
1348 | if (child.inode_id) { | |
1349 | retval = check_loops(&dfs, child); | |
1350 | if (retval) { | |
1351 | fsckPrint(gptr->context, E_DirLoop); | |
1352 | if (fsckGetVerbosity(gptr->context) >= kDebugLog) { | |
1353 | plog ("\tDetected when adding (%u,%u) to following traversal stack -\n", child.inode_id, child.catalog_id); | |
1354 | print_dfs(&dfs); | |
1355 | } | |
1356 | gptr->CatStat |= S_LinkErrNoRepair; | |
1357 | retval = E_DirLoop; | |
1358 | break; | |
1359 | } | |
1360 | ||
1361 | /* Push the current child on traversal stack */ | |
1362 | DFS_PUSH(child); | |
1363 | ||
1364 | /* Traverse down directory inode only if it was not | |
1365 | * visited previously and mark it visited. | |
1366 | */ | |
1367 | if (is_dirinode == true) { | |
1368 | if (is_dirinode_visited(child.inode_id, &visited)) { | |
1369 | continue; | |
1370 | } else { | |
1371 | mark_dirinode_visited(child.inode_id, &visited); | |
1372 | } | |
1373 | } | |
1374 | ||
1375 | /* Push unknown child to traverse down the child directory */ | |
1376 | DFS_PUSH(unknown_child); | |
1377 | } | |
1378 | } | |
1379 | ||
1380 | if (dfs.depth >= max_alloc_depth) { | |
1381 | fsckPrint(gptr->context, E_DirHardLinkNesting); | |
1382 | if (fsckGetVerbosity(gptr->context) >= kDebugLog) { | |
1383 | print_dfs(&dfs); | |
1384 | } | |
1385 | gptr->CatStat |= S_LinkErrNoRepair; | |
1386 | retval = E_DirHardLinkNesting; | |
1387 | } | |
1388 | ||
1389 | if (dfs.idptr) { | |
1390 | free(dfs.idptr); | |
1391 | } | |
1392 | if (visited.list) { | |
1393 | free(visited.list); | |
1394 | } | |
1395 | return retval; | |
1396 | } | |
1397 | ||
1398 | /* This function traverses the entire catalog btree, and checks all | |
1399 | * directory inodes and directory hard links found. | |
1400 | * | |
1401 | * Returns zero if the check is successful, and non-zero if an error was | |
1402 | * encountered during verification. | |
1403 | */ | |
1404 | int dirhardlink_check(SGlobPtr gptr) | |
1405 | { | |
1406 | int retval = 0; | |
1407 | uint16_t selcode; | |
1408 | uint32_t hint; | |
1409 | ||
1410 | CatalogRecord catrec; | |
1411 | CatalogKey catkey; | |
1412 | uint16_t recsize; | |
1413 | ||
1414 | PrimeBuckets *inode_view = NULL; | |
1415 | PrimeBuckets *dirlink_view = NULL; | |
1416 | ||
1417 | /* Check if the volume is HFS+ */ | |
1418 | if (VolumeObjectIsHFSPlus() == false) { | |
1419 | goto out; | |
1420 | } | |
1421 | ||
1422 | /* Shortcut out if no directory hard links exists on the disk */ | |
1423 | if ((gptr->dirlink_priv_dir_valence == 0) && | |
1424 | (gptr->calculated_dirlinks == 0) && | |
1425 | (gptr->calculated_dirinodes == 0)) { | |
1426 | goto out; | |
1427 | } | |
1428 | ||
1429 | fsckPrint(gptr->context, hfsMultiLinkDirCheck); | |
1430 | ||
1431 | if (fsckGetVerbosity(gptr->context) >= kDebugLog) { | |
1432 | plog ("\tprivdir_valence=%u, calc_dirlinks=%u, calc_dirinode=%u\n", gptr->dirlink_priv_dir_valence, gptr->calculated_dirlinks, gptr->calculated_dirinodes); | |
1433 | } | |
1434 | ||
1435 | /* If lookup of private directory failed and the volume has | |
1436 | * some directory hard links and directory inodes, we will need | |
1437 | * to create the private directory for directory hard links. | |
1438 | */ | |
1439 | if (gptr->dirlink_priv_dir_id == 0) { | |
1440 | fsckPrint(gptr->context, E_MissingPrivDir); | |
1441 | gptr->CatStat |= S_LinkErrNoRepair; | |
1442 | } | |
1443 | ||
1444 | /* Initialize the two prime number buckets, both buckets keep track | |
1445 | * of inode ID and corresponding directory hard link ID. The first | |
1446 | * bucket is filled when traversing the directory hard link doubly | |
1447 | * linked list from the directory inode, and the second bucket is | |
1448 | * filled when btree traversal encounters directory hard links. | |
1449 | * This method quickly allows us to check if the mapping of all | |
1450 | * inodes and directory hard links is same, and no orphans exists. | |
1451 | */ | |
1452 | inode_view = (PrimeBuckets *)calloc(1, sizeof(PrimeBuckets)); | |
1453 | if (!inode_view) { | |
1454 | retval = ENOMEM; | |
1455 | goto out; | |
1456 | } | |
1457 | ||
1458 | dirlink_view = (PrimeBuckets *)calloc(1, sizeof(PrimeBuckets)); | |
1459 | if (!dirlink_view) { | |
1460 | retval = ENOMEM; | |
1461 | goto out; | |
1462 | } | |
1463 | ||
1464 | /* Traverse the catalog btree from the first record */ | |
1465 | selcode = 0x8001; | |
1466 | retval = GetBTreeRecord(gptr->calculatedCatalogFCB, selcode, &catkey, | |
1467 | &catrec, &recsize, &hint); | |
1468 | if (retval != 0) { | |
1469 | goto out; | |
1470 | } | |
1471 | ||
1472 | /* Set code to get the next record */ | |
1473 | selcode = 1; | |
1474 | do { | |
1475 | if (catrec.hfsPlusFolder.recordType == kHFSPlusFolderRecord) { | |
1476 | /* Check directory hard link private metadata directory */ | |
1477 | if (catrec.hfsPlusFolder.folderID == gptr->dirlink_priv_dir_id) { | |
1478 | dirlink_priv_dir_check(gptr, | |
1479 | &(catrec.hfsPlusFolder), &(catkey.hfsPlus)); | |
1480 | } | |
1481 | ||
1482 | /* Check directory inode */ | |
1483 | if ((catrec.hfsPlusFolder.flags & kHFSHasLinkChainMask) || | |
1484 | (catkey.hfsPlus.parentID == gptr->dirlink_priv_dir_id)) { | |
1485 | retval = inode_check(gptr, inode_view, | |
1486 | &catrec, | |
1487 | &catkey, | |
1488 | true); | |
1489 | if (retval) { | |
1490 | /* If the corruption detected requires | |
1491 | * knowledge of all associated directory | |
1492 | * hard links for repair, stop the | |
1493 | * catalog btree traversal | |
1494 | */ | |
1495 | retval = 0; | |
1496 | break; | |
1497 | } | |
1498 | } | |
1499 | } else | |
1500 | if (catrec.recordType == kHFSPlusFileRecord) { | |
1501 | /* Check if the hard link bit is set with correct | |
1502 | * alias type/creator. If the parent is private | |
1503 | * metadata directory for file hard links, this | |
1504 | * is a hard link inode for an alias, and not | |
1505 | * directory hard link. Skip this file from our | |
1506 | * check. | |
1507 | */ | |
1508 | if ((catrec.hfsPlusFile.flags & kHFSHasLinkChainMask) && | |
1509 | (catrec.hfsPlusFile.userInfo.fdType == kHFSAliasType) && | |
1510 | (catrec.hfsPlusFile.userInfo.fdCreator == kHFSAliasCreator) && | |
1511 | (catkey.hfsPlus.parentID != gptr->filelink_priv_dir_id)) { | |
1512 | dirlink_check(gptr, dirlink_view, | |
1513 | &(catrec.hfsPlusFile), &(catkey.hfsPlus), true); | |
1514 | } | |
1515 | } | |
1516 | ||
1517 | retval = GetBTreeRecord(gptr->calculatedCatalogFCB, 1, | |
1518 | &catkey, &catrec, &recsize, &hint); | |
1519 | } while (retval == noErr); | |
1520 | ||
1521 | if (retval == btNotFound) { | |
1522 | retval = 0; | |
1523 | } else if (retval != 0) { | |
1524 | goto out; | |
1525 | } | |
1526 | ||
1527 | /* Compare the two prime number buckets only the if catalog traversal did | |
1528 | * not detect incorrect number of directory hard links corruption. | |
1529 | */ | |
1530 | if ((gptr->CatStat & S_DirHardLinkChain) == 0) { | |
1531 | retval = compare_prime_buckets(inode_view, dirlink_view); | |
1532 | if (retval) { | |
1533 | record_link_badchain(gptr, true); | |
1534 | if (fsckGetVerbosity(gptr->context) >= kDebugLog) { | |
1535 | plog ("\tdirlink prime buckets do not match\n"); | |
1536 | } | |
1537 | retval = 0; | |
1538 | } | |
1539 | } | |
1540 | ||
1541 | /* Check if there are any loops in the directory hierarchy */ | |
1542 | retval = check_hierarchy_loops(gptr); | |
1543 | if (retval) { | |
1544 | retval = 0; | |
1545 | goto out; | |
1546 | } | |
1547 | ||
1548 | out: | |
1549 | if (inode_view) { | |
1550 | free (inode_view); | |
1551 | } | |
1552 | if (dirlink_view) { | |
1553 | free (dirlink_view); | |
1554 | } | |
1555 | ||
1556 | return retval; | |
1557 | } | |
1558 |