]>
Commit | Line | Data |
---|---|---|
1 | /* Copyright © 2017-2018 Apple Inc. All rights reserved. | |
2 | * | |
3 | * lf_hfs_xattr.c | |
4 | * livefiles_hfs | |
5 | * | |
6 | * Created by Or Haimovich on 28/3/18. | |
7 | */ | |
8 | ||
9 | #include <sys/xattr.h> | |
10 | #include <sys/acl.h> | |
11 | #include <sys/kauth.h> | |
12 | #include "lf_hfs_xattr.h" | |
13 | #include "lf_hfs.h" | |
14 | #include "lf_hfs_vnops.h" | |
15 | #include "lf_hfs_raw_read_write.h" | |
16 | #include "lf_hfs_btrees_io.h" | |
17 | #include "lf_hfs_btrees_internal.h" | |
18 | #include "lf_hfs_vfsutils.h" | |
19 | #include "lf_hfs_sbunicode.h" | |
20 | #include "lf_hfs_endian.h" | |
21 | #include "lf_hfs_logger.h" | |
22 | #include "lf_hfs_utils.h" | |
23 | ||
24 | #define ATTRIBUTE_FILE_NODE_SIZE 8192 | |
25 | ||
26 | //#define HFS_XATTR_VERBOSE 1 | |
27 | ||
28 | /* State information for the listattr_callback callback function. */ | |
29 | struct listattr_callback_state { | |
30 | u_int32_t fileID; | |
31 | int result; | |
32 | void *buf; | |
33 | size_t bufsize; | |
34 | size_t size; | |
35 | }; | |
36 | ||
37 | static u_int32_t emptyfinfo[8] = {0}; | |
38 | ||
39 | ||
40 | static int hfs_zero_hidden_fields (struct cnode *cp, u_int8_t *finderinfo); | |
41 | ||
42 | const char hfs_attrdatafilename[] = "Attribute Data"; | |
43 | ||
44 | static int listattr_callback(const HFSPlusAttrKey *key, const HFSPlusAttrData *data, | |
45 | struct listattr_callback_state *state); | |
46 | ||
47 | static int remove_attribute_records(struct hfsmount *hfsmp, BTreeIterator * iterator); | |
48 | ||
49 | static int getnodecount(struct hfsmount *hfsmp, size_t nodesize); | |
50 | ||
51 | static size_t getmaxinlineattrsize(struct vnode * attrvp); | |
52 | ||
53 | static int read_attr_data(struct hfsmount *hfsmp, void * buf, size_t datasize, HFSPlusExtentDescriptor *extents); | |
54 | ||
55 | static int write_attr_data(struct hfsmount *hfsmp, void * buf, size_t datasize, HFSPlusExtentDescriptor *extents); | |
56 | ||
57 | static int alloc_attr_blks(struct hfsmount *hfsmp, size_t attrsize, size_t extentbufsize, HFSPlusExtentDescriptor *extents, int *blocks); | |
58 | ||
59 | static void free_attr_blks(struct hfsmount *hfsmp, int blkcnt, HFSPlusExtentDescriptor *extents); | |
60 | ||
61 | static int has_overflow_extents(HFSPlusForkData *forkdata); | |
62 | ||
63 | static int count_extent_blocks(int maxblks, HFSPlusExtentRecord extents); | |
64 | ||
65 | ||
66 | /* Zero out the date added field for the specified cnode */ | |
67 | static int hfs_zero_hidden_fields (struct cnode *cp, u_int8_t *finderinfo) | |
68 | { | |
69 | u_int8_t *finfo = finderinfo; | |
70 | ||
71 | /* Advance finfo by 16 bytes to the 2nd half of the finderinfo */ | |
72 | finfo = finfo + 16; | |
73 | ||
74 | if (S_ISREG(cp->c_attr.ca_mode) || S_ISLNK(cp->c_attr.ca_mode)) { | |
75 | struct FndrExtendedFileInfo *extinfo = (struct FndrExtendedFileInfo *)finfo; | |
76 | extinfo->document_id = 0; | |
77 | extinfo->date_added = 0; | |
78 | extinfo->write_gen_counter = 0; | |
79 | } else if (S_ISDIR(cp->c_attr.ca_mode)) { | |
80 | struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)finfo; | |
81 | extinfo->document_id = 0; | |
82 | extinfo->date_added = 0; | |
83 | extinfo->write_gen_counter = 0; | |
84 | } else { | |
85 | /* Return an error */ | |
86 | return -1; | |
87 | } | |
88 | return 0; | |
89 | } | |
90 | ||
91 | /* | |
92 | * Retrieve the data of an extended attribute. | |
93 | */ | |
94 | int | |
95 | hfs_vnop_getxattr(vnode_t vp, const char *attr_name, void *buf, size_t bufsize, size_t *actual_size) | |
96 | { | |
97 | struct cnode *cp; | |
98 | struct hfsmount *hfsmp; | |
99 | int result; | |
100 | ||
101 | if (attr_name == NULL || attr_name[0] == '\0') { | |
102 | return (EINVAL); /* invalid name */ | |
103 | } | |
104 | if (strlen(attr_name) > XATTR_MAXNAMELEN) { | |
105 | return (ENAMETOOLONG); | |
106 | } | |
107 | if (actual_size == NULL) { | |
108 | return (EINVAL); | |
109 | } | |
110 | if (VNODE_IS_RSRC(vp)) { | |
111 | return (EPERM); | |
112 | } | |
113 | ||
114 | cp = VTOC(vp); | |
115 | ||
116 | /* Get the Finder Info. */ | |
117 | if (strcmp(attr_name, XATTR_FINDERINFO_NAME) == 0) { | |
118 | u_int8_t finderinfo[32]; | |
119 | size_t attrsize = 32; | |
120 | ||
121 | if ((result = hfs_lock(cp, HFS_SHARED_LOCK, HFS_LOCK_DEFAULT))) { | |
122 | return (result); | |
123 | } | |
124 | /* Make a copy since we may not export all of it. */ | |
125 | bcopy(cp->c_finderinfo, finderinfo, sizeof(finderinfo)); | |
126 | hfs_unlock(cp); | |
127 | ||
128 | /* Zero out the date added field in the local copy */ | |
129 | hfs_zero_hidden_fields (cp, finderinfo); | |
130 | ||
131 | /* Don't expose a symlink's private type/creator. */ | |
132 | if (vnode_islnk(vp)) { | |
133 | struct FndrFileInfo *fip; | |
134 | ||
135 | fip = (struct FndrFileInfo *)&finderinfo; | |
136 | fip->fdType = 0; | |
137 | fip->fdCreator = 0; | |
138 | } | |
139 | /* If Finder Info is empty then it doesn't exist. */ | |
140 | if (bcmp(finderinfo, emptyfinfo, sizeof(emptyfinfo)) == 0) { | |
141 | return (ENOATTR); | |
142 | } | |
143 | *actual_size = attrsize; | |
144 | ||
145 | if (buf == NULL) { | |
146 | return (0); | |
147 | } | |
148 | if (bufsize < attrsize) | |
149 | return (ERANGE); | |
150 | ||
151 | memcpy(buf, (caddr_t)&finderinfo, attrsize); | |
152 | return (0); | |
153 | } | |
154 | ||
155 | /* Read the Resource Fork. */ | |
156 | if (strcmp(attr_name, XATTR_RESOURCEFORK_NAME) == 0) { | |
157 | return (ENOATTR); | |
158 | } | |
159 | ||
160 | hfsmp = VTOHFS(vp); | |
161 | if ((result = hfs_lock(cp, HFS_SHARED_LOCK, HFS_LOCK_DEFAULT))) { | |
162 | return (result); | |
163 | } | |
164 | ||
165 | /* Check for non-rsrc, non-finderinfo EAs - getxattr_internal */ | |
166 | ||
167 | struct filefork *btfile; | |
168 | BTreeIterator * iterator = NULL; | |
169 | size_t attrsize = 0; | |
170 | HFSPlusAttrRecord *recp = NULL; | |
171 | FSBufferDescriptor btdata; | |
172 | int lockflags = 0; | |
173 | u_int16_t datasize = 0; | |
174 | u_int32_t target_id = 0; | |
175 | ||
176 | if (cp) { | |
177 | target_id = cp->c_fileid; | |
178 | } else { | |
179 | target_id = kHFSRootParentID; | |
180 | } | |
181 | ||
182 | /* Bail if we don't have an EA B-Tree. */ | |
183 | if ((hfsmp->hfs_attribute_vp == NULL) || | |
184 | ((cp) && (cp->c_attr.ca_recflags & kHFSHasAttributesMask) == 0)) { | |
185 | result = ENOATTR; | |
186 | goto exit; | |
187 | } | |
188 | ||
189 | /* Initialize the B-Tree iterator for searching for the proper EA */ | |
190 | btfile = VTOF(hfsmp->hfs_attribute_vp); | |
191 | ||
192 | iterator = hfs_mallocz(sizeof(*iterator)); | |
193 | ||
194 | /* Allocate memory for reading in the attribute record. This buffer is | |
195 | * big enough to read in all types of attribute records. It is not big | |
196 | * enough to read inline attribute data which is read in later. | |
197 | */ | |
198 | recp = hfs_malloc(sizeof(HFSPlusAttrRecord)); | |
199 | btdata.bufferAddress = recp; | |
200 | btdata.itemSize = sizeof(HFSPlusAttrRecord); | |
201 | btdata.itemCount = 1; | |
202 | ||
203 | result = hfs_buildattrkey(target_id, attr_name, (HFSPlusAttrKey *)&iterator->key); | |
204 | if (result) { | |
205 | goto exit; | |
206 | } | |
207 | ||
208 | /* Lookup the attribute in the Attribute B-Tree */ | |
209 | lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK); | |
210 | result = BTSearchRecord(btfile, iterator, &btdata, &datasize, NULL); | |
211 | hfs_systemfile_unlock(hfsmp, lockflags); | |
212 | ||
213 | if (result) { | |
214 | if (result == btNotFound) { | |
215 | result = ENOATTR; | |
216 | } | |
217 | goto exit; | |
218 | } | |
219 | ||
220 | /* | |
221 | * Operate differently if we have inline EAs that can fit in the attribute B-Tree or if | |
222 | * we have extent based EAs. | |
223 | */ | |
224 | switch (recp->recordType) { | |
225 | ||
226 | /* Attribute fits in the Attribute B-Tree */ | |
227 | case kHFSPlusAttrInlineData: { | |
228 | /* | |
229 | * Sanity check record size. It's not required to have any | |
230 | * user data, so the minimum size is 2 bytes less that the | |
231 | * size of HFSPlusAttrData (since HFSPlusAttrData struct | |
232 | * has 2 bytes set aside for attribute data). | |
233 | */ | |
234 | if (datasize < (sizeof(HFSPlusAttrData) - 2)) { | |
235 | LFHFS_LOG(LEVEL_DEBUG, "hfs_getxattr: vol=%s %d,%s invalid record size %d (expecting %lu)\n", | |
236 | hfsmp->vcbVN, target_id, attr_name, datasize, sizeof(HFSPlusAttrData)); | |
237 | result = ENOATTR; | |
238 | break; | |
239 | } | |
240 | *actual_size = recp->attrData.attrSize; | |
241 | if (buf && recp->attrData.attrSize != 0) { | |
242 | if (*actual_size > bufsize) { | |
243 | /* User provided buffer is not large enough for the xattr data */ | |
244 | result = ERANGE; | |
245 | } else { | |
246 | /* Previous BTreeSearchRecord() read in only the attribute record, | |
247 | * and not the attribute data. Now allocate enough memory for | |
248 | * both attribute record and data, and read the attribute record again. | |
249 | */ | |
250 | attrsize = sizeof(HFSPlusAttrData) - 2 + recp->attrData.attrSize; | |
251 | hfs_free(recp); | |
252 | recp = hfs_malloc(attrsize); | |
253 | ||
254 | btdata.bufferAddress = recp; | |
255 | btdata.itemSize = attrsize; | |
256 | btdata.itemCount = 1; | |
257 | ||
258 | bzero(iterator, sizeof(*iterator)); | |
259 | result = hfs_buildattrkey(target_id, attr_name, (HFSPlusAttrKey *)&iterator->key); | |
260 | if (result) { | |
261 | goto exit; | |
262 | } | |
263 | ||
264 | /* Lookup the attribute record and inline data */ | |
265 | lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK); | |
266 | result = BTSearchRecord(btfile, iterator, &btdata, &datasize, NULL); | |
267 | hfs_systemfile_unlock(hfsmp, lockflags); | |
268 | if (result) { | |
269 | if (result == btNotFound) { | |
270 | result = ENOATTR; | |
271 | } | |
272 | goto exit; | |
273 | } | |
274 | ||
275 | /* Copy-out the attribute data to the user buffer */ | |
276 | *actual_size = recp->attrData.attrSize; | |
277 | memcpy(buf, (caddr_t) &recp->attrData.attrData, recp->attrData.attrSize); | |
278 | } | |
279 | } | |
280 | break; | |
281 | } | |
282 | ||
283 | /* Extent-Based EAs */ | |
284 | case kHFSPlusAttrForkData: { | |
285 | if (datasize < sizeof(HFSPlusAttrForkData)) { | |
286 | LFHFS_LOG(LEVEL_DEBUG, "hfs_getxattr: vol=%s %d,%s invalid record size %d (expecting %lu)\n", | |
287 | hfsmp->vcbVN, target_id, attr_name, datasize, sizeof(HFSPlusAttrForkData)); | |
288 | result = ENOATTR; | |
289 | break; | |
290 | } | |
291 | *actual_size = recp->forkData.theFork.logicalSize; | |
292 | if (buf == NULL) { | |
293 | break; | |
294 | } | |
295 | if (*actual_size > bufsize) { | |
296 | result = ERANGE; | |
297 | break; | |
298 | } | |
299 | /* Process overflow extents if necessary. */ | |
300 | if (has_overflow_extents(&recp->forkData.theFork)) { | |
301 | HFSPlusExtentDescriptor *extentbuf; | |
302 | HFSPlusExtentDescriptor *extentptr; | |
303 | size_t extentbufsize; | |
304 | u_int32_t totalblocks; | |
305 | u_int32_t blkcnt; | |
306 | u_int64_t attrlen; | |
307 | ||
308 | totalblocks = recp->forkData.theFork.totalBlocks; | |
309 | /* Ignore bogus block counts. */ | |
310 | if (totalblocks > howmany(HFS_XATTR_MAXSIZE, hfsmp->blockSize)) { | |
311 | result = ERANGE; | |
312 | break; | |
313 | } | |
314 | attrlen = recp->forkData.theFork.logicalSize; | |
315 | ||
316 | /* Get a buffer to hold the worst case amount of extents. */ | |
317 | extentbufsize = totalblocks * sizeof(HFSPlusExtentDescriptor); | |
318 | extentbufsize = roundup(extentbufsize, sizeof(HFSPlusExtentRecord)); | |
319 | extentbuf = hfs_mallocz(extentbufsize); | |
320 | extentptr = extentbuf; | |
321 | ||
322 | /* Grab the first 8 extents. */ | |
323 | bcopy(&recp->forkData.theFork.extents[0], extentptr, sizeof(HFSPlusExtentRecord)); | |
324 | extentptr += kHFSPlusExtentDensity; | |
325 | blkcnt = count_extent_blocks(totalblocks, recp->forkData.theFork.extents); | |
326 | ||
327 | /* Now lookup the overflow extents. */ | |
328 | lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK); | |
329 | while (blkcnt < totalblocks) { | |
330 | ((HFSPlusAttrKey *)&iterator->key)->startBlock = blkcnt; | |
331 | result = BTSearchRecord(btfile, iterator, &btdata, &datasize, NULL); | |
332 | if (result || | |
333 | (recp->recordType != kHFSPlusAttrExtents) || | |
334 | (datasize < sizeof(HFSPlusAttrExtents))) { | |
335 | LFHFS_LOG(LEVEL_DEBUG, "hfs_getxattr: %s missing extents, only %d blks of %d found\n", | |
336 | attr_name, blkcnt, totalblocks); | |
337 | break; /* break from while */ | |
338 | } | |
339 | /* Grab the next 8 extents. */ | |
340 | bcopy(&recp->overflowExtents.extents[0], extentptr, sizeof(HFSPlusExtentRecord)); | |
341 | extentptr += kHFSPlusExtentDensity; | |
342 | blkcnt += count_extent_blocks(totalblocks, recp->overflowExtents.extents); | |
343 | } | |
344 | ||
345 | /* Release Attr B-Tree lock */ | |
346 | hfs_systemfile_unlock(hfsmp, lockflags); | |
347 | ||
348 | if (blkcnt < totalblocks) { | |
349 | result = ENOATTR; | |
350 | } else { | |
351 | result = read_attr_data(hfsmp, buf, attrlen, extentbuf); | |
352 | } | |
353 | hfs_free(extentbuf); | |
354 | ||
355 | } else { /* No overflow extents. */ | |
356 | result = read_attr_data(hfsmp, buf, recp->forkData.theFork.logicalSize, recp->forkData.theFork.extents); | |
357 | } | |
358 | break; | |
359 | } | |
360 | ||
361 | default: | |
362 | /* We only support inline EAs. Default to ENOATTR for anything else */ | |
363 | result = ENOATTR; | |
364 | break; | |
365 | } | |
366 | ||
367 | exit: | |
368 | hfs_free(iterator); | |
369 | hfs_free(recp); | |
370 | hfs_unlock(cp); | |
371 | ||
372 | return MacToVFSError(result); | |
373 | } | |
374 | ||
375 | /* | |
376 | * Set the data of an extended attribute. | |
377 | */ | |
378 | int | |
379 | hfs_vnop_setxattr(vnode_t vp, const char *attr_name, const void *buf, size_t bufsize, UVFSXattrHow option) | |
380 | { | |
381 | struct cnode *cp = NULL; | |
382 | struct hfsmount *hfsmp; | |
383 | size_t attrsize; | |
384 | int result; | |
385 | ||
386 | if (attr_name == NULL || attr_name[0] == '\0') { | |
387 | return (EINVAL); /* invalid name */ | |
388 | } | |
389 | if (strlen(attr_name) > XATTR_MAXNAMELEN) { | |
390 | return (ENAMETOOLONG); | |
391 | } | |
392 | if (buf == NULL) { | |
393 | return (EINVAL); | |
394 | } | |
395 | if (VNODE_IS_RSRC(vp)) { | |
396 | return (EPERM); | |
397 | } | |
398 | ||
399 | hfsmp = VTOHFS(vp); | |
400 | ||
401 | /* Set the Finder Info. */ | |
402 | if (strcmp(attr_name, XATTR_FINDERINFO_NAME) == 0) { | |
403 | union { | |
404 | uint8_t data[32]; | |
405 | char cdata[32]; | |
406 | struct FndrFileInfo info; | |
407 | } fi; | |
408 | void * finderinfo_start; | |
409 | u_int8_t *finfo = NULL; | |
410 | u_int16_t fdFlags; | |
411 | u_int32_t dateadded = 0; | |
412 | u_int32_t write_gen_counter = 0; | |
413 | u_int32_t document_id = 0; | |
414 | ||
415 | attrsize = sizeof(VTOC(vp)->c_finderinfo); | |
416 | ||
417 | if (bufsize != attrsize) { | |
418 | return (ERANGE); | |
419 | } | |
420 | /* Grab the new Finder Info data. */ | |
421 | memcpy(fi.cdata, buf, attrsize); | |
422 | ||
423 | if ((result = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) { | |
424 | return (result); | |
425 | } | |
426 | cp = VTOC(vp); | |
427 | ||
428 | /* Symlink's don't have an external type/creator. */ | |
429 | if (vnode_islnk(vp)) { | |
430 | /* Skip over type/creator fields. */ | |
431 | finderinfo_start = &cp->c_finderinfo[8]; | |
432 | attrsize -= 8; | |
433 | } else { | |
434 | finderinfo_start = &cp->c_finderinfo[0]; | |
435 | /* | |
436 | * Don't allow the external setting of | |
437 | * file type to kHardLinkFileType. | |
438 | */ | |
439 | if (fi.info.fdType == SWAP_BE32(kHardLinkFileType)) { | |
440 | hfs_unlock(cp); | |
441 | return (EPERM); | |
442 | } | |
443 | } | |
444 | ||
445 | /* Grab the current date added from the cnode */ | |
446 | dateadded = hfs_get_dateadded (cp); | |
447 | if (S_ISREG(cp->c_attr.ca_mode) || S_ISLNK(cp->c_attr.ca_mode)) { | |
448 | struct FndrExtendedFileInfo *extinfo = (struct FndrExtendedFileInfo *)((u_int8_t*)cp->c_finderinfo + 16); | |
449 | /* | |
450 | * Grab generation counter directly from the cnode | |
451 | * instead of calling hfs_get_gencount(), because | |
452 | * for zero generation count values hfs_get_gencount() | |
453 | * lies and bumps it up to one. | |
454 | */ | |
455 | write_gen_counter = extinfo->write_gen_counter; | |
456 | document_id = extinfo->document_id; | |
457 | } else if (S_ISDIR(cp->c_attr.ca_mode)) { | |
458 | struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)((u_int8_t*)cp->c_finderinfo + 16); | |
459 | write_gen_counter = extinfo->write_gen_counter; | |
460 | document_id = extinfo->document_id; | |
461 | } | |
462 | ||
463 | /* | |
464 | * Zero out the finder info's reserved fields like date added, | |
465 | * generation counter, and document id to ignore user's attempts | |
466 | * to set it | |
467 | */ | |
468 | hfs_zero_hidden_fields(cp, fi.data); | |
469 | ||
470 | if (bcmp(finderinfo_start, emptyfinfo, attrsize)) { | |
471 | /* attr exists and "create" was specified. */ | |
472 | if (option == UVFSXattrHowCreate) { | |
473 | hfs_unlock(cp); | |
474 | return (EEXIST); | |
475 | } | |
476 | } else { /* empty */ | |
477 | /* attr doesn't exists and "replace" was specified. */ | |
478 | if (option == UVFSXattrHowReplace) { | |
479 | hfs_unlock(cp); | |
480 | return (ENOATTR); | |
481 | } | |
482 | } | |
483 | ||
484 | /* | |
485 | * Now restore the date added and other reserved fields to the finderinfo to | |
486 | * be written out. Advance to the 2nd half of the finderinfo to write them | |
487 | * out into the buffer. | |
488 | * | |
489 | * Make sure to endian swap the date added back into big endian. When we used | |
490 | * hfs_get_dateadded above to retrieve it, it swapped into local endianness | |
491 | * for us. But now that we're writing it out, put it back into big endian. | |
492 | */ | |
493 | finfo = &fi.data[16]; | |
494 | if (S_ISREG(cp->c_attr.ca_mode) || S_ISLNK(cp->c_attr.ca_mode)) { | |
495 | struct FndrExtendedFileInfo *extinfo = (struct FndrExtendedFileInfo *)finfo; | |
496 | extinfo->date_added = OSSwapHostToBigInt32(dateadded); | |
497 | extinfo->write_gen_counter = write_gen_counter; | |
498 | extinfo->document_id = document_id; | |
499 | } else if (S_ISDIR(cp->c_attr.ca_mode)) { | |
500 | struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)finfo; | |
501 | extinfo->date_added = OSSwapHostToBigInt32(dateadded); | |
502 | extinfo->write_gen_counter = write_gen_counter; | |
503 | extinfo->document_id = document_id; | |
504 | } | |
505 | ||
506 | /* Set the cnode's Finder Info. */ | |
507 | if (attrsize == sizeof(cp->c_finderinfo)) { | |
508 | bcopy(&fi.data[0], finderinfo_start, attrsize); | |
509 | } else { | |
510 | bcopy(&fi.data[8], finderinfo_start, attrsize); | |
511 | } | |
512 | ||
513 | /* Updating finderInfo updates change time and modified time */ | |
514 | cp->c_touch_chgtime = TRUE; | |
515 | cp->c_flag |= C_MODIFIED; | |
516 | ||
517 | /* | |
518 | * Mirror the invisible bit to the UF_HIDDEN flag. | |
519 | * | |
520 | * The fdFlags for files and frFlags for folders are both 8 bytes | |
521 | * into the userInfo (the first 16 bytes of the Finder Info). They | |
522 | * are both 16-bit fields. | |
523 | */ | |
524 | fdFlags = *((u_int16_t *) &cp->c_finderinfo[8]); | |
525 | if (fdFlags & OSSwapHostToBigConstInt16(kFinderInvisibleMask)) { | |
526 | cp->c_bsdflags |= UF_HIDDEN; | |
527 | } else { | |
528 | cp->c_bsdflags &= ~UF_HIDDEN; | |
529 | } | |
530 | ||
531 | result = hfs_update(vp, 0); | |
532 | ||
533 | hfs_unlock(cp); | |
534 | return (result); | |
535 | } | |
536 | ||
537 | /* Write the Resource Fork. */ | |
538 | if (strcmp(attr_name, XATTR_RESOURCEFORK_NAME) == 0) { | |
539 | return (ENOTSUP); | |
540 | } | |
541 | ||
542 | attrsize = bufsize; | |
543 | ||
544 | result = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); | |
545 | if (result) { | |
546 | goto exit; | |
547 | } | |
548 | cp = VTOC(vp); | |
549 | ||
550 | /* | |
551 | * If we're trying to set a non-finderinfo, non-resourcefork EA, then | |
552 | * call the breakout function - hfs_setxattr_internal. | |
553 | */ | |
554 | int started_transaction = 0; | |
555 | BTreeIterator * iterator = NULL; | |
556 | struct filefork *btfile = NULL; | |
557 | FSBufferDescriptor btdata; | |
558 | HFSPlusAttrRecord attrdata; /* 90 bytes */ | |
559 | HFSPlusAttrRecord *recp = NULL; | |
560 | HFSPlusExtentDescriptor *extentptr = NULL; | |
561 | size_t extentbufsize = 0; | |
562 | int lockflags = 0; | |
563 | int exists = 0; | |
564 | int allocatedblks = 0; | |
565 | u_int32_t target_id; | |
566 | ||
567 | if (cp) { | |
568 | target_id = cp->c_fileid; | |
569 | } else { | |
570 | target_id = kHFSRootParentID; | |
571 | } | |
572 | ||
573 | /* Start a transaction for our changes. */ | |
574 | if (hfs_start_transaction(hfsmp) != 0) { | |
575 | result = EINVAL; | |
576 | goto exit; | |
577 | } | |
578 | started_transaction = 1; | |
579 | ||
580 | /* | |
581 | * Once we started the transaction, nobody can compete | |
582 | * with us, so make sure this file is still there. | |
583 | */ | |
584 | if ((cp) && (cp->c_flag & C_NOEXISTS)) { | |
585 | result = ENOENT; | |
586 | goto exit; | |
587 | } | |
588 | ||
589 | /* | |
590 | * If there isn't an attributes b-tree then create one. | |
591 | */ | |
592 | if (hfsmp->hfs_attribute_vp == NULL) { | |
593 | result = hfs_create_attr_btree(hfsmp, ATTRIBUTE_FILE_NODE_SIZE, | |
594 | getnodecount(hfsmp, ATTRIBUTE_FILE_NODE_SIZE)); | |
595 | if (result) { | |
596 | goto exit; | |
597 | } | |
598 | } | |
599 | if (hfsmp->hfs_max_inline_attrsize == 0) { | |
600 | hfsmp->hfs_max_inline_attrsize = getmaxinlineattrsize(hfsmp->hfs_attribute_vp); | |
601 | } | |
602 | ||
603 | lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_EXCLUSIVE_LOCK); | |
604 | ||
605 | /* Build the b-tree key. */ | |
606 | iterator = hfs_mallocz(sizeof(*iterator)); | |
607 | result = hfs_buildattrkey(target_id, attr_name, (HFSPlusAttrKey *)&iterator->key); | |
608 | if (result) { | |
609 | goto exit_lock; | |
610 | } | |
611 | ||
612 | /* Preflight for replace/create semantics. */ | |
613 | btfile = VTOF(hfsmp->hfs_attribute_vp); | |
614 | btdata.bufferAddress = &attrdata; | |
615 | btdata.itemSize = sizeof(attrdata); | |
616 | btdata.itemCount = 1; | |
617 | exists = BTSearchRecord(btfile, iterator, &btdata, NULL, NULL) == 0; | |
618 | ||
619 | /* Replace requires that the attribute already exists. */ | |
620 | if ((option == UVFSXattrHowReplace) && !exists) { | |
621 | result = ENOATTR; | |
622 | goto exit_lock; | |
623 | } | |
624 | /* Create requires that the attribute doesn't exist. */ | |
625 | if ((option == UVFSXattrHowCreate) && exists) { | |
626 | result = EEXIST; | |
627 | goto exit_lock; | |
628 | } | |
629 | ||
630 | /* Enforce an upper limit. */ | |
631 | if (attrsize > HFS_XATTR_MAXSIZE) { | |
632 | result = E2BIG; | |
633 | goto exit_lock; | |
634 | } | |
635 | ||
636 | /* If it won't fit inline then use extent-based attributes. */ | |
637 | if (attrsize > hfsmp->hfs_max_inline_attrsize) { | |
638 | int blkcnt; | |
639 | int extentblks; | |
640 | u_int32_t *keystartblk; | |
641 | int i; | |
642 | ||
643 | /* Get some blocks. */ | |
644 | blkcnt = (int)howmany(attrsize, hfsmp->blockSize); | |
645 | extentbufsize = blkcnt * sizeof(HFSPlusExtentDescriptor); | |
646 | extentbufsize = roundup(extentbufsize, sizeof(HFSPlusExtentRecord)); | |
647 | extentptr = hfs_mallocz(extentbufsize); | |
648 | result = alloc_attr_blks(hfsmp, attrsize, extentbufsize, extentptr, &allocatedblks); | |
649 | if (result) { | |
650 | allocatedblks = 0; | |
651 | goto exit_lock; /* no more space */ | |
652 | } | |
653 | /* Copy data into the blocks. */ | |
654 | result = write_attr_data(hfsmp, (void*)buf, attrsize, extentptr); | |
655 | if (result) { | |
656 | if (vp) { | |
657 | LFHFS_LOG(LEVEL_DEBUG, "hfs_setxattr: write_attr_data vol=%s err (%d) :%s\n", | |
658 | hfsmp->vcbVN, result, attr_name); | |
659 | } | |
660 | goto exit_lock; | |
661 | } | |
662 | ||
663 | /* Now remove any previous attribute. */ | |
664 | if (exists) { | |
665 | result = remove_attribute_records(hfsmp, iterator); | |
666 | if (result) { | |
667 | if (vp) { | |
668 | LFHFS_LOG(LEVEL_DEBUG, "hfs_setxattr: remove_attribute_records vol=%s err (%d) %s:%s\n", | |
669 | hfsmp->vcbVN, result, "", attr_name); | |
670 | } | |
671 | goto exit_lock; | |
672 | } | |
673 | } | |
674 | /* Create attribute fork data record. */ | |
675 | recp = hfs_malloc(sizeof(HFSPlusAttrRecord)); | |
676 | ||
677 | btdata.bufferAddress = recp; | |
678 | btdata.itemCount = 1; | |
679 | btdata.itemSize = sizeof(HFSPlusAttrForkData); | |
680 | ||
681 | recp->recordType = kHFSPlusAttrForkData; | |
682 | recp->forkData.reserved = 0; | |
683 | recp->forkData.theFork.logicalSize = attrsize; | |
684 | recp->forkData.theFork.clumpSize = 0; | |
685 | recp->forkData.theFork.totalBlocks = blkcnt; | |
686 | bcopy(extentptr, recp->forkData.theFork.extents, sizeof(HFSPlusExtentRecord)); | |
687 | ||
688 | (void) hfs_buildattrkey(target_id, attr_name, (HFSPlusAttrKey *)&iterator->key); | |
689 | ||
690 | result = BTInsertRecord(btfile, iterator, &btdata, btdata.itemSize); | |
691 | if (result) { | |
692 | LFHFS_LOG(LEVEL_DEBUG, "hfs_setxattr: BTInsertRecord(): vol=%s %d,%s err=%d\n", | |
693 | hfsmp->vcbVN, target_id, attr_name, result); | |
694 | goto exit_lock; | |
695 | } | |
696 | extentblks = count_extent_blocks(blkcnt, recp->forkData.theFork.extents); | |
697 | blkcnt -= extentblks; | |
698 | keystartblk = &((HFSPlusAttrKey *)&iterator->key)->startBlock; | |
699 | i = 0; | |
700 | ||
701 | /* Create overflow extents as needed. */ | |
702 | while (blkcnt > 0) { | |
703 | /* Initialize the key and record. */ | |
704 | *keystartblk += (u_int32_t)extentblks; | |
705 | btdata.itemSize = sizeof(HFSPlusAttrExtents); | |
706 | recp->recordType = kHFSPlusAttrExtents; | |
707 | recp->overflowExtents.reserved = 0; | |
708 | ||
709 | /* Copy the next set of extents. */ | |
710 | i += kHFSPlusExtentDensity; | |
711 | bcopy(&extentptr[i], recp->overflowExtents.extents, sizeof(HFSPlusExtentRecord)); | |
712 | ||
713 | result = BTInsertRecord(btfile, iterator, &btdata, btdata.itemSize); | |
714 | if (result) { | |
715 | LFHFS_LOG(LEVEL_DEBUG, "hfs_setxattr: BTInsertRecord() overflow: vol=%s %d,%s err=%d\n", | |
716 | hfsmp->vcbVN, target_id, attr_name, result); | |
717 | goto exit_lock; | |
718 | } | |
719 | extentblks = count_extent_blocks(blkcnt, recp->overflowExtents.extents); | |
720 | blkcnt -= extentblks; | |
721 | } | |
722 | } else { /* Inline data */ | |
723 | if (exists) { | |
724 | result = remove_attribute_records(hfsmp, iterator); | |
725 | if (result) { | |
726 | goto exit_lock; | |
727 | } | |
728 | } | |
729 | ||
730 | /* Calculate size of record rounded up to multiple of 2 bytes. */ | |
731 | btdata.itemSize = sizeof(HFSPlusAttrData) - 2 + attrsize + ((attrsize & 1) ? 1 : 0); | |
732 | recp = hfs_malloc(btdata.itemSize); | |
733 | ||
734 | recp->recordType = kHFSPlusAttrInlineData; | |
735 | recp->attrData.reserved[0] = 0; | |
736 | recp->attrData.reserved[1] = 0; | |
737 | recp->attrData.attrSize = (u_int32_t)attrsize; | |
738 | ||
739 | /* Copy in the attribute data (if any). */ | |
740 | if (attrsize > 0) { | |
741 | bcopy(buf, &recp->attrData.attrData, attrsize); | |
742 | } | |
743 | ||
744 | (void) hfs_buildattrkey(target_id, attr_name, (HFSPlusAttrKey *)&iterator->key); | |
745 | ||
746 | btdata.bufferAddress = recp; | |
747 | btdata.itemCount = 1; | |
748 | result = BTInsertRecord(btfile, iterator, &btdata, btdata.itemSize); | |
749 | } | |
750 | ||
751 | exit_lock: | |
752 | if (btfile && started_transaction) { | |
753 | (void) BTFlushPath(btfile); | |
754 | } | |
755 | hfs_systemfile_unlock(hfsmp, lockflags); | |
756 | if (result == 0) { | |
757 | if (vp) { | |
758 | cp = VTOC(vp); | |
759 | /* Setting an attribute only updates change time and not | |
760 | * modified time of the file. | |
761 | */ | |
762 | cp->c_touch_chgtime = TRUE; | |
763 | cp->c_flag |= C_MODIFIED; | |
764 | cp->c_attr.ca_recflags |= kHFSHasAttributesMask; | |
765 | if ((strcmp(attr_name, KAUTH_FILESEC_XATTR) == 0)) { | |
766 | cp->c_attr.ca_recflags |= kHFSHasSecurityMask; | |
767 | } | |
768 | (void) hfs_update(vp, 0); | |
769 | } | |
770 | } | |
771 | if (started_transaction) { | |
772 | if (result && allocatedblks) { | |
773 | free_attr_blks(hfsmp, allocatedblks, extentptr); | |
774 | } | |
775 | hfs_end_transaction(hfsmp); | |
776 | } | |
777 | ||
778 | hfs_free(recp); | |
779 | hfs_free(extentptr); | |
780 | hfs_free(iterator); | |
781 | ||
782 | exit: | |
783 | if (cp) { | |
784 | hfs_unlock(cp); | |
785 | } | |
786 | ||
787 | return (result == btNotFound ? ENOATTR : MacToVFSError(result)); | |
788 | } | |
789 | ||
790 | /* | |
791 | * Remove an extended attribute. | |
792 | */ | |
793 | int | |
794 | hfs_vnop_removexattr(vnode_t vp, const char *attr_name) | |
795 | { | |
796 | struct cnode *cp = VTOC(vp); | |
797 | struct hfsmount *hfsmp; | |
798 | BTreeIterator * iterator = NULL; | |
799 | int lockflags; | |
800 | int result; | |
801 | ||
802 | if (attr_name == NULL || attr_name[0] == '\0') { | |
803 | return (EINVAL); /* invalid name */ | |
804 | } | |
805 | hfsmp = VTOHFS(vp); | |
806 | if (VNODE_IS_RSRC(vp)) { | |
807 | return (EPERM); | |
808 | } | |
809 | ||
810 | /* Write the Resource Fork. */ | |
811 | if (strcmp(attr_name, XATTR_RESOURCEFORK_NAME) == 0) { | |
812 | return (ENOTSUP); | |
813 | } | |
814 | ||
815 | /* Clear out the Finder Info. */ | |
816 | if (strcmp(attr_name, XATTR_FINDERINFO_NAME) == 0) { | |
817 | void * finderinfo_start; | |
818 | int finderinfo_size; | |
819 | u_int8_t finderinfo[32]; | |
820 | u_int32_t date_added = 0, write_gen_counter = 0, document_id = 0; | |
821 | u_int8_t *finfo = NULL; | |
822 | ||
823 | if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) { | |
824 | return (result); | |
825 | } | |
826 | ||
827 | /* Use the local copy to store our temporary changes. */ | |
828 | bcopy(cp->c_finderinfo, finderinfo, sizeof(finderinfo)); | |
829 | ||
830 | /* Zero out the date added field in the local copy */ | |
831 | hfs_zero_hidden_fields (cp, finderinfo); | |
832 | ||
833 | /* Don't expose a symlink's private type/creator. */ | |
834 | if (vnode_islnk(vp)) { | |
835 | struct FndrFileInfo *fip; | |
836 | ||
837 | fip = (struct FndrFileInfo *)&finderinfo; | |
838 | fip->fdType = 0; | |
839 | fip->fdCreator = 0; | |
840 | } | |
841 | ||
842 | /* Do the byte compare against the local copy */ | |
843 | if (bcmp(finderinfo, emptyfinfo, sizeof(emptyfinfo)) == 0) { | |
844 | hfs_unlock(cp); | |
845 | return (ENOATTR); | |
846 | } | |
847 | ||
848 | /* | |
849 | * If there was other content, zero out everything except | |
850 | * type/creator and date added. First, save the date added. | |
851 | */ | |
852 | finfo = cp->c_finderinfo; | |
853 | finfo = finfo + 16; | |
854 | if (S_ISREG(cp->c_attr.ca_mode) || S_ISLNK(cp->c_attr.ca_mode)) { | |
855 | struct FndrExtendedFileInfo *extinfo = (struct FndrExtendedFileInfo *)finfo; | |
856 | date_added = extinfo->date_added; | |
857 | write_gen_counter = extinfo->write_gen_counter; | |
858 | document_id = extinfo->document_id; | |
859 | } else if (S_ISDIR(cp->c_attr.ca_mode)) { | |
860 | struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)finfo; | |
861 | date_added = extinfo->date_added; | |
862 | write_gen_counter = extinfo->write_gen_counter; | |
863 | document_id = extinfo->document_id; | |
864 | } | |
865 | ||
866 | if (vnode_islnk(vp)) { | |
867 | /* Ignore type/creator */ | |
868 | finderinfo_start = &cp->c_finderinfo[8]; | |
869 | finderinfo_size = sizeof(cp->c_finderinfo) - 8; | |
870 | } else { | |
871 | finderinfo_start = &cp->c_finderinfo[0]; | |
872 | finderinfo_size = sizeof(cp->c_finderinfo); | |
873 | } | |
874 | bzero(finderinfo_start, finderinfo_size); | |
875 | ||
876 | /* Now restore the date added */ | |
877 | if (S_ISREG(cp->c_attr.ca_mode) || S_ISLNK(cp->c_attr.ca_mode)) { | |
878 | struct FndrExtendedFileInfo *extinfo = (struct FndrExtendedFileInfo *)finfo; | |
879 | extinfo->date_added = date_added; | |
880 | extinfo->write_gen_counter = write_gen_counter; | |
881 | extinfo->document_id = document_id; | |
882 | } else if (S_ISDIR(cp->c_attr.ca_mode)) { | |
883 | struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)finfo; | |
884 | extinfo->date_added = date_added; | |
885 | extinfo->write_gen_counter = write_gen_counter; | |
886 | extinfo->document_id = document_id; | |
887 | } | |
888 | ||
889 | /* Updating finderInfo updates change time and modified time */ | |
890 | cp->c_touch_chgtime = TRUE; | |
891 | cp->c_flag |= C_MODIFIED; | |
892 | hfs_update(vp, 0); | |
893 | ||
894 | hfs_unlock(cp); | |
895 | ||
896 | return (0); | |
897 | } | |
898 | ||
899 | if (hfsmp->hfs_attribute_vp == NULL) { | |
900 | return (ENOATTR); | |
901 | } | |
902 | ||
903 | iterator = hfs_mallocz(sizeof(*iterator)); | |
904 | ||
905 | if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) { | |
906 | goto exit_nolock; | |
907 | } | |
908 | ||
909 | result = hfs_buildattrkey(cp->c_fileid, attr_name, (HFSPlusAttrKey *)&iterator->key); | |
910 | if (result) { | |
911 | goto exit; | |
912 | } | |
913 | ||
914 | if (hfs_start_transaction(hfsmp) != 0) { | |
915 | result = EINVAL; | |
916 | goto exit; | |
917 | } | |
918 | lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE | SFL_BITMAP, HFS_EXCLUSIVE_LOCK); | |
919 | ||
920 | result = remove_attribute_records(hfsmp, iterator); | |
921 | ||
922 | hfs_systemfile_unlock(hfsmp, lockflags); | |
923 | ||
924 | if (result == 0) { | |
925 | cp->c_touch_chgtime = TRUE; | |
926 | ||
927 | lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK); | |
928 | ||
929 | /* If no more attributes exist, clear attribute bit */ | |
930 | result = file_attribute_exist(hfsmp, cp->c_fileid); | |
931 | if (result == 0) { | |
932 | cp->c_attr.ca_recflags &= ~kHFSHasAttributesMask; | |
933 | cp->c_flag |= C_MODIFIED; | |
934 | } | |
935 | if (result == EEXIST) { | |
936 | result = 0; | |
937 | } | |
938 | ||
939 | hfs_systemfile_unlock(hfsmp, lockflags); | |
940 | ||
941 | /* If ACL was removed, clear security bit */ | |
942 | if (strcmp(attr_name, KAUTH_FILESEC_XATTR) == 0) { | |
943 | cp->c_attr.ca_recflags &= ~kHFSHasSecurityMask; | |
944 | cp->c_flag |= C_MODIFIED; | |
945 | } | |
946 | (void) hfs_update(vp, 0); | |
947 | } | |
948 | ||
949 | hfs_end_transaction(hfsmp); | |
950 | exit: | |
951 | hfs_unlock(cp); | |
952 | exit_nolock: | |
953 | hfs_free(iterator); | |
954 | return MacToVFSError(result); | |
955 | } | |
956 | ||
957 | /* | |
958 | * Initialize vnode for attribute data I/O. | |
959 | * | |
960 | * On success, | |
961 | * - returns zero | |
962 | * - the attrdata vnode is initialized as hfsmp->hfs_attrdata_vp | |
963 | * - an iocount is taken on the attrdata vnode which exists | |
964 | * for the entire duration of the mount. It is only dropped | |
965 | * during unmount | |
966 | * - the attrdata cnode is not locked | |
967 | * | |
968 | * On failure, | |
969 | * - returns non-zero value | |
970 | * - the caller does not have to worry about any locks or references | |
971 | */ | |
972 | int init_attrdata_vnode(struct hfsmount *hfsmp) | |
973 | { | |
974 | vnode_t vp; | |
975 | int result = 0; | |
976 | struct cat_desc cat_desc; | |
977 | struct cat_attr cat_attr; | |
978 | struct cat_fork cat_fork; | |
979 | int newvnode_flags = 0; | |
980 | ||
981 | bzero(&cat_desc, sizeof(cat_desc)); | |
982 | cat_desc.cd_parentcnid = kHFSRootParentID; | |
983 | cat_desc.cd_nameptr = (const u_int8_t *)hfs_attrdatafilename; | |
984 | cat_desc.cd_namelen = strlen(hfs_attrdatafilename); | |
985 | cat_desc.cd_cnid = kHFSAttributeDataFileID; | |
986 | /* Tag vnode as system file, note that we can still use cluster I/O */ | |
987 | cat_desc.cd_flags |= CD_ISMETA; | |
988 | ||
989 | bzero(&cat_attr, sizeof(cat_attr)); | |
990 | cat_attr.ca_linkcount = 1; | |
991 | cat_attr.ca_mode = S_IFREG; | |
992 | cat_attr.ca_fileid = cat_desc.cd_cnid; | |
993 | cat_attr.ca_blocks = hfsmp->totalBlocks; | |
994 | ||
995 | /* | |
996 | * The attribute data file is a virtual file that spans the | |
997 | * entire file system space. | |
998 | * | |
999 | * Each extent-based attribute occupies a unique portion of | |
1000 | * in this virtual file. The cluster I/O is done using actual | |
1001 | * allocation block offsets so no additional mapping is needed | |
1002 | * for the VNOP_BLOCKMAP call. | |
1003 | * | |
1004 | * This approach allows the attribute data to be cached without | |
1005 | * incurring the high cost of using a separate vnode per attribute. | |
1006 | * | |
1007 | * Since we need to acquire the attribute b-tree file lock anyways, | |
1008 | * the virtual file doesn't introduce any additional serialization. | |
1009 | */ | |
1010 | bzero(&cat_fork, sizeof(cat_fork)); | |
1011 | cat_fork.cf_size = (u_int64_t)hfsmp->totalBlocks * (u_int64_t)hfsmp->blockSize; | |
1012 | cat_fork.cf_blocks = hfsmp->totalBlocks; | |
1013 | cat_fork.cf_extents[0].startBlock = 0; | |
1014 | cat_fork.cf_extents[0].blockCount = cat_fork.cf_blocks; | |
1015 | ||
1016 | result = hfs_getnewvnode(hfsmp, NULL, NULL, &cat_desc, 0, &cat_attr, | |
1017 | &cat_fork, &vp, &newvnode_flags); | |
1018 | if (result == 0) { | |
1019 | hfsmp->hfs_attrdata_vp = vp; | |
1020 | hfs_unlock(VTOC(vp)); | |
1021 | } | |
1022 | return (result); | |
1023 | } | |
1024 | ||
1025 | /* Check if any attribute record exist for given fileID. This function | |
1026 | * is called by hfs_vnop_removexattr to determine if it should clear the | |
1027 | * attribute bit in the catalog record or not. | |
1028 | * | |
1029 | * Note - you must acquire a shared lock on the attribute btree before | |
1030 | * calling this function. | |
1031 | * | |
1032 | * Output: | |
1033 | * EEXIST - If attribute record was found | |
1034 | * 0 - Attribute was not found | |
1035 | * (other) - Other error (such as EIO) | |
1036 | */ | |
1037 | int | |
1038 | file_attribute_exist(struct hfsmount *hfsmp, uint32_t fileID) | |
1039 | { | |
1040 | HFSPlusAttrKey *key; | |
1041 | BTreeIterator * iterator = NULL; | |
1042 | struct filefork *btfile; | |
1043 | int result = 0; | |
1044 | ||
1045 | // if there's no attribute b-tree we sure as heck | |
1046 | // can't have any attributes! | |
1047 | if (hfsmp->hfs_attribute_vp == NULL) { | |
1048 | return 0; | |
1049 | } | |
1050 | ||
1051 | iterator = hfs_mallocz(sizeof(BTreeIterator)); | |
1052 | if (iterator == NULL) return ENOMEM; | |
1053 | ||
1054 | key = (HFSPlusAttrKey *)&iterator->key; | |
1055 | ||
1056 | result = hfs_buildattrkey(fileID, NULL, key); | |
1057 | if (result) { | |
1058 | goto out; | |
1059 | } | |
1060 | ||
1061 | btfile = VTOF(hfsmp->hfs_attribute_vp); | |
1062 | result = BTSearchRecord(btfile, iterator, NULL, NULL, NULL); | |
1063 | if (result && (result != btNotFound)) { | |
1064 | goto out; | |
1065 | } | |
1066 | ||
1067 | result = BTIterateRecord(btfile, kBTreeNextRecord, iterator, NULL, NULL); | |
1068 | /* If no next record was found or fileID for next record did not match, | |
1069 | * no more attributes exist for this fileID | |
1070 | */ | |
1071 | if ((result && (result == btNotFound)) || (key->fileID != fileID)) { | |
1072 | result = 0; | |
1073 | } else { | |
1074 | result = EEXIST; | |
1075 | } | |
1076 | ||
1077 | out: | |
1078 | hfs_free(iterator); | |
1079 | return result; | |
1080 | } | |
1081 | ||
1082 | /* | |
1083 | * Read an extent based attribute. | |
1084 | */ | |
1085 | static int | |
1086 | read_attr_data(struct hfsmount *hfsmp, void *buf, size_t datasize, HFSPlusExtentDescriptor *extents) | |
1087 | { | |
1088 | vnode_t evp = hfsmp->hfs_attrdata_vp; | |
1089 | uint64_t iosize; | |
1090 | uint64_t attrsize; | |
1091 | uint64_t blksize; | |
1092 | uint64_t alreadyread; | |
1093 | int i; | |
1094 | int result = 0; | |
1095 | ||
1096 | hfs_lock_truncate(VTOC(evp), HFS_SHARED_LOCK, HFS_LOCK_DEFAULT); | |
1097 | ||
1098 | attrsize = (uint64_t)datasize; | |
1099 | blksize = (uint64_t)hfsmp->blockSize; | |
1100 | alreadyread = 0; | |
1101 | ||
1102 | /* | |
1103 | * Read the attribute data one extent at a time. | |
1104 | * For the typical case there is only one extent. | |
1105 | */ | |
1106 | for (i = 0; (attrsize > 0) && (extents[i].startBlock != 0); ++i) { | |
1107 | iosize = extents[i].blockCount * blksize; | |
1108 | iosize = MIN(iosize, attrsize); | |
1109 | ||
1110 | uint64_t actualread = 0; | |
1111 | ||
1112 | result = raw_readwrite_read_internal( evp, extents[i].startBlock, extents[i].blockCount * blksize, | |
1113 | alreadyread, iosize, buf, &actualread ); | |
1114 | #if HFS_XATTR_VERBOSE | |
1115 | LFHFS_LOG(LEVEL_DEBUG, "hfs: read_attr_data: cr iosize %lld [%d, %d] (%d)\n", | |
1116 | actualread, extents[i].startBlock, extents[i].blockCount, result); | |
1117 | #endif | |
1118 | if (result) | |
1119 | break; | |
1120 | ||
1121 | // read the remaining part after sector boundary if we have such | |
1122 | if (iosize != actualread) | |
1123 | { | |
1124 | result = raw_readwrite_read_internal( evp, extents[i].startBlock, extents[i].blockCount * blksize, | |
1125 | alreadyread + actualread, iosize - actualread, | |
1126 | (uint8_t*)buf + actualread, &actualread ); | |
1127 | #if HFS_XATTR_VERBOSE | |
1128 | LFHFS_LOG(LEVEL_DEBUG, "hfs: read_attr_data: cr iosize %lld [%d, %d] (%d)\n", | |
1129 | actualread, extents[i].startBlock, extents[i].blockCount, result); | |
1130 | #endif | |
1131 | if (result) | |
1132 | break; | |
1133 | } | |
1134 | ||
1135 | attrsize -= iosize; | |
1136 | ||
1137 | alreadyread += iosize; | |
1138 | buf = (uint8_t*)buf + iosize; | |
1139 | } | |
1140 | ||
1141 | hfs_unlock_truncate(VTOC(evp), HFS_LOCK_DEFAULT); | |
1142 | return (result); | |
1143 | } | |
1144 | ||
1145 | /* | |
1146 | * Write an extent based attribute. | |
1147 | */ | |
1148 | static int | |
1149 | write_attr_data(struct hfsmount *hfsmp, void *buf, size_t datasize, HFSPlusExtentDescriptor *extents) | |
1150 | { | |
1151 | vnode_t evp = hfsmp->hfs_attrdata_vp; | |
1152 | uint64_t iosize; | |
1153 | uint64_t attrsize; | |
1154 | uint64_t blksize; | |
1155 | uint64_t alreadywritten; | |
1156 | int i; | |
1157 | int result = 0; | |
1158 | ||
1159 | hfs_lock_truncate(VTOC(evp), HFS_SHARED_LOCK, HFS_LOCK_DEFAULT); | |
1160 | ||
1161 | attrsize = (uint64_t)datasize; | |
1162 | blksize = (uint64_t)hfsmp->blockSize; | |
1163 | alreadywritten = 0; | |
1164 | ||
1165 | /* | |
1166 | * Write the attribute data one extent at a time. | |
1167 | */ | |
1168 | for (i = 0; (attrsize > 0) && (extents[i].startBlock != 0); ++i) { | |
1169 | iosize = extents[i].blockCount * blksize; | |
1170 | iosize = MIN(iosize, attrsize); | |
1171 | ||
1172 | uint64_t actualwritten = 0; | |
1173 | ||
1174 | result = raw_readwrite_write_internal( evp, extents[i].startBlock, extents[i].blockCount * blksize, | |
1175 | alreadywritten, iosize, buf, &actualwritten ); | |
1176 | #if HFS_XATTR_VERBOSE | |
1177 | LFHFS_LOG(LEVEL_DEBUG, "hfs: write_attr_data: cw iosize %lld [%d, %d] (%d)\n", | |
1178 | actualwritten, extents[i].startBlock, extents[i].blockCount, result); | |
1179 | #endif | |
1180 | if (result) | |
1181 | break; | |
1182 | ||
1183 | // write the remaining part after sector boundary if we have such | |
1184 | if (iosize != actualwritten) | |
1185 | { | |
1186 | result = raw_readwrite_write_internal( evp, extents[i].startBlock, extents[i].blockCount * blksize, | |
1187 | alreadywritten + actualwritten, iosize - actualwritten, | |
1188 | (uint8_t*)buf + actualwritten, &actualwritten ); | |
1189 | #if HFS_XATTR_VERBOSE | |
1190 | LFHFS_LOG(LEVEL_DEBUG, "hfs: write_attr_data: cw iosize %lld [%d, %d] (%d)\n", | |
1191 | actualwritten, extents[i].startBlock, extents[i].blockCount, result); | |
1192 | #endif | |
1193 | if (result) | |
1194 | break; | |
1195 | } | |
1196 | ||
1197 | attrsize -= iosize; | |
1198 | ||
1199 | alreadywritten += iosize; | |
1200 | buf = (uint8_t*)buf + iosize; | |
1201 | } | |
1202 | ||
1203 | hfs_unlock_truncate(VTOC(evp), HFS_LOCK_DEFAULT); | |
1204 | return (result); | |
1205 | } | |
1206 | ||
1207 | /* | |
1208 | * Allocate blocks for an extent based attribute. | |
1209 | */ | |
1210 | static int | |
1211 | alloc_attr_blks(struct hfsmount *hfsmp, size_t attrsize, size_t extentbufsize, HFSPlusExtentDescriptor *extents, int *blocks) | |
1212 | { | |
1213 | int blkcnt; | |
1214 | int startblk; | |
1215 | int lockflags; | |
1216 | int i; | |
1217 | int maxextents; | |
1218 | int result = 0; | |
1219 | ||
1220 | startblk = hfsmp->hfs_metazone_end; | |
1221 | blkcnt = (int)howmany(attrsize, hfsmp->blockSize); | |
1222 | if (blkcnt > (int)hfs_freeblks(hfsmp, 0)) { | |
1223 | return (ENOSPC); | |
1224 | } | |
1225 | *blocks = blkcnt; | |
1226 | maxextents = (int)extentbufsize / sizeof(HFSPlusExtentDescriptor); | |
1227 | ||
1228 | lockflags = hfs_systemfile_lock(hfsmp, SFL_BITMAP, HFS_EXCLUSIVE_LOCK); | |
1229 | ||
1230 | for (i = 0; (blkcnt > 0) && (i < maxextents); i++) { | |
1231 | /* Try allocating and see if we find something decent */ | |
1232 | result = BlockAllocate(hfsmp, startblk, blkcnt, blkcnt, 0, | |
1233 | &extents[i].startBlock, &extents[i].blockCount); | |
1234 | /* | |
1235 | * If we couldn't find anything, then re-try the allocation but allow | |
1236 | * journal flushes. | |
1237 | */ | |
1238 | if (result == dskFulErr) { | |
1239 | result = BlockAllocate(hfsmp, startblk, blkcnt, blkcnt, HFS_ALLOC_FLUSHTXN, | |
1240 | &extents[i].startBlock, &extents[i].blockCount); | |
1241 | } | |
1242 | #if HFS_XATTR_VERBOSE | |
1243 | LFHFS_LOG(LEVEL_DEBUG,"hfs: alloc_attr_blks: BA blkcnt %d [%d, %d] (%d)\n", | |
1244 | blkcnt, extents[i].startBlock, extents[i].blockCount, result); | |
1245 | #endif | |
1246 | if (result) { | |
1247 | extents[i].startBlock = 0; | |
1248 | extents[i].blockCount = 0; | |
1249 | break; | |
1250 | } | |
1251 | blkcnt -= extents[i].blockCount; | |
1252 | startblk = extents[i].startBlock + extents[i].blockCount; | |
1253 | } | |
1254 | /* | |
1255 | * If it didn't fit in the extents buffer then bail. | |
1256 | */ | |
1257 | if (blkcnt) { | |
1258 | result = ENOSPC; | |
1259 | #if HFS_XATTR_VERBOSE | |
1260 | LFHFS_LOG(LEVEL_DEBUG, "hfs: alloc_attr_blks: unexpected failure, %d blocks unallocated\n", blkcnt); | |
1261 | #endif | |
1262 | for (; i >= 0; i--) { | |
1263 | if ((blkcnt = extents[i].blockCount) != 0) { | |
1264 | (void) BlockDeallocate(hfsmp, extents[i].startBlock, blkcnt, 0); | |
1265 | extents[i].startBlock = 0; | |
1266 | extents[i].blockCount = 0; | |
1267 | } | |
1268 | } | |
1269 | } | |
1270 | ||
1271 | hfs_systemfile_unlock(hfsmp, lockflags); | |
1272 | return MacToVFSError(result); | |
1273 | } | |
1274 | ||
1275 | /* | |
1276 | * Release blocks from an extent based attribute. | |
1277 | */ | |
1278 | static void | |
1279 | free_attr_blks(struct hfsmount *hfsmp, int blkcnt, HFSPlusExtentDescriptor *extents) | |
1280 | { | |
1281 | vnode_t evp = hfsmp->hfs_attrdata_vp; | |
1282 | int remblks = blkcnt; | |
1283 | int lockflags; | |
1284 | int i; | |
1285 | ||
1286 | lockflags = hfs_systemfile_lock(hfsmp, SFL_BITMAP, HFS_EXCLUSIVE_LOCK); | |
1287 | ||
1288 | for (i = 0; (remblks > 0) && (extents[i].blockCount != 0); i++) { | |
1289 | if (extents[i].blockCount > (u_int32_t)blkcnt) { | |
1290 | #if HFS_XATTR_VERBOSE | |
1291 | LFHFS_LOG(LEVEL_DEBUG, "hfs: free_attr_blks: skipping bad extent [%d, %d]\n", | |
1292 | extents[i].startBlock, extents[i].blockCount); | |
1293 | #endif | |
1294 | extents[i].blockCount = 0; | |
1295 | continue; | |
1296 | } | |
1297 | if (extents[i].startBlock == 0) { | |
1298 | break; | |
1299 | } | |
1300 | (void)BlockDeallocate(hfsmp, extents[i].startBlock, extents[i].blockCount, 0); | |
1301 | remblks -= extents[i].blockCount; | |
1302 | #if HFS_XATTR_VERBOSE | |
1303 | LFHFS_LOG(LEVEL_DEBUG, "hfs: free_attr_blks: BlockDeallocate [%d, %d]\n", | |
1304 | extents[i].startBlock, extents[i].blockCount); | |
1305 | #endif | |
1306 | extents[i].startBlock = 0; | |
1307 | extents[i].blockCount = 0; | |
1308 | ||
1309 | /* Discard any resident pages for this block range. */ | |
1310 | if (evp) { | |
1311 | #if LF_HFS_FULL_VNODE_SUPPORT | |
1312 | off_t start, end; | |
1313 | start = (u_int64_t)extents[i].startBlock * (u_int64_t)hfsmp->blockSize; | |
1314 | end = start + (u_int64_t)extents[i].blockCount * (u_int64_t)hfsmp->blockSize; | |
1315 | //TBD - Need to update this vnode | |
1316 | (void) ubc_msync(hfsmp->hfs_attrdata_vp, start, end, &start, UBC_INVALIDATE); | |
1317 | #endif | |
1318 | } | |
1319 | } | |
1320 | ||
1321 | hfs_systemfile_unlock(hfsmp, lockflags); | |
1322 | } | |
1323 | ||
1324 | static int | |
1325 | has_overflow_extents(HFSPlusForkData *forkdata) | |
1326 | { | |
1327 | u_int32_t blocks; | |
1328 | ||
1329 | if (forkdata->extents[7].blockCount == 0) | |
1330 | return (0); | |
1331 | ||
1332 | blocks = forkdata->extents[0].blockCount + | |
1333 | forkdata->extents[1].blockCount + | |
1334 | forkdata->extents[2].blockCount + | |
1335 | forkdata->extents[3].blockCount + | |
1336 | forkdata->extents[4].blockCount + | |
1337 | forkdata->extents[5].blockCount + | |
1338 | forkdata->extents[6].blockCount + | |
1339 | forkdata->extents[7].blockCount; | |
1340 | ||
1341 | return (forkdata->totalBlocks > blocks); | |
1342 | } | |
1343 | ||
1344 | static int | |
1345 | count_extent_blocks(int maxblks, HFSPlusExtentRecord extents) | |
1346 | { | |
1347 | int blocks; | |
1348 | int i; | |
1349 | ||
1350 | for (i = 0, blocks = 0; i < kHFSPlusExtentDensity; ++i) { | |
1351 | /* Ignore obvious bogus extents. */ | |
1352 | if (extents[i].blockCount > (u_int32_t)maxblks) | |
1353 | continue; | |
1354 | if (extents[i].startBlock == 0 || extents[i].blockCount == 0) | |
1355 | break; | |
1356 | blocks += extents[i].blockCount; | |
1357 | } | |
1358 | return (blocks); | |
1359 | } | |
1360 | ||
1361 | /* | |
1362 | * Remove all the records for a given attribute. | |
1363 | * | |
1364 | * - Used by hfs_vnop_removexattr, hfs_vnop_setxattr and hfs_removeallattr. | |
1365 | * - A transaction must have been started. | |
1366 | * - The Attribute b-tree file must be locked exclusive. | |
1367 | * - The Allocation Bitmap file must be locked exclusive. | |
1368 | * - The iterator key must be initialized. | |
1369 | */ | |
1370 | static int | |
1371 | remove_attribute_records(struct hfsmount *hfsmp, BTreeIterator * iterator) | |
1372 | { | |
1373 | struct filefork *btfile; | |
1374 | FSBufferDescriptor btdata; | |
1375 | HFSPlusAttrRecord attrdata; /* 90 bytes */ | |
1376 | u_int16_t datasize; | |
1377 | int result; | |
1378 | ||
1379 | btfile = VTOF(hfsmp->hfs_attribute_vp); | |
1380 | ||
1381 | btdata.bufferAddress = &attrdata; | |
1382 | btdata.itemSize = sizeof(attrdata); | |
1383 | btdata.itemCount = 1; | |
1384 | result = BTSearchRecord(btfile, iterator, &btdata, &datasize, NULL); | |
1385 | if (result) { | |
1386 | goto exit; /* no records. */ | |
1387 | } | |
1388 | /* | |
1389 | * Free the blocks from extent based attributes. | |
1390 | * | |
1391 | * Note that the block references (btree records) are removed | |
1392 | * before releasing the blocks in the allocation bitmap. | |
1393 | */ | |
1394 | if (attrdata.recordType == kHFSPlusAttrForkData) { | |
1395 | int totalblks; | |
1396 | int extentblks; | |
1397 | u_int32_t *keystartblk; | |
1398 | ||
1399 | if (datasize < sizeof(HFSPlusAttrForkData)) { | |
1400 | LFHFS_LOG(LEVEL_DEBUG, "remove_attribute_records: bad record size %d (expecting %lu)\n", datasize, sizeof(HFSPlusAttrForkData)); | |
1401 | } | |
1402 | totalblks = attrdata.forkData.theFork.totalBlocks; | |
1403 | ||
1404 | /* Process the first 8 extents. */ | |
1405 | extentblks = count_extent_blocks(totalblks, attrdata.forkData.theFork.extents); | |
1406 | if (extentblks > totalblks) | |
1407 | { | |
1408 | LFHFS_LOG(LEVEL_ERROR, "remove_attribute_records: corruption (1)..."); | |
1409 | hfs_assert(0); | |
1410 | } | |
1411 | if (BTDeleteRecord(btfile, iterator) == 0) { | |
1412 | free_attr_blks(hfsmp, extentblks, attrdata.forkData.theFork.extents); | |
1413 | } | |
1414 | totalblks -= extentblks; | |
1415 | keystartblk = &((HFSPlusAttrKey *)&iterator->key)->startBlock; | |
1416 | ||
1417 | /* Process any overflow extents. */ | |
1418 | while (totalblks) { | |
1419 | *keystartblk += (u_int32_t)extentblks; | |
1420 | ||
1421 | result = BTSearchRecord(btfile, iterator, &btdata, &datasize, NULL); | |
1422 | if (result || | |
1423 | (attrdata.recordType != kHFSPlusAttrExtents) || | |
1424 | (datasize < sizeof(HFSPlusAttrExtents))) { | |
1425 | LFHFS_LOG(LEVEL_ERROR, "remove_attribute_records: BTSearchRecord: vol=%s, err=%d (%d), totalblks %d\n", | |
1426 | hfsmp->vcbVN, MacToVFSError(result), attrdata.recordType != kHFSPlusAttrExtents, totalblks); | |
1427 | result = ENOATTR; | |
1428 | break; /* break from while */ | |
1429 | } | |
1430 | /* Process the next 8 extents. */ | |
1431 | extentblks = count_extent_blocks(totalblks, attrdata.overflowExtents.extents); | |
1432 | if (extentblks > totalblks) | |
1433 | { | |
1434 | LFHFS_LOG(LEVEL_ERROR, "remove_attribute_records: corruption (2)..."); | |
1435 | hfs_assert(0); | |
1436 | } | |
1437 | if (BTDeleteRecord(btfile, iterator) == 0) { | |
1438 | free_attr_blks(hfsmp, extentblks, attrdata.overflowExtents.extents); | |
1439 | } | |
1440 | totalblks -= extentblks; | |
1441 | } | |
1442 | } else { | |
1443 | result = BTDeleteRecord(btfile, iterator); | |
1444 | } | |
1445 | (void) BTFlushPath(btfile); | |
1446 | exit: | |
1447 | return (result == btNotFound ? ENOATTR : MacToVFSError(result)); | |
1448 | } | |
1449 | ||
1450 | /* | |
1451 | * Retrieve the list of extended attribute names. | |
1452 | */ | |
1453 | int | |
1454 | hfs_vnop_listxattr(vnode_t vp, void *buf, size_t bufsize, size_t *actual_size) | |
1455 | { | |
1456 | struct cnode *cp = VTOC(vp); | |
1457 | struct hfsmount *hfsmp; | |
1458 | BTreeIterator * iterator = NULL; | |
1459 | struct filefork *btfile; | |
1460 | struct listattr_callback_state state; | |
1461 | int lockflags; | |
1462 | int result; | |
1463 | u_int8_t finderinfo[32]; | |
1464 | ||
1465 | if (actual_size == NULL) { | |
1466 | return (EINVAL); | |
1467 | } | |
1468 | if (VNODE_IS_RSRC(vp)) { | |
1469 | return (EPERM); | |
1470 | } | |
1471 | ||
1472 | hfsmp = VTOHFS(vp); | |
1473 | *actual_size = 0; | |
1474 | ||
1475 | /* | |
1476 | * Take the truncate lock; this serializes us against the ioctl | |
1477 | * to truncate data & reset the decmpfs state | |
1478 | * in the compressed file handler. | |
1479 | */ | |
1480 | hfs_lock_truncate(cp, HFS_SHARED_LOCK, HFS_LOCK_DEFAULT); | |
1481 | ||
1482 | /* Now the regular cnode lock (shared) */ | |
1483 | if ((result = hfs_lock(cp, HFS_SHARED_LOCK, HFS_LOCK_DEFAULT))) { | |
1484 | hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT); | |
1485 | return (result); | |
1486 | } | |
1487 | ||
1488 | /* | |
1489 | * Make a copy of the cnode's finderinfo to a local so we can | |
1490 | * zero out the date added field. Also zero out the private type/creator | |
1491 | * for symlinks. | |
1492 | */ | |
1493 | bcopy(cp->c_finderinfo, finderinfo, sizeof(finderinfo)); | |
1494 | hfs_zero_hidden_fields (cp, finderinfo); | |
1495 | ||
1496 | /* Don't expose a symlink's private type/creator. */ | |
1497 | if (vnode_islnk(vp)) { | |
1498 | struct FndrFileInfo *fip; | |
1499 | ||
1500 | fip = (struct FndrFileInfo *)&finderinfo; | |
1501 | fip->fdType = 0; | |
1502 | fip->fdCreator = 0; | |
1503 | } | |
1504 | ||
1505 | ||
1506 | /* If Finder Info is non-empty then export it's name. */ | |
1507 | if (bcmp(finderinfo, emptyfinfo, sizeof(emptyfinfo)) != 0) { | |
1508 | if (buf == NULL) { | |
1509 | *actual_size += sizeof(XATTR_FINDERINFO_NAME); | |
1510 | } else if (bufsize < sizeof(XATTR_FINDERINFO_NAME)) { | |
1511 | result = ERANGE; | |
1512 | goto exit; | |
1513 | } else { | |
1514 | *actual_size += sizeof(XATTR_FINDERINFO_NAME); | |
1515 | strcpy((char*)buf, XATTR_FINDERINFO_NAME); | |
1516 | } | |
1517 | } | |
1518 | ||
1519 | /* Bail if we don't have any extended attributes. */ | |
1520 | if ((hfsmp->hfs_attribute_vp == NULL) || | |
1521 | (cp->c_attr.ca_recflags & kHFSHasAttributesMask) == 0) { | |
1522 | result = 0; | |
1523 | goto exit; | |
1524 | } | |
1525 | btfile = VTOF(hfsmp->hfs_attribute_vp); | |
1526 | ||
1527 | iterator = hfs_mallocz(sizeof(*iterator)); | |
1528 | ||
1529 | result = hfs_buildattrkey(cp->c_fileid, NULL, (HFSPlusAttrKey *)&iterator->key); | |
1530 | if (result) { | |
1531 | goto exit; | |
1532 | } | |
1533 | ||
1534 | lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK); | |
1535 | ||
1536 | result = BTSearchRecord(btfile, iterator, NULL, NULL, NULL); | |
1537 | if (result && result != btNotFound) { | |
1538 | hfs_systemfile_unlock(hfsmp, lockflags); | |
1539 | goto exit; | |
1540 | } | |
1541 | ||
1542 | state.fileID = cp->c_fileid; | |
1543 | state.result = 0; | |
1544 | state.buf = (buf == NULL ? NULL : ((u_int8_t*)buf + *actual_size)); | |
1545 | state.bufsize = bufsize - *actual_size; | |
1546 | state.size = 0; | |
1547 | ||
1548 | /* | |
1549 | * Process entries starting just after iterator->key. | |
1550 | */ | |
1551 | result = BTIterateRecords(btfile, kBTreeNextRecord, iterator, | |
1552 | (IterateCallBackProcPtr)listattr_callback, &state); | |
1553 | hfs_systemfile_unlock(hfsmp, lockflags); | |
1554 | ||
1555 | *actual_size += state.size; | |
1556 | ||
1557 | if (state.result || result == btNotFound) { | |
1558 | result = state.result; | |
1559 | } | |
1560 | ||
1561 | exit: | |
1562 | hfs_free(iterator); | |
1563 | hfs_unlock(cp); | |
1564 | hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT); | |
1565 | ||
1566 | return MacToVFSError(result); | |
1567 | } | |
1568 | ||
1569 | /* | |
1570 | * Callback - called for each attribute record | |
1571 | */ | |
1572 | static int | |
1573 | listattr_callback(const HFSPlusAttrKey *key, __unused const HFSPlusAttrData *data, struct listattr_callback_state *state) | |
1574 | { | |
1575 | char attrname[XATTR_MAXNAMELEN + 1]; | |
1576 | ssize_t bytecount; | |
1577 | int result; | |
1578 | ||
1579 | if (state->fileID != key->fileID) { | |
1580 | state->result = 0; | |
1581 | return (0); /* stop */ | |
1582 | } | |
1583 | /* | |
1584 | * Skip over non-primary keys | |
1585 | */ | |
1586 | if (key->startBlock != 0) { | |
1587 | return (1); /* continue */ | |
1588 | } | |
1589 | ||
1590 | /* Convert the attribute name into UTF-8. */ | |
1591 | result = utf8_encodestr(key->attrName, key->attrNameLen * sizeof(UniChar), | |
1592 | (u_int8_t *)attrname, (size_t *)&bytecount, sizeof(attrname), '/', UTF_ADD_NULL_TERM); | |
1593 | if (result) { | |
1594 | state->result = result; | |
1595 | return (0); /* stop */ | |
1596 | } | |
1597 | bytecount++; /* account for null termination char */ | |
1598 | ||
1599 | state->size += bytecount; | |
1600 | ||
1601 | if (state->buf != NULL) { | |
1602 | if ((size_t)bytecount > state->bufsize) { | |
1603 | state->result = ERANGE; | |
1604 | return (0); /* stop */ | |
1605 | } | |
1606 | ||
1607 | memcpy(state->buf, attrname, bytecount); | |
1608 | ||
1609 | state->buf = (state->buf == NULL ? NULL : ((u_int8_t*)state->buf + bytecount)); | |
1610 | state->bufsize -= bytecount; | |
1611 | } | |
1612 | return (1); /* continue */ | |
1613 | } | |
1614 | ||
1615 | /* | |
1616 | * Remove all the attributes from a cnode. | |
1617 | * | |
1618 | * This function creates/ends its own transaction so that each | |
1619 | * attribute is deleted in its own transaction (to avoid having | |
1620 | * a transaction grow too large). | |
1621 | * | |
1622 | * This function takes the necessary locks on the attribute | |
1623 | * b-tree file and the allocation (bitmap) file. | |
1624 | * | |
1625 | * NOTE: Upon sucecss, this function will return with an open | |
1626 | * transaction. The reason we do it this way is because when we | |
1627 | * delete the last attribute, we must make sure the flag in the | |
1628 | * catalog record that indicates there are no more records is cleared. | |
1629 | * The caller is responsible for doing this and *must* do it before | |
1630 | * ending the transaction. | |
1631 | */ | |
1632 | int | |
1633 | hfs_removeallattr(struct hfsmount *hfsmp, u_int32_t fileid, bool *open_transaction) | |
1634 | { | |
1635 | BTreeIterator *iterator = NULL; | |
1636 | HFSPlusAttrKey *key; | |
1637 | struct filefork *btfile; | |
1638 | int result, lockflags = 0; | |
1639 | ||
1640 | *open_transaction = false; | |
1641 | ||
1642 | if (hfsmp->hfs_attribute_vp == NULL) | |
1643 | return 0; | |
1644 | ||
1645 | btfile = VTOF(hfsmp->hfs_attribute_vp); | |
1646 | ||
1647 | iterator = hfs_mallocz(sizeof(BTreeIterator)); | |
1648 | if (iterator == NULL) | |
1649 | return ENOMEM; | |
1650 | ||
1651 | key = (HFSPlusAttrKey *)&iterator->key; | |
1652 | ||
1653 | /* Loop until there are no more attributes for this file id */ | |
1654 | do { | |
1655 | if (!*open_transaction) | |
1656 | lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK); | |
1657 | ||
1658 | (void) hfs_buildattrkey(fileid, NULL, key); | |
1659 | result = BTIterateRecord(btfile, kBTreeNextRecord, iterator, NULL, NULL); | |
1660 | if (result || key->fileID != fileid) | |
1661 | goto exit; | |
1662 | ||
1663 | hfs_systemfile_unlock(hfsmp, lockflags); | |
1664 | lockflags = 0; | |
1665 | ||
1666 | if (*open_transaction) { | |
1667 | hfs_end_transaction(hfsmp); | |
1668 | *open_transaction = false; | |
1669 | } | |
1670 | ||
1671 | if (hfs_start_transaction(hfsmp) != 0) { | |
1672 | result = EINVAL; | |
1673 | goto exit; | |
1674 | } | |
1675 | ||
1676 | *open_transaction = true; | |
1677 | ||
1678 | lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE | SFL_BITMAP, HFS_EXCLUSIVE_LOCK); | |
1679 | ||
1680 | result = remove_attribute_records(hfsmp, iterator); | |
1681 | ||
1682 | } while (!result); | |
1683 | ||
1684 | exit: | |
1685 | hfs_free(iterator); | |
1686 | ||
1687 | if (lockflags) | |
1688 | hfs_systemfile_unlock(hfsmp, lockflags); | |
1689 | ||
1690 | result = result == btNotFound ? 0 : MacToVFSError(result); | |
1691 | ||
1692 | if (result && *open_transaction) { | |
1693 | hfs_end_transaction(hfsmp); | |
1694 | *open_transaction = false; | |
1695 | } | |
1696 | ||
1697 | return result; | |
1698 | } | |
1699 | ||
1700 | /* | |
1701 | * hfs_attrkeycompare - compare two attribute b-tree keys. | |
1702 | * | |
1703 | * The name portion of the key is compared using a 16-bit binary comparison. | |
1704 | * This is called from the b-tree code. | |
1705 | */ | |
1706 | int | |
1707 | hfs_attrkeycompare(HFSPlusAttrKey *searchKey, HFSPlusAttrKey *trialKey) | |
1708 | { | |
1709 | u_int32_t searchFileID, trialFileID; | |
1710 | int result; | |
1711 | ||
1712 | searchFileID = searchKey->fileID; | |
1713 | trialFileID = trialKey->fileID; | |
1714 | result = 0; | |
1715 | ||
1716 | if (searchFileID > trialFileID) { | |
1717 | ++result; | |
1718 | } else if (searchFileID < trialFileID) { | |
1719 | --result; | |
1720 | } else { | |
1721 | u_int16_t * str1 = &searchKey->attrName[0]; | |
1722 | u_int16_t * str2 = &trialKey->attrName[0]; | |
1723 | int length1 = searchKey->attrNameLen; | |
1724 | int length2 = trialKey->attrNameLen; | |
1725 | u_int16_t c1, c2; | |
1726 | int length; | |
1727 | ||
1728 | if (length1 < length2) { | |
1729 | length = length1; | |
1730 | --result; | |
1731 | } else if (length1 > length2) { | |
1732 | length = length2; | |
1733 | ++result; | |
1734 | } else { | |
1735 | length = length1; | |
1736 | } | |
1737 | ||
1738 | while (length--) { | |
1739 | c1 = *(str1++); | |
1740 | c2 = *(str2++); | |
1741 | ||
1742 | if (c1 > c2) { | |
1743 | result = 1; | |
1744 | break; | |
1745 | } | |
1746 | if (c1 < c2) { | |
1747 | result = -1; | |
1748 | break; | |
1749 | } | |
1750 | } | |
1751 | if (result) | |
1752 | return (result); | |
1753 | /* | |
1754 | * Names are equal; compare startBlock | |
1755 | */ | |
1756 | if (searchKey->startBlock == trialKey->startBlock) { | |
1757 | return (0); | |
1758 | } else { | |
1759 | return (searchKey->startBlock < trialKey->startBlock ? -1 : 1); | |
1760 | } | |
1761 | } | |
1762 | ||
1763 | return result; | |
1764 | } | |
1765 | ||
1766 | /* | |
1767 | * hfs_buildattrkey - build an Attribute b-tree key | |
1768 | */ | |
1769 | int | |
1770 | hfs_buildattrkey(u_int32_t fileID, const char *attrname, HFSPlusAttrKey *key) | |
1771 | { | |
1772 | int result = 0; | |
1773 | size_t unicodeBytes = 0; | |
1774 | ||
1775 | if (attrname != NULL) { | |
1776 | /* | |
1777 | * Convert filename from UTF-8 into Unicode | |
1778 | */ | |
1779 | result = utf8_decodestr((const u_int8_t *)attrname, strlen(attrname), key->attrName, | |
1780 | &unicodeBytes, sizeof(key->attrName), 0, 0); | |
1781 | if (result) { | |
1782 | if (result != ENAMETOOLONG) | |
1783 | result = EINVAL; /* name has invalid characters */ | |
1784 | return (result); | |
1785 | } | |
1786 | key->attrNameLen = unicodeBytes / sizeof(UniChar); | |
1787 | key->keyLength = kHFSPlusAttrKeyMinimumLength + unicodeBytes; | |
1788 | } else { | |
1789 | key->attrNameLen = 0; | |
1790 | key->keyLength = kHFSPlusAttrKeyMinimumLength; | |
1791 | } | |
1792 | key->pad = 0; | |
1793 | key->fileID = fileID; | |
1794 | key->startBlock = 0; | |
1795 | ||
1796 | return (0); | |
1797 | } | |
1798 | ||
1799 | /* | |
1800 | * getnodecount - calculate starting node count for attributes b-tree. | |
1801 | */ | |
1802 | static int | |
1803 | getnodecount(struct hfsmount *hfsmp, size_t nodesize) | |
1804 | { | |
1805 | u_int64_t freebytes; | |
1806 | u_int64_t calcbytes; | |
1807 | ||
1808 | /* | |
1809 | * 10.4: Scale base on current catalog file size (20 %) up to 20 MB. | |
1810 | * 10.5: Attempt to be as big as the catalog clump size. | |
1811 | * | |
1812 | * Use no more than 10 % of the remaining free space. | |
1813 | */ | |
1814 | freebytes = (u_int64_t)hfs_freeblks(hfsmp, 0) * (u_int64_t)hfsmp->blockSize; | |
1815 | ||
1816 | calcbytes = MIN(hfsmp->hfs_catalog_cp->c_datafork->ff_size / 5, 20 * 1024 * 1024); | |
1817 | ||
1818 | calcbytes = MAX(calcbytes, hfsmp->hfs_catalog_cp->c_datafork->ff_clumpsize); | |
1819 | ||
1820 | calcbytes = MIN(calcbytes, freebytes / 10); | |
1821 | ||
1822 | return (MAX(2, (int)(calcbytes / nodesize))); | |
1823 | } | |
1824 | ||
1825 | /* | |
1826 | * getmaxinlineattrsize - calculate maximum inline attribute size. | |
1827 | * | |
1828 | * This yields 3,802 bytes for an 8K node size. | |
1829 | */ | |
1830 | static size_t | |
1831 | getmaxinlineattrsize(struct vnode * attrvp) | |
1832 | { | |
1833 | BTreeInfoRec btinfo; | |
1834 | size_t nodesize = ATTRIBUTE_FILE_NODE_SIZE; | |
1835 | size_t maxsize; | |
1836 | ||
1837 | if (attrvp != NULL) { | |
1838 | (void) hfs_lock(VTOC(attrvp), HFS_SHARED_LOCK, HFS_LOCK_DEFAULT); | |
1839 | if (BTGetInformation(VTOF(attrvp), 0, &btinfo) == 0) | |
1840 | nodesize = btinfo.nodeSize; | |
1841 | hfs_unlock(VTOC(attrvp)); | |
1842 | } | |
1843 | maxsize = nodesize; | |
1844 | maxsize -= sizeof(BTNodeDescriptor); /* minus node descriptor */ | |
1845 | maxsize -= 3 * sizeof(u_int16_t); /* minus 3 index slots */ | |
1846 | maxsize /= 2; /* 2 key/rec pairs minumum */ | |
1847 | maxsize -= sizeof(HFSPlusAttrKey); /* minus maximum key size */ | |
1848 | maxsize -= sizeof(HFSPlusAttrData) - 2; /* minus data header */ | |
1849 | maxsize &= 0xFFFFFFFE; /* multiple of 2 bytes */ | |
1850 | ||
1851 | return (maxsize); | |
1852 | } |