1 /* Copyright © 2017-2018 Apple Inc. All rights reserved.
6 * Created by Or Haimovich on 28/3/18.
11 #include <sys/kauth.h>
12 #include "lf_hfs_xattr.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"
24 #define ATTRIBUTE_FILE_NODE_SIZE 8192
26 //#define HFS_XATTR_VERBOSE 1
28 /* State information for the listattr_callback callback function. */
29 struct listattr_callback_state
{
37 static u_int32_t emptyfinfo
[8] = {0};
40 static int hfs_zero_hidden_fields (struct cnode
*cp
, u_int8_t
*finderinfo
);
42 const char hfs_attrdatafilename
[] = "Attribute Data";
44 static int listattr_callback(const HFSPlusAttrKey
*key
, const HFSPlusAttrData
*data
,
45 struct listattr_callback_state
*state
);
47 static int remove_attribute_records(struct hfsmount
*hfsmp
, BTreeIterator
* iterator
);
49 static int getnodecount(struct hfsmount
*hfsmp
, size_t nodesize
);
51 static size_t getmaxinlineattrsize(struct vnode
* attrvp
);
53 static int read_attr_data(struct hfsmount
*hfsmp
, void * buf
, size_t datasize
, HFSPlusExtentDescriptor
*extents
);
55 static int write_attr_data(struct hfsmount
*hfsmp
, void * buf
, size_t datasize
, HFSPlusExtentDescriptor
*extents
);
57 static int alloc_attr_blks(struct hfsmount
*hfsmp
, size_t attrsize
, size_t extentbufsize
, HFSPlusExtentDescriptor
*extents
, int *blocks
);
59 static void free_attr_blks(struct hfsmount
*hfsmp
, int blkcnt
, HFSPlusExtentDescriptor
*extents
);
61 static int has_overflow_extents(HFSPlusForkData
*forkdata
);
63 static int count_extent_blocks(int maxblks
, HFSPlusExtentRecord extents
);
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
)
69 u_int8_t
*finfo
= finderinfo
;
71 /* Advance finfo by 16 bytes to the 2nd half of the finderinfo */
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;
92 * Retrieve the data of an extended attribute.
95 hfs_vnop_getxattr(vnode_t vp
, const char *attr_name
, void *buf
, size_t bufsize
, size_t *actual_size
)
98 struct hfsmount
*hfsmp
;
101 if (attr_name
== NULL
|| attr_name
[0] == '\0') {
102 return (EINVAL
); /* invalid name */
104 if (strlen(attr_name
) > XATTR_MAXNAMELEN
) {
105 return (ENAMETOOLONG
);
107 if (actual_size
== NULL
) {
110 if (VNODE_IS_RSRC(vp
)) {
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;
121 if ((result
= hfs_lock(cp
, HFS_SHARED_LOCK
, HFS_LOCK_DEFAULT
))) {
124 /* Make a copy since we may not export all of it. */
125 bcopy(cp
->c_finderinfo
, finderinfo
, sizeof(finderinfo
));
128 /* Zero out the date added field in the local copy */
129 hfs_zero_hidden_fields (cp
, finderinfo
);
131 /* Don't expose a symlink's private type/creator. */
132 if (vnode_islnk(vp
)) {
133 struct FndrFileInfo
*fip
;
135 fip
= (struct FndrFileInfo
*)&finderinfo
;
139 /* If Finder Info is empty then it doesn't exist. */
140 if (bcmp(finderinfo
, emptyfinfo
, sizeof(emptyfinfo
)) == 0) {
143 *actual_size
= attrsize
;
148 if (bufsize
< attrsize
)
151 memcpy(buf
, (caddr_t
)&finderinfo
, attrsize
);
155 /* Read the Resource Fork. */
156 if (strcmp(attr_name
, XATTR_RESOURCEFORK_NAME
) == 0) {
161 if ((result
= hfs_lock(cp
, HFS_SHARED_LOCK
, HFS_LOCK_DEFAULT
))) {
165 /* Check for non-rsrc, non-finderinfo EAs - getxattr_internal */
167 struct filefork
*btfile
;
168 BTreeIterator
* iterator
= NULL
;
170 HFSPlusAttrRecord
*recp
= NULL
;
171 size_t recp_size
= 0;
172 FSBufferDescriptor btdata
;
174 u_int16_t datasize
= 0;
175 u_int32_t target_id
= 0;
178 target_id
= cp
->c_fileid
;
180 target_id
= kHFSRootParentID
;
183 /* Bail if we don't have an EA B-Tree. */
184 if ((hfsmp
->hfs_attribute_vp
== NULL
) ||
185 ((cp
) && (cp
->c_attr
.ca_recflags
& kHFSHasAttributesMask
) == 0)) {
190 /* Initialize the B-Tree iterator for searching for the proper EA */
191 btfile
= VTOF(hfsmp
->hfs_attribute_vp
);
193 iterator
= hfs_mallocz(sizeof(*iterator
));
195 /* Allocate memory for reading in the attribute record. This buffer is
196 * big enough to read in all types of attribute records. It is not big
197 * enough to read inline attribute data which is read in later.
199 recp
= hfs_malloc(recp_size
= sizeof(HFSPlusAttrRecord
));
200 btdata
.bufferAddress
= recp
;
201 btdata
.itemSize
= sizeof(HFSPlusAttrRecord
);
202 btdata
.itemCount
= 1;
204 result
= hfs_buildattrkey(target_id
, attr_name
, (HFSPlusAttrKey
*)&iterator
->key
);
209 /* Lookup the attribute in the Attribute B-Tree */
210 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
, HFS_SHARED_LOCK
);
211 result
= BTSearchRecord(btfile
, iterator
, &btdata
, &datasize
, NULL
);
212 hfs_systemfile_unlock(hfsmp
, lockflags
);
215 if (result
== btNotFound
) {
222 * Operate differently if we have inline EAs that can fit in the attribute B-Tree or if
223 * we have extent based EAs.
225 switch (recp
->recordType
) {
227 /* Attribute fits in the Attribute B-Tree */
228 case kHFSPlusAttrInlineData
: {
230 * Sanity check record size. It's not required to have any
231 * user data, so the minimum size is 2 bytes less that the
232 * size of HFSPlusAttrData (since HFSPlusAttrData struct
233 * has 2 bytes set aside for attribute data).
235 if (datasize
< (sizeof(HFSPlusAttrData
) - 2)) {
236 LFHFS_LOG(LEVEL_DEBUG
, "hfs_getxattr: vol=%s %d,%s invalid record size %d (expecting %lu)\n",
237 hfsmp
->vcbVN
, target_id
, attr_name
, datasize
, sizeof(HFSPlusAttrData
));
241 *actual_size
= recp
->attrData
.attrSize
;
242 if (buf
&& recp
->attrData
.attrSize
!= 0) {
243 if (*actual_size
> bufsize
) {
244 /* User provided buffer is not large enough for the xattr data */
247 /* Previous BTreeSearchRecord() read in only the attribute record,
248 * and not the attribute data. Now allocate enough memory for
249 * both attribute record and data, and read the attribute record again.
251 attrsize
= sizeof(HFSPlusAttrData
) - 2 + recp
->attrData
.attrSize
;
253 recp
= hfs_malloc(recp_size
= attrsize
);
255 btdata
.bufferAddress
= recp
;
256 btdata
.itemSize
= attrsize
;
257 btdata
.itemCount
= 1;
259 bzero(iterator
, sizeof(*iterator
));
260 result
= hfs_buildattrkey(target_id
, attr_name
, (HFSPlusAttrKey
*)&iterator
->key
);
265 /* Lookup the attribute record and inline data */
266 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
, HFS_SHARED_LOCK
);
267 result
= BTSearchRecord(btfile
, iterator
, &btdata
, &datasize
, NULL
);
268 hfs_systemfile_unlock(hfsmp
, lockflags
);
270 if (result
== btNotFound
) {
276 /* Copy-out the attribute data to the user buffer */
277 *actual_size
= recp
->attrData
.attrSize
;
278 memcpy(buf
, (caddr_t
) &recp
->attrData
.attrData
, recp
->attrData
.attrSize
);
284 /* Extent-Based EAs */
285 case kHFSPlusAttrForkData
: {
286 if (datasize
< sizeof(HFSPlusAttrForkData
)) {
287 LFHFS_LOG(LEVEL_DEBUG
, "hfs_getxattr: vol=%s %d,%s invalid record size %d (expecting %lu)\n",
288 hfsmp
->vcbVN
, target_id
, attr_name
, datasize
, sizeof(HFSPlusAttrForkData
));
292 *actual_size
= recp
->forkData
.theFork
.logicalSize
;
296 if (*actual_size
> bufsize
) {
300 /* Process overflow extents if necessary. */
301 if (has_overflow_extents(&recp
->forkData
.theFork
)) {
302 HFSPlusExtentDescriptor
*extentbuf
;
303 HFSPlusExtentDescriptor
*extentptr
;
304 size_t extentbufsize
;
305 u_int32_t totalblocks
;
309 totalblocks
= recp
->forkData
.theFork
.totalBlocks
;
310 /* Ignore bogus block counts. */
311 if (totalblocks
> howmany(HFS_XATTR_MAXSIZE
, hfsmp
->blockSize
)) {
315 attrlen
= recp
->forkData
.theFork
.logicalSize
;
317 /* Get a buffer to hold the worst case amount of extents. */
318 extentbufsize
= totalblocks
* sizeof(HFSPlusExtentDescriptor
);
319 extentbufsize
= roundup(extentbufsize
, sizeof(HFSPlusExtentRecord
));
320 extentbuf
= hfs_mallocz(extentbufsize
);
321 extentptr
= extentbuf
;
323 /* Grab the first 8 extents. */
324 bcopy(&recp
->forkData
.theFork
.extents
[0], extentptr
, sizeof(HFSPlusExtentRecord
));
325 extentptr
+= kHFSPlusExtentDensity
;
326 blkcnt
= count_extent_blocks(totalblocks
, recp
->forkData
.theFork
.extents
);
328 /* Now lookup the overflow extents. */
329 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
, HFS_SHARED_LOCK
);
330 while (blkcnt
< totalblocks
) {
331 ((HFSPlusAttrKey
*)&iterator
->key
)->startBlock
= blkcnt
;
332 result
= BTSearchRecord(btfile
, iterator
, &btdata
, &datasize
, NULL
);
334 (recp
->recordType
!= kHFSPlusAttrExtents
) ||
335 (datasize
< sizeof(HFSPlusAttrExtents
))) {
336 LFHFS_LOG(LEVEL_DEBUG
, "hfs_getxattr: %s missing extents, only %d blks of %d found\n",
337 attr_name
, blkcnt
, totalblocks
);
338 break; /* break from while */
340 /* Grab the next 8 extents. */
341 bcopy(&recp
->overflowExtents
.extents
[0], extentptr
, sizeof(HFSPlusExtentRecord
));
342 extentptr
+= kHFSPlusExtentDensity
;
343 blkcnt
+= count_extent_blocks(totalblocks
, recp
->overflowExtents
.extents
);
346 /* Release Attr B-Tree lock */
347 hfs_systemfile_unlock(hfsmp
, lockflags
);
349 if (blkcnt
< totalblocks
) {
352 result
= read_attr_data(hfsmp
, buf
, attrlen
, extentbuf
);
356 } else { /* No overflow extents. */
357 result
= read_attr_data(hfsmp
, buf
, recp
->forkData
.theFork
.logicalSize
, recp
->forkData
.theFork
.extents
);
363 /* We only support inline EAs. Default to ENOATTR for anything else */
373 return MacToVFSError(result
);
377 * Set the data of an extended attribute.
380 hfs_vnop_setxattr(vnode_t vp
, const char *attr_name
, const void *buf
, size_t bufsize
, UVFSXattrHow option
)
382 struct cnode
*cp
= NULL
;
383 struct hfsmount
*hfsmp
;
387 if (attr_name
== NULL
|| attr_name
[0] == '\0') {
388 return (EINVAL
); /* invalid name */
390 if (strlen(attr_name
) > XATTR_MAXNAMELEN
) {
391 return (ENAMETOOLONG
);
396 if (VNODE_IS_RSRC(vp
)) {
402 /* Set the Finder Info. */
403 if (strcmp(attr_name
, XATTR_FINDERINFO_NAME
) == 0) {
407 struct FndrFileInfo info
;
409 void * finderinfo_start
;
410 u_int8_t
*finfo
= NULL
;
412 u_int32_t dateadded
= 0;
413 u_int32_t write_gen_counter
= 0;
414 u_int32_t document_id
= 0;
416 attrsize
= sizeof(VTOC(vp
)->c_finderinfo
);
418 if (bufsize
!= attrsize
) {
421 /* Grab the new Finder Info data. */
422 memcpy(fi
.cdata
, buf
, attrsize
);
424 if ((result
= hfs_lock(VTOC(vp
), HFS_EXCLUSIVE_LOCK
, HFS_LOCK_DEFAULT
))) {
429 /* Symlink's don't have an external type/creator. */
430 if (vnode_islnk(vp
)) {
431 /* Skip over type/creator fields. */
432 finderinfo_start
= &cp
->c_finderinfo
[8];
435 finderinfo_start
= &cp
->c_finderinfo
[0];
437 * Don't allow the external setting of
438 * file type to kHardLinkFileType.
440 if (fi
.info
.fdType
== SWAP_BE32(kHardLinkFileType
)) {
446 /* Grab the current date added from the cnode */
447 dateadded
= hfs_get_dateadded (cp
);
448 if (S_ISREG(cp
->c_attr
.ca_mode
) || S_ISLNK(cp
->c_attr
.ca_mode
)) {
449 struct FndrExtendedFileInfo
*extinfo
= (struct FndrExtendedFileInfo
*)((u_int8_t
*)cp
->c_finderinfo
+ 16);
451 * Grab generation counter directly from the cnode
452 * instead of calling hfs_get_gencount(), because
453 * for zero generation count values hfs_get_gencount()
454 * lies and bumps it up to one.
456 write_gen_counter
= extinfo
->write_gen_counter
;
457 document_id
= extinfo
->document_id
;
458 } else if (S_ISDIR(cp
->c_attr
.ca_mode
)) {
459 struct FndrExtendedDirInfo
*extinfo
= (struct FndrExtendedDirInfo
*)((u_int8_t
*)cp
->c_finderinfo
+ 16);
460 write_gen_counter
= extinfo
->write_gen_counter
;
461 document_id
= extinfo
->document_id
;
465 * Zero out the finder info's reserved fields like date added,
466 * generation counter, and document id to ignore user's attempts
469 hfs_zero_hidden_fields(cp
, fi
.data
);
471 if (bcmp(finderinfo_start
, emptyfinfo
, attrsize
)) {
472 /* attr exists and "create" was specified. */
473 if (option
== UVFSXattrHowCreate
) {
478 /* attr doesn't exists and "replace" was specified. */
479 if (option
== UVFSXattrHowReplace
) {
486 * Now restore the date added and other reserved fields to the finderinfo to
487 * be written out. Advance to the 2nd half of the finderinfo to write them
488 * out into the buffer.
490 * Make sure to endian swap the date added back into big endian. When we used
491 * hfs_get_dateadded above to retrieve it, it swapped into local endianness
492 * for us. But now that we're writing it out, put it back into big endian.
494 finfo
= &fi
.data
[16];
495 if (S_ISREG(cp
->c_attr
.ca_mode
) || S_ISLNK(cp
->c_attr
.ca_mode
)) {
496 struct FndrExtendedFileInfo
*extinfo
= (struct FndrExtendedFileInfo
*)finfo
;
497 extinfo
->date_added
= OSSwapHostToBigInt32(dateadded
);
498 extinfo
->write_gen_counter
= write_gen_counter
;
499 extinfo
->document_id
= document_id
;
500 } else if (S_ISDIR(cp
->c_attr
.ca_mode
)) {
501 struct FndrExtendedDirInfo
*extinfo
= (struct FndrExtendedDirInfo
*)finfo
;
502 extinfo
->date_added
= OSSwapHostToBigInt32(dateadded
);
503 extinfo
->write_gen_counter
= write_gen_counter
;
504 extinfo
->document_id
= document_id
;
507 /* Set the cnode's Finder Info. */
508 if (attrsize
== sizeof(cp
->c_finderinfo
)) {
509 bcopy(&fi
.data
[0], finderinfo_start
, attrsize
);
511 bcopy(&fi
.data
[8], finderinfo_start
, attrsize
);
514 /* Updating finderInfo updates change time and modified time */
515 cp
->c_touch_chgtime
= TRUE
;
516 cp
->c_flag
|= C_MODIFIED
;
519 * Mirror the invisible bit to the UF_HIDDEN flag.
521 * The fdFlags for files and frFlags for folders are both 8 bytes
522 * into the userInfo (the first 16 bytes of the Finder Info). They
523 * are both 16-bit fields.
525 fdFlags
= *((u_int16_t
*) &cp
->c_finderinfo
[8]);
526 if (fdFlags
& OSSwapHostToBigConstInt16(kFinderInvisibleMask
)) {
527 cp
->c_bsdflags
|= UF_HIDDEN
;
529 cp
->c_bsdflags
&= ~UF_HIDDEN
;
532 result
= hfs_update(vp
, 0);
538 /* Write the Resource Fork. */
539 if (strcmp(attr_name
, XATTR_RESOURCEFORK_NAME
) == 0) {
545 result
= hfs_lock(VTOC(vp
), HFS_EXCLUSIVE_LOCK
, HFS_LOCK_DEFAULT
);
552 * If we're trying to set a non-finderinfo, non-resourcefork EA, then
553 * call the breakout function - hfs_setxattr_internal.
555 int started_transaction
= 0;
556 BTreeIterator
* iterator
= NULL
;
557 struct filefork
*btfile
= NULL
;
558 FSBufferDescriptor btdata
;
559 HFSPlusAttrRecord attrdata
; /* 90 bytes */
560 HFSPlusAttrRecord
*recp
= NULL
;
561 size_t recp_size
= 0;
562 HFSPlusExtentDescriptor
*extentptr
= NULL
;
563 size_t extentbufsize
= 0;
566 int allocatedblks
= 0;
570 target_id
= cp
->c_fileid
;
572 target_id
= kHFSRootParentID
;
575 /* Start a transaction for our changes. */
576 if (hfs_start_transaction(hfsmp
) != 0) {
580 started_transaction
= 1;
583 * Once we started the transaction, nobody can compete
584 * with us, so make sure this file is still there.
586 if ((cp
) && (cp
->c_flag
& C_NOEXISTS
)) {
592 * If there isn't an attributes b-tree then create one.
594 if (hfsmp
->hfs_attribute_vp
== NULL
) {
595 result
= hfs_create_attr_btree(hfsmp
, ATTRIBUTE_FILE_NODE_SIZE
,
596 getnodecount(hfsmp
, ATTRIBUTE_FILE_NODE_SIZE
));
601 if (hfsmp
->hfs_max_inline_attrsize
== 0) {
602 hfsmp
->hfs_max_inline_attrsize
= getmaxinlineattrsize(hfsmp
->hfs_attribute_vp
);
605 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
, HFS_EXCLUSIVE_LOCK
);
607 /* Build the b-tree key. */
608 iterator
= hfs_mallocz(sizeof(*iterator
));
609 result
= hfs_buildattrkey(target_id
, attr_name
, (HFSPlusAttrKey
*)&iterator
->key
);
614 /* Preflight for replace/create semantics. */
615 btfile
= VTOF(hfsmp
->hfs_attribute_vp
);
616 btdata
.bufferAddress
= &attrdata
;
617 btdata
.itemSize
= sizeof(attrdata
);
618 btdata
.itemCount
= 1;
619 exists
= BTSearchRecord(btfile
, iterator
, &btdata
, NULL
, NULL
) == 0;
621 /* Replace requires that the attribute already exists. */
622 if ((option
== UVFSXattrHowReplace
) && !exists
) {
626 /* Create requires that the attribute doesn't exist. */
627 if ((option
== UVFSXattrHowCreate
) && exists
) {
632 /* Enforce an upper limit. */
633 if (attrsize
> HFS_XATTR_MAXSIZE
) {
638 /* If it won't fit inline then use extent-based attributes. */
639 if (attrsize
> hfsmp
->hfs_max_inline_attrsize
) {
642 u_int32_t
*keystartblk
;
645 /* Get some blocks. */
646 blkcnt
= (int)howmany(attrsize
, hfsmp
->blockSize
);
647 extentbufsize
= blkcnt
* sizeof(HFSPlusExtentDescriptor
);
648 extentbufsize
= roundup(extentbufsize
, sizeof(HFSPlusExtentRecord
));
649 extentptr
= hfs_mallocz(extentbufsize
);
650 result
= alloc_attr_blks(hfsmp
, attrsize
, extentbufsize
, extentptr
, &allocatedblks
);
653 goto exit_lock
; /* no more space */
655 /* Copy data into the blocks. */
656 result
= write_attr_data(hfsmp
, (void*)buf
, attrsize
, extentptr
);
659 LFHFS_LOG(LEVEL_DEBUG
, "hfs_setxattr: write_attr_data vol=%s err (%d) :%s\n",
660 hfsmp
->vcbVN
, result
, attr_name
);
665 /* Now remove any previous attribute. */
667 result
= remove_attribute_records(hfsmp
, iterator
);
670 LFHFS_LOG(LEVEL_DEBUG
, "hfs_setxattr: remove_attribute_records vol=%s err (%d) %s:%s\n",
671 hfsmp
->vcbVN
, result
, "", attr_name
);
676 /* Create attribute fork data record. */
677 recp
= hfs_malloc(recp_size
= sizeof(HFSPlusAttrRecord
));
679 btdata
.bufferAddress
= recp
;
680 btdata
.itemCount
= 1;
681 btdata
.itemSize
= sizeof(HFSPlusAttrForkData
);
683 recp
->recordType
= kHFSPlusAttrForkData
;
684 recp
->forkData
.reserved
= 0;
685 recp
->forkData
.theFork
.logicalSize
= attrsize
;
686 recp
->forkData
.theFork
.clumpSize
= 0;
687 recp
->forkData
.theFork
.totalBlocks
= blkcnt
;
688 bcopy(extentptr
, recp
->forkData
.theFork
.extents
, sizeof(HFSPlusExtentRecord
));
690 (void) hfs_buildattrkey(target_id
, attr_name
, (HFSPlusAttrKey
*)&iterator
->key
);
692 result
= BTInsertRecord(btfile
, iterator
, &btdata
, btdata
.itemSize
);
694 LFHFS_LOG(LEVEL_DEBUG
, "hfs_setxattr: BTInsertRecord(): vol=%s %d,%s err=%d\n",
695 hfsmp
->vcbVN
, target_id
, attr_name
, result
);
698 extentblks
= count_extent_blocks(blkcnt
, recp
->forkData
.theFork
.extents
);
699 blkcnt
-= extentblks
;
700 keystartblk
= &((HFSPlusAttrKey
*)&iterator
->key
)->startBlock
;
703 /* Create overflow extents as needed. */
705 /* Initialize the key and record. */
706 *keystartblk
+= (u_int32_t
)extentblks
;
707 btdata
.itemSize
= sizeof(HFSPlusAttrExtents
);
708 recp
->recordType
= kHFSPlusAttrExtents
;
709 recp
->overflowExtents
.reserved
= 0;
711 /* Copy the next set of extents. */
712 i
+= kHFSPlusExtentDensity
;
713 bcopy(&extentptr
[i
], recp
->overflowExtents
.extents
, sizeof(HFSPlusExtentRecord
));
715 result
= BTInsertRecord(btfile
, iterator
, &btdata
, btdata
.itemSize
);
717 LFHFS_LOG(LEVEL_DEBUG
, "hfs_setxattr: BTInsertRecord() overflow: vol=%s %d,%s err=%d\n",
718 hfsmp
->vcbVN
, target_id
, attr_name
, result
);
721 extentblks
= count_extent_blocks(blkcnt
, recp
->overflowExtents
.extents
);
722 blkcnt
-= extentblks
;
724 } else { /* Inline data */
726 result
= remove_attribute_records(hfsmp
, iterator
);
732 /* Calculate size of record rounded up to multiple of 2 bytes. */
733 btdata
.itemSize
= sizeof(HFSPlusAttrData
) - 2 + attrsize
+ ((attrsize
& 1) ? 1 : 0);
734 recp
= hfs_malloc(recp_size
= btdata
.itemSize
);
736 recp
->recordType
= kHFSPlusAttrInlineData
;
737 recp
->attrData
.reserved
[0] = 0;
738 recp
->attrData
.reserved
[1] = 0;
739 recp
->attrData
.attrSize
= (u_int32_t
)attrsize
;
741 /* Copy in the attribute data (if any). */
743 bcopy(buf
, &recp
->attrData
.attrData
, attrsize
);
746 (void) hfs_buildattrkey(target_id
, attr_name
, (HFSPlusAttrKey
*)&iterator
->key
);
748 btdata
.bufferAddress
= recp
;
749 btdata
.itemCount
= 1;
750 result
= BTInsertRecord(btfile
, iterator
, &btdata
, btdata
.itemSize
);
754 if (btfile
&& started_transaction
) {
755 (void) BTFlushPath(btfile
);
757 hfs_systemfile_unlock(hfsmp
, lockflags
);
761 /* Setting an attribute only updates change time and not
762 * modified time of the file.
764 cp
->c_touch_chgtime
= TRUE
;
765 cp
->c_flag
|= C_MODIFIED
;
766 cp
->c_attr
.ca_recflags
|= kHFSHasAttributesMask
;
767 if ((strcmp(attr_name
, KAUTH_FILESEC_XATTR
) == 0)) {
768 cp
->c_attr
.ca_recflags
|= kHFSHasSecurityMask
;
770 (void) hfs_update(vp
, 0);
773 if (started_transaction
) {
774 if (result
&& allocatedblks
) {
775 free_attr_blks(hfsmp
, allocatedblks
, extentptr
);
777 hfs_end_transaction(hfsmp
);
789 return (result
== btNotFound
? ENOATTR
: MacToVFSError(result
));
793 * Remove an extended attribute.
796 hfs_vnop_removexattr(vnode_t vp
, const char *attr_name
)
798 struct cnode
*cp
= VTOC(vp
);
799 struct hfsmount
*hfsmp
;
800 BTreeIterator
* iterator
= NULL
;
804 if (attr_name
== NULL
|| attr_name
[0] == '\0') {
805 return (EINVAL
); /* invalid name */
808 if (VNODE_IS_RSRC(vp
)) {
812 /* Write the Resource Fork. */
813 if (strcmp(attr_name
, XATTR_RESOURCEFORK_NAME
) == 0) {
817 /* Clear out the Finder Info. */
818 if (strcmp(attr_name
, XATTR_FINDERINFO_NAME
) == 0) {
819 void * finderinfo_start
;
821 u_int8_t finderinfo
[32];
822 u_int32_t date_added
= 0, write_gen_counter
= 0, document_id
= 0;
823 u_int8_t
*finfo
= NULL
;
825 if ((result
= hfs_lock(cp
, HFS_EXCLUSIVE_LOCK
, HFS_LOCK_DEFAULT
))) {
829 /* Use the local copy to store our temporary changes. */
830 bcopy(cp
->c_finderinfo
, finderinfo
, sizeof(finderinfo
));
832 /* Zero out the date added field in the local copy */
833 hfs_zero_hidden_fields (cp
, finderinfo
);
835 /* Don't expose a symlink's private type/creator. */
836 if (vnode_islnk(vp
)) {
837 struct FndrFileInfo
*fip
;
839 fip
= (struct FndrFileInfo
*)&finderinfo
;
844 /* Do the byte compare against the local copy */
845 if (bcmp(finderinfo
, emptyfinfo
, sizeof(emptyfinfo
)) == 0) {
851 * If there was other content, zero out everything except
852 * type/creator and date added. First, save the date added.
854 finfo
= cp
->c_finderinfo
;
856 if (S_ISREG(cp
->c_attr
.ca_mode
) || S_ISLNK(cp
->c_attr
.ca_mode
)) {
857 struct FndrExtendedFileInfo
*extinfo
= (struct FndrExtendedFileInfo
*)finfo
;
858 date_added
= extinfo
->date_added
;
859 write_gen_counter
= extinfo
->write_gen_counter
;
860 document_id
= extinfo
->document_id
;
861 } else if (S_ISDIR(cp
->c_attr
.ca_mode
)) {
862 struct FndrExtendedDirInfo
*extinfo
= (struct FndrExtendedDirInfo
*)finfo
;
863 date_added
= extinfo
->date_added
;
864 write_gen_counter
= extinfo
->write_gen_counter
;
865 document_id
= extinfo
->document_id
;
868 if (vnode_islnk(vp
)) {
869 /* Ignore type/creator */
870 finderinfo_start
= &cp
->c_finderinfo
[8];
871 finderinfo_size
= sizeof(cp
->c_finderinfo
) - 8;
873 finderinfo_start
= &cp
->c_finderinfo
[0];
874 finderinfo_size
= sizeof(cp
->c_finderinfo
);
876 bzero(finderinfo_start
, finderinfo_size
);
878 /* Now restore the date added */
879 if (S_ISREG(cp
->c_attr
.ca_mode
) || S_ISLNK(cp
->c_attr
.ca_mode
)) {
880 struct FndrExtendedFileInfo
*extinfo
= (struct FndrExtendedFileInfo
*)finfo
;
881 extinfo
->date_added
= date_added
;
882 extinfo
->write_gen_counter
= write_gen_counter
;
883 extinfo
->document_id
= document_id
;
884 } else if (S_ISDIR(cp
->c_attr
.ca_mode
)) {
885 struct FndrExtendedDirInfo
*extinfo
= (struct FndrExtendedDirInfo
*)finfo
;
886 extinfo
->date_added
= date_added
;
887 extinfo
->write_gen_counter
= write_gen_counter
;
888 extinfo
->document_id
= document_id
;
891 /* Updating finderInfo updates change time and modified time */
892 cp
->c_touch_chgtime
= TRUE
;
893 cp
->c_flag
|= C_MODIFIED
;
901 if (hfsmp
->hfs_attribute_vp
== NULL
) {
905 iterator
= hfs_mallocz(sizeof(*iterator
));
907 if ((result
= hfs_lock(cp
, HFS_EXCLUSIVE_LOCK
, HFS_LOCK_DEFAULT
))) {
911 result
= hfs_buildattrkey(cp
->c_fileid
, attr_name
, (HFSPlusAttrKey
*)&iterator
->key
);
916 if (hfs_start_transaction(hfsmp
) != 0) {
920 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
| SFL_BITMAP
, HFS_EXCLUSIVE_LOCK
);
922 result
= remove_attribute_records(hfsmp
, iterator
);
924 hfs_systemfile_unlock(hfsmp
, lockflags
);
927 cp
->c_touch_chgtime
= TRUE
;
929 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
, HFS_SHARED_LOCK
);
931 /* If no more attributes exist, clear attribute bit */
932 result
= file_attribute_exist(hfsmp
, cp
->c_fileid
);
934 cp
->c_attr
.ca_recflags
&= ~kHFSHasAttributesMask
;
935 cp
->c_flag
|= C_MODIFIED
;
937 if (result
== EEXIST
) {
941 hfs_systemfile_unlock(hfsmp
, lockflags
);
943 /* If ACL was removed, clear security bit */
944 if (strcmp(attr_name
, KAUTH_FILESEC_XATTR
) == 0) {
945 cp
->c_attr
.ca_recflags
&= ~kHFSHasSecurityMask
;
946 cp
->c_flag
|= C_MODIFIED
;
948 (void) hfs_update(vp
, 0);
951 hfs_end_transaction(hfsmp
);
956 return MacToVFSError(result
);
960 * Initialize vnode for attribute data I/O.
964 * - the attrdata vnode is initialized as hfsmp->hfs_attrdata_vp
965 * - an iocount is taken on the attrdata vnode which exists
966 * for the entire duration of the mount. It is only dropped
968 * - the attrdata cnode is not locked
971 * - returns non-zero value
972 * - the caller does not have to worry about any locks or references
974 int init_attrdata_vnode(struct hfsmount
*hfsmp
)
978 struct cat_desc cat_desc
;
979 struct cat_attr cat_attr
;
980 struct cat_fork cat_fork
;
981 int newvnode_flags
= 0;
983 bzero(&cat_desc
, sizeof(cat_desc
));
984 cat_desc
.cd_parentcnid
= kHFSRootParentID
;
985 cat_desc
.cd_nameptr
= (const u_int8_t
*)hfs_attrdatafilename
;
986 cat_desc
.cd_namelen
= strlen(hfs_attrdatafilename
);
987 cat_desc
.cd_cnid
= kHFSAttributeDataFileID
;
988 /* Tag vnode as system file, note that we can still use cluster I/O */
989 cat_desc
.cd_flags
|= CD_ISMETA
;
991 bzero(&cat_attr
, sizeof(cat_attr
));
992 cat_attr
.ca_linkcount
= 1;
993 cat_attr
.ca_mode
= S_IFREG
;
994 cat_attr
.ca_fileid
= cat_desc
.cd_cnid
;
995 cat_attr
.ca_blocks
= hfsmp
->totalBlocks
;
998 * The attribute data file is a virtual file that spans the
999 * entire file system space.
1001 * Each extent-based attribute occupies a unique portion of
1002 * in this virtual file. The cluster I/O is done using actual
1003 * allocation block offsets so no additional mapping is needed
1004 * for the VNOP_BLOCKMAP call.
1006 * This approach allows the attribute data to be cached without
1007 * incurring the high cost of using a separate vnode per attribute.
1009 * Since we need to acquire the attribute b-tree file lock anyways,
1010 * the virtual file doesn't introduce any additional serialization.
1012 bzero(&cat_fork
, sizeof(cat_fork
));
1013 cat_fork
.cf_size
= (u_int64_t
)hfsmp
->totalBlocks
* (u_int64_t
)hfsmp
->blockSize
;
1014 cat_fork
.cf_blocks
= hfsmp
->totalBlocks
;
1015 cat_fork
.cf_extents
[0].startBlock
= 0;
1016 cat_fork
.cf_extents
[0].blockCount
= cat_fork
.cf_blocks
;
1018 result
= hfs_getnewvnode(hfsmp
, NULL
, NULL
, &cat_desc
, 0, &cat_attr
,
1019 &cat_fork
, &vp
, &newvnode_flags
);
1021 hfsmp
->hfs_attrdata_vp
= vp
;
1022 hfs_unlock(VTOC(vp
));
1027 /* Check if any attribute record exist for given fileID. This function
1028 * is called by hfs_vnop_removexattr to determine if it should clear the
1029 * attribute bit in the catalog record or not.
1031 * Note - you must acquire a shared lock on the attribute btree before
1032 * calling this function.
1035 * EEXIST - If attribute record was found
1036 * 0 - Attribute was not found
1037 * (other) - Other error (such as EIO)
1040 file_attribute_exist(struct hfsmount
*hfsmp
, uint32_t fileID
)
1042 HFSPlusAttrKey
*key
;
1043 BTreeIterator
* iterator
= NULL
;
1044 struct filefork
*btfile
;
1047 // if there's no attribute b-tree we sure as heck
1048 // can't have any attributes!
1049 if (hfsmp
->hfs_attribute_vp
== NULL
) {
1053 iterator
= hfs_mallocz(sizeof(BTreeIterator
));
1054 if (iterator
== NULL
) return ENOMEM
;
1056 key
= (HFSPlusAttrKey
*)&iterator
->key
;
1058 result
= hfs_buildattrkey(fileID
, NULL
, key
);
1063 btfile
= VTOF(hfsmp
->hfs_attribute_vp
);
1064 result
= BTSearchRecord(btfile
, iterator
, NULL
, NULL
, NULL
);
1065 if (result
&& (result
!= btNotFound
)) {
1069 result
= BTIterateRecord(btfile
, kBTreeNextRecord
, iterator
, NULL
, NULL
);
1070 /* If no next record was found or fileID for next record did not match,
1071 * no more attributes exist for this fileID
1073 if ((result
&& (result
== btNotFound
)) || (key
->fileID
!= fileID
)) {
1085 * Read an extent based attribute.
1088 read_attr_data(struct hfsmount
*hfsmp
, void *buf
, size_t datasize
, HFSPlusExtentDescriptor
*extents
)
1090 vnode_t evp
= hfsmp
->hfs_attrdata_vp
;
1094 uint64_t alreadyread
;
1098 hfs_lock_truncate(VTOC(evp
), HFS_SHARED_LOCK
, HFS_LOCK_DEFAULT
);
1100 attrsize
= (uint64_t)datasize
;
1101 blksize
= (uint64_t)hfsmp
->blockSize
;
1105 * Read the attribute data one extent at a time.
1106 * For the typical case there is only one extent.
1108 for (i
= 0; (attrsize
> 0) && (extents
[i
].startBlock
!= 0); ++i
) {
1109 iosize
= extents
[i
].blockCount
* blksize
;
1110 iosize
= MIN(iosize
, attrsize
);
1112 uint64_t actualread
= 0;
1114 result
= raw_readwrite_read_internal( evp
, extents
[i
].startBlock
, extents
[i
].blockCount
* blksize
,
1115 alreadyread
, iosize
, buf
, &actualread
);
1116 #if HFS_XATTR_VERBOSE
1117 LFHFS_LOG(LEVEL_DEBUG
, "hfs: read_attr_data: cr iosize %lld [%d, %d] (%d)\n",
1118 actualread
, extents
[i
].startBlock
, extents
[i
].blockCount
, result
);
1123 // read the remaining part after sector boundary if we have such
1124 if (iosize
!= actualread
)
1126 result
= raw_readwrite_read_internal( evp
, extents
[i
].startBlock
, extents
[i
].blockCount
* blksize
,
1127 alreadyread
+ actualread
, iosize
- actualread
,
1128 (uint8_t*)buf
+ actualread
, &actualread
);
1129 #if HFS_XATTR_VERBOSE
1130 LFHFS_LOG(LEVEL_DEBUG
, "hfs: read_attr_data: cr iosize %lld [%d, %d] (%d)\n",
1131 actualread
, extents
[i
].startBlock
, extents
[i
].blockCount
, result
);
1139 alreadyread
+= iosize
;
1140 buf
= (uint8_t*)buf
+ iosize
;
1143 hfs_unlock_truncate(VTOC(evp
), HFS_LOCK_DEFAULT
);
1148 * Write an extent based attribute.
1151 write_attr_data(struct hfsmount
*hfsmp
, void *buf
, size_t datasize
, HFSPlusExtentDescriptor
*extents
)
1153 vnode_t evp
= hfsmp
->hfs_attrdata_vp
;
1157 uint64_t alreadywritten
;
1161 hfs_lock_truncate(VTOC(evp
), HFS_SHARED_LOCK
, HFS_LOCK_DEFAULT
);
1163 attrsize
= (uint64_t)datasize
;
1164 blksize
= (uint64_t)hfsmp
->blockSize
;
1168 * Write the attribute data one extent at a time.
1170 for (i
= 0; (attrsize
> 0) && (extents
[i
].startBlock
!= 0); ++i
) {
1171 iosize
= extents
[i
].blockCount
* blksize
;
1172 iosize
= MIN(iosize
, attrsize
);
1174 uint64_t actualwritten
= 0;
1176 result
= raw_readwrite_write_internal( evp
, extents
[i
].startBlock
, extents
[i
].blockCount
* blksize
,
1177 alreadywritten
, iosize
, buf
, &actualwritten
);
1178 #if HFS_XATTR_VERBOSE
1179 LFHFS_LOG(LEVEL_DEBUG
, "hfs: write_attr_data: cw iosize %lld [%d, %d] (%d)\n",
1180 actualwritten
, extents
[i
].startBlock
, extents
[i
].blockCount
, result
);
1185 // write the remaining part after sector boundary if we have such
1186 if (iosize
!= actualwritten
)
1188 result
= raw_readwrite_write_internal( evp
, extents
[i
].startBlock
, extents
[i
].blockCount
* blksize
,
1189 alreadywritten
+ actualwritten
, iosize
- actualwritten
,
1190 (uint8_t*)buf
+ actualwritten
, &actualwritten
);
1191 #if HFS_XATTR_VERBOSE
1192 LFHFS_LOG(LEVEL_DEBUG
, "hfs: write_attr_data: cw iosize %lld [%d, %d] (%d)\n",
1193 actualwritten
, extents
[i
].startBlock
, extents
[i
].blockCount
, result
);
1201 alreadywritten
+= iosize
;
1202 buf
= (uint8_t*)buf
+ iosize
;
1205 hfs_unlock_truncate(VTOC(evp
), HFS_LOCK_DEFAULT
);
1210 * Allocate blocks for an extent based attribute.
1213 alloc_attr_blks(struct hfsmount
*hfsmp
, size_t attrsize
, size_t extentbufsize
, HFSPlusExtentDescriptor
*extents
, int *blocks
)
1222 startblk
= hfsmp
->hfs_metazone_end
;
1223 blkcnt
= (int)howmany(attrsize
, hfsmp
->blockSize
);
1224 if (blkcnt
> (int)hfs_freeblks(hfsmp
, 0)) {
1228 maxextents
= (int)extentbufsize
/ sizeof(HFSPlusExtentDescriptor
);
1230 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_BITMAP
, HFS_EXCLUSIVE_LOCK
);
1232 for (i
= 0; (blkcnt
> 0) && (i
< maxextents
); i
++) {
1233 /* Try allocating and see if we find something decent */
1234 result
= BlockAllocate(hfsmp
, startblk
, blkcnt
, blkcnt
, 0,
1235 &extents
[i
].startBlock
, &extents
[i
].blockCount
);
1237 * If we couldn't find anything, then re-try the allocation but allow
1240 if (result
== dskFulErr
) {
1241 result
= BlockAllocate(hfsmp
, startblk
, blkcnt
, blkcnt
, HFS_ALLOC_FLUSHTXN
,
1242 &extents
[i
].startBlock
, &extents
[i
].blockCount
);
1244 #if HFS_XATTR_VERBOSE
1245 LFHFS_LOG(LEVEL_DEBUG
,"hfs: alloc_attr_blks: BA blkcnt %d [%d, %d] (%d)\n",
1246 blkcnt
, extents
[i
].startBlock
, extents
[i
].blockCount
, result
);
1249 extents
[i
].startBlock
= 0;
1250 extents
[i
].blockCount
= 0;
1253 blkcnt
-= extents
[i
].blockCount
;
1254 startblk
= extents
[i
].startBlock
+ extents
[i
].blockCount
;
1257 * If it didn't fit in the extents buffer then bail.
1261 #if HFS_XATTR_VERBOSE
1262 LFHFS_LOG(LEVEL_DEBUG
, "hfs: alloc_attr_blks: unexpected failure, %d blocks unallocated\n", blkcnt
);
1264 for (; i
>= 0; i
--) {
1265 if ((blkcnt
= extents
[i
].blockCount
) != 0) {
1266 (void) BlockDeallocate(hfsmp
, extents
[i
].startBlock
, blkcnt
, 0);
1267 extents
[i
].startBlock
= 0;
1268 extents
[i
].blockCount
= 0;
1273 hfs_systemfile_unlock(hfsmp
, lockflags
);
1274 return MacToVFSError(result
);
1278 * Release blocks from an extent based attribute.
1281 free_attr_blks(struct hfsmount
*hfsmp
, int blkcnt
, HFSPlusExtentDescriptor
*extents
)
1283 vnode_t evp
= hfsmp
->hfs_attrdata_vp
;
1284 int remblks
= blkcnt
;
1288 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_BITMAP
, HFS_EXCLUSIVE_LOCK
);
1290 for (i
= 0; (remblks
> 0) && (extents
[i
].blockCount
!= 0); i
++) {
1291 if (extents
[i
].blockCount
> (u_int32_t
)blkcnt
) {
1292 #if HFS_XATTR_VERBOSE
1293 LFHFS_LOG(LEVEL_DEBUG
, "hfs: free_attr_blks: skipping bad extent [%d, %d]\n",
1294 extents
[i
].startBlock
, extents
[i
].blockCount
);
1296 extents
[i
].blockCount
= 0;
1299 if (extents
[i
].startBlock
== 0) {
1302 (void)BlockDeallocate(hfsmp
, extents
[i
].startBlock
, extents
[i
].blockCount
, 0);
1303 remblks
-= extents
[i
].blockCount
;
1304 #if HFS_XATTR_VERBOSE
1305 LFHFS_LOG(LEVEL_DEBUG
, "hfs: free_attr_blks: BlockDeallocate [%d, %d]\n",
1306 extents
[i
].startBlock
, extents
[i
].blockCount
);
1308 extents
[i
].startBlock
= 0;
1309 extents
[i
].blockCount
= 0;
1311 /* Discard any resident pages for this block range. */
1313 #if LF_HFS_FULL_VNODE_SUPPORT
1315 start
= (u_int64_t
)extents
[i
].startBlock
* (u_int64_t
)hfsmp
->blockSize
;
1316 end
= start
+ (u_int64_t
)extents
[i
].blockCount
* (u_int64_t
)hfsmp
->blockSize
;
1317 //TBD - Need to update this vnode
1318 (void) ubc_msync(hfsmp
->hfs_attrdata_vp
, start
, end
, &start
, UBC_INVALIDATE
);
1323 hfs_systemfile_unlock(hfsmp
, lockflags
);
1327 has_overflow_extents(HFSPlusForkData
*forkdata
)
1331 if (forkdata
->extents
[7].blockCount
== 0)
1334 blocks
= forkdata
->extents
[0].blockCount
+
1335 forkdata
->extents
[1].blockCount
+
1336 forkdata
->extents
[2].blockCount
+
1337 forkdata
->extents
[3].blockCount
+
1338 forkdata
->extents
[4].blockCount
+
1339 forkdata
->extents
[5].blockCount
+
1340 forkdata
->extents
[6].blockCount
+
1341 forkdata
->extents
[7].blockCount
;
1343 return (forkdata
->totalBlocks
> blocks
);
1347 count_extent_blocks(int maxblks
, HFSPlusExtentRecord extents
)
1352 for (i
= 0, blocks
= 0; i
< kHFSPlusExtentDensity
; ++i
) {
1353 /* Ignore obvious bogus extents. */
1354 if (extents
[i
].blockCount
> (u_int32_t
)maxblks
)
1356 if (extents
[i
].startBlock
== 0 || extents
[i
].blockCount
== 0)
1358 blocks
+= extents
[i
].blockCount
;
1364 * Remove all the records for a given attribute.
1366 * - Used by hfs_vnop_removexattr, hfs_vnop_setxattr and hfs_removeallattr.
1367 * - A transaction must have been started.
1368 * - The Attribute b-tree file must be locked exclusive.
1369 * - The Allocation Bitmap file must be locked exclusive.
1370 * - The iterator key must be initialized.
1373 remove_attribute_records(struct hfsmount
*hfsmp
, BTreeIterator
* iterator
)
1375 struct filefork
*btfile
;
1376 FSBufferDescriptor btdata
;
1377 HFSPlusAttrRecord attrdata
; /* 90 bytes */
1381 btfile
= VTOF(hfsmp
->hfs_attribute_vp
);
1383 btdata
.bufferAddress
= &attrdata
;
1384 btdata
.itemSize
= sizeof(attrdata
);
1385 btdata
.itemCount
= 1;
1386 result
= BTSearchRecord(btfile
, iterator
, &btdata
, &datasize
, NULL
);
1388 goto exit
; /* no records. */
1391 * Free the blocks from extent based attributes.
1393 * Note that the block references (btree records) are removed
1394 * before releasing the blocks in the allocation bitmap.
1396 if (attrdata
.recordType
== kHFSPlusAttrForkData
) {
1399 u_int32_t
*keystartblk
;
1401 if (datasize
< sizeof(HFSPlusAttrForkData
)) {
1402 LFHFS_LOG(LEVEL_DEBUG
, "remove_attribute_records: bad record size %d (expecting %lu)\n", datasize
, sizeof(HFSPlusAttrForkData
));
1404 totalblks
= attrdata
.forkData
.theFork
.totalBlocks
;
1406 /* Process the first 8 extents. */
1407 extentblks
= count_extent_blocks(totalblks
, attrdata
.forkData
.theFork
.extents
);
1408 if (extentblks
> totalblks
)
1410 LFHFS_LOG(LEVEL_ERROR
, "remove_attribute_records: corruption (1)...");
1413 if (BTDeleteRecord(btfile
, iterator
) == 0) {
1414 free_attr_blks(hfsmp
, extentblks
, attrdata
.forkData
.theFork
.extents
);
1416 totalblks
-= extentblks
;
1417 keystartblk
= &((HFSPlusAttrKey
*)&iterator
->key
)->startBlock
;
1419 /* Process any overflow extents. */
1421 *keystartblk
+= (u_int32_t
)extentblks
;
1423 result
= BTSearchRecord(btfile
, iterator
, &btdata
, &datasize
, NULL
);
1425 (attrdata
.recordType
!= kHFSPlusAttrExtents
) ||
1426 (datasize
< sizeof(HFSPlusAttrExtents
))) {
1427 LFHFS_LOG(LEVEL_ERROR
, "remove_attribute_records: BTSearchRecord: vol=%s, err=%d (%d), totalblks %d\n",
1428 hfsmp
->vcbVN
, MacToVFSError(result
), attrdata
.recordType
!= kHFSPlusAttrExtents
, totalblks
);
1430 break; /* break from while */
1432 /* Process the next 8 extents. */
1433 extentblks
= count_extent_blocks(totalblks
, attrdata
.overflowExtents
.extents
);
1434 if (extentblks
> totalblks
)
1436 LFHFS_LOG(LEVEL_ERROR
, "remove_attribute_records: corruption (2)...");
1439 if (BTDeleteRecord(btfile
, iterator
) == 0) {
1440 free_attr_blks(hfsmp
, extentblks
, attrdata
.overflowExtents
.extents
);
1442 totalblks
-= extentblks
;
1445 result
= BTDeleteRecord(btfile
, iterator
);
1447 (void) BTFlushPath(btfile
);
1449 return (result
== btNotFound
? ENOATTR
: MacToVFSError(result
));
1453 * Retrieve the list of extended attribute names.
1456 hfs_vnop_listxattr(vnode_t vp
, void *buf
, size_t bufsize
, size_t *actual_size
)
1458 struct cnode
*cp
= VTOC(vp
);
1459 struct hfsmount
*hfsmp
;
1460 BTreeIterator
* iterator
= NULL
;
1461 struct filefork
*btfile
;
1462 struct listattr_callback_state state
;
1465 u_int8_t finderinfo
[32];
1467 if (actual_size
== NULL
) {
1470 if (VNODE_IS_RSRC(vp
)) {
1478 * Take the truncate lock; this serializes us against the ioctl
1479 * to truncate data & reset the decmpfs state
1480 * in the compressed file handler.
1482 hfs_lock_truncate(cp
, HFS_SHARED_LOCK
, HFS_LOCK_DEFAULT
);
1484 /* Now the regular cnode lock (shared) */
1485 if ((result
= hfs_lock(cp
, HFS_SHARED_LOCK
, HFS_LOCK_DEFAULT
))) {
1486 hfs_unlock_truncate(cp
, HFS_LOCK_DEFAULT
);
1491 * Make a copy of the cnode's finderinfo to a local so we can
1492 * zero out the date added field. Also zero out the private type/creator
1495 bcopy(cp
->c_finderinfo
, finderinfo
, sizeof(finderinfo
));
1496 hfs_zero_hidden_fields (cp
, finderinfo
);
1498 /* Don't expose a symlink's private type/creator. */
1499 if (vnode_islnk(vp
)) {
1500 struct FndrFileInfo
*fip
;
1502 fip
= (struct FndrFileInfo
*)&finderinfo
;
1508 /* If Finder Info is non-empty then export it's name. */
1509 if (bcmp(finderinfo
, emptyfinfo
, sizeof(emptyfinfo
)) != 0) {
1511 *actual_size
+= sizeof(XATTR_FINDERINFO_NAME
);
1512 } else if (bufsize
< sizeof(XATTR_FINDERINFO_NAME
)) {
1516 *actual_size
+= sizeof(XATTR_FINDERINFO_NAME
);
1517 strcpy((char*)buf
, XATTR_FINDERINFO_NAME
);
1521 /* Bail if we don't have any extended attributes. */
1522 if ((hfsmp
->hfs_attribute_vp
== NULL
) ||
1523 (cp
->c_attr
.ca_recflags
& kHFSHasAttributesMask
) == 0) {
1527 btfile
= VTOF(hfsmp
->hfs_attribute_vp
);
1529 iterator
= hfs_mallocz(sizeof(*iterator
));
1531 result
= hfs_buildattrkey(cp
->c_fileid
, NULL
, (HFSPlusAttrKey
*)&iterator
->key
);
1536 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
, HFS_SHARED_LOCK
);
1538 result
= BTSearchRecord(btfile
, iterator
, NULL
, NULL
, NULL
);
1539 if (result
&& result
!= btNotFound
) {
1540 hfs_systemfile_unlock(hfsmp
, lockflags
);
1544 state
.fileID
= cp
->c_fileid
;
1546 state
.buf
= (buf
== NULL
? NULL
: ((u_int8_t
*)buf
+ *actual_size
));
1547 state
.bufsize
= bufsize
- *actual_size
;
1551 * Process entries starting just after iterator->key.
1553 result
= BTIterateRecords(btfile
, kBTreeNextRecord
, iterator
,
1554 (IterateCallBackProcPtr
)listattr_callback
, &state
);
1555 hfs_systemfile_unlock(hfsmp
, lockflags
);
1557 *actual_size
+= state
.size
;
1559 if (state
.result
|| result
== btNotFound
) {
1560 result
= state
.result
;
1566 hfs_unlock_truncate(cp
, HFS_LOCK_DEFAULT
);
1568 return MacToVFSError(result
);
1572 * Callback - called for each attribute record
1575 listattr_callback(const HFSPlusAttrKey
*key
, __unused
const HFSPlusAttrData
*data
, struct listattr_callback_state
*state
)
1577 char attrname
[XATTR_MAXNAMELEN
+ 1];
1581 if (state
->fileID
!= key
->fileID
) {
1583 return (0); /* stop */
1586 * Skip over non-primary keys
1588 if (key
->startBlock
!= 0) {
1589 return (1); /* continue */
1592 /* Convert the attribute name into UTF-8. */
1593 result
= utf8_encodestr(key
->attrName
, key
->attrNameLen
* sizeof(UniChar
),
1594 (u_int8_t
*)attrname
, (size_t *)&bytecount
, sizeof(attrname
), '/', UTF_ADD_NULL_TERM
);
1596 state
->result
= result
;
1597 return (0); /* stop */
1599 bytecount
++; /* account for null termination char */
1601 state
->size
+= bytecount
;
1603 if (state
->buf
!= NULL
) {
1604 if ((size_t)bytecount
> state
->bufsize
) {
1605 state
->result
= ERANGE
;
1606 return (0); /* stop */
1609 memcpy(state
->buf
, attrname
, bytecount
);
1611 state
->buf
= (state
->buf
== NULL
? NULL
: ((u_int8_t
*)state
->buf
+ bytecount
));
1612 state
->bufsize
-= bytecount
;
1614 return (1); /* continue */
1618 * Remove all the attributes from a cnode.
1620 * This function creates/ends its own transaction so that each
1621 * attribute is deleted in its own transaction (to avoid having
1622 * a transaction grow too large).
1624 * This function takes the necessary locks on the attribute
1625 * b-tree file and the allocation (bitmap) file.
1627 * NOTE: Upon sucecss, this function will return with an open
1628 * transaction. The reason we do it this way is because when we
1629 * delete the last attribute, we must make sure the flag in the
1630 * catalog record that indicates there are no more records is cleared.
1631 * The caller is responsible for doing this and *must* do it before
1632 * ending the transaction.
1635 hfs_removeallattr(struct hfsmount
*hfsmp
, u_int32_t fileid
, bool *open_transaction
)
1637 BTreeIterator
*iterator
= NULL
;
1638 HFSPlusAttrKey
*key
;
1639 struct filefork
*btfile
;
1640 int result
, lockflags
= 0;
1642 *open_transaction
= false;
1644 if (hfsmp
->hfs_attribute_vp
== NULL
)
1647 btfile
= VTOF(hfsmp
->hfs_attribute_vp
);
1649 iterator
= hfs_mallocz(sizeof(BTreeIterator
));
1650 if (iterator
== NULL
)
1653 key
= (HFSPlusAttrKey
*)&iterator
->key
;
1655 /* Loop until there are no more attributes for this file id */
1657 if (!*open_transaction
)
1658 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
, HFS_SHARED_LOCK
);
1660 (void) hfs_buildattrkey(fileid
, NULL
, key
);
1661 result
= BTIterateRecord(btfile
, kBTreeNextRecord
, iterator
, NULL
, NULL
);
1662 if (result
|| key
->fileID
!= fileid
)
1665 hfs_systemfile_unlock(hfsmp
, lockflags
);
1668 if (*open_transaction
) {
1669 hfs_end_transaction(hfsmp
);
1670 *open_transaction
= false;
1673 if (hfs_start_transaction(hfsmp
) != 0) {
1678 *open_transaction
= true;
1680 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
| SFL_BITMAP
, HFS_EXCLUSIVE_LOCK
);
1682 result
= remove_attribute_records(hfsmp
, iterator
);
1690 hfs_systemfile_unlock(hfsmp
, lockflags
);
1692 result
= result
== btNotFound
? 0 : MacToVFSError(result
);
1694 if (result
&& *open_transaction
) {
1695 hfs_end_transaction(hfsmp
);
1696 *open_transaction
= false;
1703 * hfs_attrkeycompare - compare two attribute b-tree keys.
1705 * The name portion of the key is compared using a 16-bit binary comparison.
1706 * This is called from the b-tree code.
1709 hfs_attrkeycompare(HFSPlusAttrKey
*searchKey
, HFSPlusAttrKey
*trialKey
)
1711 u_int32_t searchFileID
, trialFileID
;
1714 searchFileID
= searchKey
->fileID
;
1715 trialFileID
= trialKey
->fileID
;
1718 if (searchFileID
> trialFileID
) {
1720 } else if (searchFileID
< trialFileID
) {
1723 u_int16_t
* str1
= &searchKey
->attrName
[0];
1724 u_int16_t
* str2
= &trialKey
->attrName
[0];
1725 int length1
= searchKey
->attrNameLen
;
1726 int length2
= trialKey
->attrNameLen
;
1730 if (length1
< length2
) {
1733 } else if (length1
> length2
) {
1756 * Names are equal; compare startBlock
1758 if (searchKey
->startBlock
== trialKey
->startBlock
) {
1761 return (searchKey
->startBlock
< trialKey
->startBlock
? -1 : 1);
1769 * hfs_buildattrkey - build an Attribute b-tree key
1772 hfs_buildattrkey(u_int32_t fileID
, const char *attrname
, HFSPlusAttrKey
*key
)
1775 size_t unicodeBytes
= 0;
1777 if (attrname
!= NULL
) {
1779 * Convert filename from UTF-8 into Unicode
1781 result
= utf8_decodestr((const u_int8_t
*)attrname
, strlen(attrname
), key
->attrName
,
1782 &unicodeBytes
, sizeof(key
->attrName
), 0, 0);
1784 if (result
!= ENAMETOOLONG
)
1785 result
= EINVAL
; /* name has invalid characters */
1788 key
->attrNameLen
= unicodeBytes
/ sizeof(UniChar
);
1789 key
->keyLength
= kHFSPlusAttrKeyMinimumLength
+ unicodeBytes
;
1791 key
->attrNameLen
= 0;
1792 key
->keyLength
= kHFSPlusAttrKeyMinimumLength
;
1795 key
->fileID
= fileID
;
1796 key
->startBlock
= 0;
1802 * getnodecount - calculate starting node count for attributes b-tree.
1805 getnodecount(struct hfsmount
*hfsmp
, size_t nodesize
)
1807 u_int64_t freebytes
;
1808 u_int64_t calcbytes
;
1811 * 10.4: Scale base on current catalog file size (20 %) up to 20 MB.
1812 * 10.5: Attempt to be as big as the catalog clump size.
1814 * Use no more than 10 % of the remaining free space.
1816 freebytes
= (u_int64_t
)hfs_freeblks(hfsmp
, 0) * (u_int64_t
)hfsmp
->blockSize
;
1818 calcbytes
= MIN(hfsmp
->hfs_catalog_cp
->c_datafork
->ff_size
/ 5, 20 * 1024 * 1024);
1820 calcbytes
= MAX(calcbytes
, hfsmp
->hfs_catalog_cp
->c_datafork
->ff_clumpsize
);
1822 calcbytes
= MIN(calcbytes
, freebytes
/ 10);
1824 return (MAX(2, (int)(calcbytes
/ nodesize
)));
1828 * getmaxinlineattrsize - calculate maximum inline attribute size.
1830 * This yields 3,802 bytes for an 8K node size.
1833 getmaxinlineattrsize(struct vnode
* attrvp
)
1835 BTreeInfoRec btinfo
;
1836 size_t nodesize
= ATTRIBUTE_FILE_NODE_SIZE
;
1839 if (attrvp
!= NULL
) {
1840 (void) hfs_lock(VTOC(attrvp
), HFS_SHARED_LOCK
, HFS_LOCK_DEFAULT
);
1841 if (BTGetInformation(VTOF(attrvp
), 0, &btinfo
) == 0)
1842 nodesize
= btinfo
.nodeSize
;
1843 hfs_unlock(VTOC(attrvp
));
1846 maxsize
-= sizeof(BTNodeDescriptor
); /* minus node descriptor */
1847 maxsize
-= 3 * sizeof(u_int16_t
); /* minus 3 index slots */
1848 maxsize
/= 2; /* 2 key/rec pairs minumum */
1849 maxsize
-= sizeof(HFSPlusAttrKey
); /* minus maximum key size */
1850 maxsize
-= sizeof(HFSPlusAttrData
) - 2; /* minus data header */
1851 maxsize
&= 0xFFFFFFFE; /* multiple of 2 bytes */