2 * Copyright (c) 2004-2020 Apple Inc. All rights reserved.
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. The rights granted to you under the License
10 * may not be used to create, or enable the creation or redistribution of,
11 * unlawful or unlicensed copies of an Apple operating system, or to
12 * circumvent, violate, or enable the circumvention or violation of, any
13 * terms of an Apple operating system software license agreement.
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
18 * The Original Code and all software distributed under the License are
19 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23 * Please see the License for the specific language governing rights and
24 * limitations under the License.
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
29 #include <sys/param.h>
30 #include <sys/systm.h>
31 #include <sys/kernel.h>
32 #include <sys/malloc.h>
34 #include <sys/utfconv.h>
35 #include <sys/vnode.h>
36 #include <sys/xattr.h>
37 #include <sys/fcntl.h>
38 #include <sys/fsctl.h>
39 #include <sys/kauth.h>
42 #include "hfs_cnode.h"
43 #include "hfs_mount.h"
44 #include "hfs_format.h"
45 #include "hfs_endian.h"
46 #include "hfs_btreeio.h"
47 #include "hfs_fsctl.h"
48 #include "hfs_cprotect.h"
50 #include "BTreesInternal.h"
52 #define HFS_XATTR_VERBOSE 0
54 #define ATTRIBUTE_FILE_NODE_SIZE 8192
57 /* State information for the listattr_callback callback function. */
58 struct listattr_callback_state
{
67 #endif /* HFS_COMPRESSION */
71 /* HFS Internal Names */
72 #define XATTR_XATTREXTENTS_NAME "system.xattrextents"
74 static u_int32_t emptyfinfo
[8] = {0};
76 static int hfs_zero_hidden_fields (struct cnode
*cp
, u_int8_t
*finderinfo
);
78 const char hfs_attrdatafilename
[] = "Attribute Data";
80 static int listattr_callback(const HFSPlusAttrKey
*key
, const HFSPlusAttrData
*data
,
81 struct listattr_callback_state
*state
);
83 static int remove_attribute_records(struct hfsmount
*hfsmp
, BTreeIterator
* iterator
);
85 static int getnodecount(struct hfsmount
*hfsmp
, size_t nodesize
);
87 static size_t getmaxinlineattrsize(struct vnode
* attrvp
);
89 static int read_attr_data(struct hfsmount
*hfsmp
, uio_t uio
, size_t datasize
, HFSPlusExtentDescriptor
*extents
);
91 static int write_attr_data(struct hfsmount
*hfsmp
, uio_t uio
, size_t datasize
, HFSPlusExtentDescriptor
*extents
);
93 static int alloc_attr_blks(struct hfsmount
*hfsmp
, size_t attrsize
, size_t extentbufsize
, HFSPlusExtentDescriptor
*extents
, int *blocks
);
95 static void free_attr_blks(struct hfsmount
*hfsmp
, int blkcnt
, HFSPlusExtentDescriptor
*extents
);
97 static int has_overflow_extents(HFSPlusForkData
*forkdata
);
99 static int count_extent_blocks(int maxblks
, HFSPlusExtentRecord extents
);
104 * Iterator over set bits within an xattr free fext bitmap.
106 * See xbm_make_iter() for details on creation.
109 const uint64_t *bitmap
;
114 // xattr IO subsystem assertion
115 #define xattr_assert(cond) if(!(cond)) do { \
116 panic("xattr_assert() failed: %s",#cond); \
119 #define xattr_infomsg(...) // errmsg(__VA_ARGS__)
122 * Purely for documentation purposes, simulate C++ const_cast<>() in the C
124 * This makes it quite obvious that the `const' part of a cast is being removed
125 * to workaround an api that accepts a non-const pointer but otherwise has no
126 * reason to modify its value.
129 * ancient_sum(a, count)
140 * int modern_sum(const int *a, int count) {
141 * return ancient_sum(const_cast(int *, a), count);
144 #define const_cast(type, expr) ((type)(expr))
146 static uint64_t fext2cluster(const HFSPlusExtentDescriptor
*fext
, uint32_t fs_bsize
);
147 static bool cluster_off_in_fext(uint64_t off
, const HFSPlusExtentDescriptor
*fext
,
150 static bool fext2cluster_check(const HFSPlusExtentDescriptor
*fext
, uint32_t fs_bsize
,
152 static uint32_t xattr_cluster_scale(uint32_t fs_bsize
);
154 // xattr fext thread local storage routines
155 static const HFSPlusExtentDescriptor
**_Nullable
xattr_fext_alloc(
156 xattr_io_info_t
*info
) __attribute__((warn_unused_result
));
157 static void xattr_fext_free(xattr_io_info_t
*info
,
158 const HFSPlusExtentDescriptor
**xfext
);
159 static void xattr_fext_set(xattr_io_info_t
*info
,
160 const HFSPlusExtentDescriptor
**xfext
, const HFSPlusExtentDescriptor
*fext
);
161 static void xattr_fext_clear(xattr_io_info_t
*info
,
162 const HFSPlusExtentDescriptor
**xfext
);
164 static size_t xattr_fext_index(const xattr_io_info_t
*info
,
165 const HFSPlusExtentDescriptor
**xfext
);
167 // xattr fext free bitmap routines, namespace `xbm_'
168 static bool xbm_find_free(const xattr_io_info_t
*info
, size_t *out
);
169 static void xbm_set_used(xattr_io_info_t
*info
, size_t i
);
170 static void xbm_clear_used(xattr_io_info_t
*info
, size_t i
);
172 static bool xbm_valid_index(const xattr_io_info_t
*info
, int64_t i
);
173 static size_t xbm_size(const xattr_io_info_t
*info
);
175 // bitmap iterator functions
176 static xbm_iter_t
xbm_make_iter(const uint64_t *bitmap
, size_t len
);
177 static bool xbm_iter_next(xbm_iter_t
*ier
);
178 static size_t xbm_iter_peek(const xbm_iter_t
*ier
);
180 // bitmap_ wrappers under namespace bm_
181 static bool bm_find(const uint64_t *bm
,
182 size_t from
, size_t len
, bool value
, size_t *out
);
183 static bool bm_valid_index(int64_t i
, size_t len
);
188 * Obtain the vnode for a stream.
191 hfs_vnop_getnamedstream(struct vnop_getnamedstream_args
* ap
)
193 vnode_t vp
= ap
->a_vp
;
194 vnode_t
*svpp
= ap
->a_svpp
;
201 * We only support the "com.apple.ResourceFork" stream.
203 if (strcmp(ap
->a_name
, XATTR_RESOURCEFORK_NAME
) != 0) {
207 if ( !S_ISREG(cp
->c_mode
) ) {
211 int hide_rsrc
= hfs_hides_rsrc(ap
->a_context
, VTOC(vp
), 1);
212 #endif /* HFS_COMPRESSION */
213 if ((error
= hfs_lock(VTOC(vp
), HFS_EXCLUSIVE_LOCK
, HFS_LOCK_DEFAULT
))) {
216 if ((!hfs_has_rsrc(cp
)
219 #endif /* HFS_COMPRESSION */
220 ) && (ap
->a_operation
!= NS_OPEN
)) {
224 error
= hfs_vgetrsrc(VTOHFS(vp
), vp
, svpp
);
234 hfs_vnop_makenamedstream(struct vnop_makenamedstream_args
* ap
)
236 vnode_t vp
= ap
->a_vp
;
237 vnode_t
*svpp
= ap
->a_svpp
;
244 * We only support the "com.apple.ResourceFork" stream.
246 if (strcmp(ap
->a_name
, XATTR_RESOURCEFORK_NAME
) != 0) {
250 if ( !S_ISREG(cp
->c_mode
) ) {
254 if (hfs_hides_rsrc(ap
->a_context
, VTOC(vp
), 1)) {
255 if (VNODE_IS_RSRC(vp
)) {
258 error
= decmpfs_decompress_file(vp
, VTOCMP(vp
), -1, 1, 0);
263 #endif /* HFS_COMPRESSION */
264 if ((error
= hfs_lock(cp
, HFS_EXCLUSIVE_LOCK
, HFS_LOCK_DEFAULT
))) {
267 error
= hfs_vgetrsrc(VTOHFS(vp
), vp
, svpp
);
277 hfs_vnop_removenamedstream(struct vnop_removenamedstream_args
* ap
)
279 vnode_t svp
= ap
->a_svp
;
280 cnode_t
*scp
= VTOC(svp
);
284 * We only support the "com.apple.ResourceFork" stream.
286 if (strcmp(ap
->a_name
, XATTR_RESOURCEFORK_NAME
) != 0) {
290 if (hfs_hides_rsrc(ap
->a_context
, scp
, 1)) {
294 #endif /* HFS_COMPRESSION */
296 hfs_lock_truncate(scp
, HFS_EXCLUSIVE_LOCK
, HFS_LOCK_DEFAULT
);
297 if (VTOF(svp
)->ff_size
) {
298 // hfs_truncate will deal with the cnode lock
299 error
= hfs_truncate(svp
, 0, IO_NDELAY
, 0, ap
->a_context
);
301 hfs_unlock_truncate(scp
, HFS_LOCK_DEFAULT
);
308 /* Zero out the date added field for the specified cnode */
309 static int hfs_zero_hidden_fields (struct cnode
*cp
, u_int8_t
*finderinfo
)
311 u_int8_t
*finfo
= finderinfo
;
313 /* Advance finfo by 16 bytes to the 2nd half of the finderinfo */
316 if (S_ISREG(cp
->c_attr
.ca_mode
) || S_ISLNK(cp
->c_attr
.ca_mode
)) {
317 struct FndrExtendedFileInfo
*extinfo
= (struct FndrExtendedFileInfo
*)finfo
;
318 extinfo
->document_id
= 0;
319 extinfo
->date_added
= 0;
320 extinfo
->write_gen_counter
= 0;
321 } else if (S_ISDIR(cp
->c_attr
.ca_mode
)) {
322 struct FndrExtendedDirInfo
*extinfo
= (struct FndrExtendedDirInfo
*)finfo
;
323 extinfo
->document_id
= 0;
324 extinfo
->date_added
= 0;
325 extinfo
->write_gen_counter
= 0;
327 /* Return an error */
335 * Retrieve the data of an extended attribute.
338 hfs_vnop_getxattr(struct vnop_getxattr_args
*ap
)
340 struct vnop_getxattr_args {
341 struct vnodeop_desc *a_desc;
347 vfs_context_t a_context;
351 struct vnode
*vp
= ap
->a_vp
;
353 struct hfsmount
*hfsmp
;
354 uio_t uio
= ap
->a_uio
;
359 if (vp
== cp
->c_vp
) {
361 int decmpfs_hide
= hfs_hides_xattr(ap
->a_context
, VTOC(vp
), ap
->a_name
, 1); /* 1 == don't take the cnode lock */
362 if (decmpfs_hide
&& !(ap
->a_options
& XATTR_SHOWCOMPRESSION
))
364 #endif /* HFS_COMPRESSION */
366 /* Get the Finder Info. */
367 if (strcmp(ap
->a_name
, XATTR_FINDERINFO_NAME
) == 0) {
368 u_int8_t finderinfo
[32];
371 if ((result
= hfs_lock(cp
, HFS_SHARED_LOCK
, HFS_LOCK_DEFAULT
))) {
374 /* Make a copy since we may not export all of it. */
375 bcopy(cp
->c_finderinfo
, finderinfo
, sizeof(finderinfo
));
378 /* Zero out the date added field in the local copy */
379 hfs_zero_hidden_fields (cp
, finderinfo
);
381 /* Don't expose a symlink's private type/creator. */
382 if (vnode_islnk(vp
)) {
383 struct FndrFileInfo
*fip
;
385 fip
= (struct FndrFileInfo
*)&finderinfo
;
389 /* If Finder Info is empty then it doesn't exist. */
390 if (bcmp(finderinfo
, emptyfinfo
, sizeof(emptyfinfo
)) == 0) {
394 *ap
->a_size
= bufsize
;
397 if ((user_size_t
)uio_resid(uio
) < bufsize
)
400 result
= uiomove((caddr_t
)&finderinfo
, bufsize
, uio
);
404 /* Read the Resource Fork. */
405 if (strcmp(ap
->a_name
, XATTR_RESOURCEFORK_NAME
) == 0) {
406 struct vnode
*rvp
= NULL
;
407 int openunlinked
= 0;
410 if ( !S_ISREG(cp
->c_mode
) ) {
413 if ((result
= hfs_lock(cp
, HFS_EXCLUSIVE_LOCK
, HFS_LOCK_DEFAULT
))) {
416 namelen
= cp
->c_desc
.cd_namelen
;
418 if (!hfs_has_rsrc(cp
)) {
423 if ((cp
->c_flag
& C_DELETED
) && (namelen
== 0)) {
427 result
= hfs_vgetrsrc(hfsmp
, vp
, &rvp
);
433 *ap
->a_size
= (size_t)VTOF(rvp
)->ff_size
;
436 user_ssize_t uio_size
= 0;
438 uio_size
= uio_resid(uio
);
439 #endif /* HFS_COMPRESSION */
440 result
= VNOP_READ(rvp
, uio
, 0, ap
->a_context
);
444 (uio_resid(uio
) == uio_size
)) {
446 * We intentionally make the above call to VNOP_READ so that
447 * it can return an authorization/permission/etc. Error
448 * based on ap->a_context and thus deny this operation;
449 * in that case, result != 0 and we won't proceed.
451 * However, if result == 0, it will have returned no data
452 * because hfs_vnop_read hid the resource fork
453 * (hence uio_resid(uio) == uio_size, i.e. the uio is untouched)
455 * In that case, we try again with the decmpfs_ctx context
456 * to get the actual data
458 result
= VNOP_READ(rvp
, uio
, 0, decmpfs_ctx
);
460 #endif /* HFS_COMPRESSION */
462 /* force the rsrc fork vnode to recycle right away */
465 vref
= vnode_ref (rvp
);
478 * Standard HFS only supports native FinderInfo and Resource Forks.
480 if (hfsmp
->hfs_flags
& HFS_STANDARD
) {
485 if ((result
= hfs_lock(cp
, HFS_SHARED_LOCK
, HFS_LOCK_DEFAULT
))) {
489 /* Check for non-rsrc, non-finderinfo EAs */
490 result
= hfs_getxattr_internal (cp
, ap
, VTOHFS(cp
->c_vp
), 0);
494 return MacToVFSError(result
);
497 // Has same limitations as hfs_getxattr_internal below
498 int hfs_xattr_read(vnode_t vp
, const char *name
, void *data
, size_t *size
)
500 uio_t uio
= uio_create(1, 0, UIO_SYSSPACE
, UIO_READ
);
502 uio_addiov(uio
, CAST_USER_ADDR_T(data
), *size
);
504 struct vnop_getxattr_args args
= {
510 int ret
= hfs_getxattr_internal(VTOC(vp
), &args
, VTOHFS(vp
), 0);
520 * We break out this internal function which searches the attributes B-Tree and the
521 * overflow extents file to find non-resource, non-finderinfo EAs. There may be cases
522 * where we need to get EAs in contexts where we are already holding the cnode lock,
523 * and to re-enter hfs_vnop_getxattr would cause us to double-lock the cnode. Instead,
524 * we can just directly call this function.
526 * We pass the hfsmp argument directly here because we may not necessarily have a cnode to
527 * operate on. Under normal conditions, we have a file or directory to query, but if we
528 * are operating on the root directory (id 1), then we may not have a cnode. In this case, if hte
529 * 'cp' argument is NULL, then we need to use the 'fileid' argument as the entry to manipulate
531 * NOTE: This function assumes the cnode lock for 'cp' is held exclusive or shared.
533 int hfs_getxattr_internal (struct cnode
*cp
, struct vnop_getxattr_args
*ap
,
534 struct hfsmount
*hfsmp
, u_int32_t fileid
)
537 struct filefork
*btfile
;
538 struct BTreeIterator
* iterator
= NULL
;
540 HFSPlusAttrRecord
*recp
= NULL
;
541 size_t recp_size
= 0;
542 FSBufferDescriptor btdata
;
545 u_int16_t datasize
= 0;
546 uio_t uio
= ap
->a_uio
;
547 u_int32_t target_id
= 0;
550 target_id
= cp
->c_fileid
;
556 /* Bail if we don't have an EA B-Tree. */
557 if ((hfsmp
->hfs_attribute_vp
== NULL
) ||
558 ((cp
) && (cp
->c_attr
.ca_recflags
& kHFSHasAttributesMask
) == 0)) {
563 /* Initialize the B-Tree iterator for searching for the proper EA */
564 btfile
= VTOF(hfsmp
->hfs_attribute_vp
);
566 iterator
= hfs_mallocz(sizeof(*iterator
));
568 /* Allocate memory for reading in the attribute record. This buffer is
569 * big enough to read in all types of attribute records. It is not big
570 * enough to read inline attribute data which is read in later.
572 recp
= hfs_malloc(recp_size
= sizeof(HFSPlusAttrRecord
));
573 btdata
.bufferAddress
= recp
;
574 btdata
.itemSize
= sizeof(HFSPlusAttrRecord
);
575 btdata
.itemCount
= 1;
577 result
= hfs_buildattrkey(target_id
, ap
->a_name
, (HFSPlusAttrKey
*)&iterator
->key
);
582 /* Lookup the attribute in the Attribute B-Tree */
583 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
, HFS_SHARED_LOCK
);
584 result
= BTSearchRecord(btfile
, iterator
, &btdata
, &datasize
, NULL
);
585 hfs_systemfile_unlock(hfsmp
, lockflags
);
588 if (result
== btNotFound
) {
595 * Operate differently if we have inline EAs that can fit in the attribute B-Tree or if
596 * we have extent based EAs.
598 switch (recp
->recordType
) {
600 /* Attribute fits in the Attribute B-Tree */
601 case kHFSPlusAttrInlineData
: {
603 * Sanity check record size. It's not required to have any
604 * user data, so the minimum size is 2 bytes less that the
605 * size of HFSPlusAttrData (since HFSPlusAttrData struct
606 * has 2 bytes set aside for attribute data).
608 if (datasize
< (sizeof(HFSPlusAttrData
) - 2)) {
609 printf("hfs_getxattr: vol=%s %d,%s invalid record size %d (expecting %lu)\n",
610 hfsmp
->vcbVN
, target_id
, ap
->a_name
, datasize
, sizeof(HFSPlusAttrData
));
614 *ap
->a_size
= recp
->attrData
.attrSize
;
615 if (uio
&& recp
->attrData
.attrSize
!= 0) {
616 if (*ap
->a_size
> (user_size_t
)uio_resid(uio
)) {
617 /* User provided buffer is not large enough for the xattr data */
620 /* Previous BTreeSearchRecord() read in only the attribute record,
621 * and not the attribute data. Now allocate enough memory for
622 * both attribute record and data, and read the attribute record again.
624 bufsize
= sizeof(HFSPlusAttrData
) - 2 + recp
->attrData
.attrSize
;
625 hfs_free(recp
, recp_size
);
626 recp
= hfs_malloc(recp_size
= bufsize
);
628 btdata
.bufferAddress
= recp
;
629 btdata
.itemSize
= bufsize
;
630 btdata
.itemCount
= 1;
632 bzero(iterator
, sizeof(*iterator
));
633 result
= hfs_buildattrkey(target_id
, ap
->a_name
, (HFSPlusAttrKey
*)&iterator
->key
);
638 /* Lookup the attribute record and inline data */
639 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
, HFS_SHARED_LOCK
);
640 result
= BTSearchRecord(btfile
, iterator
, &btdata
, &datasize
, NULL
);
641 hfs_systemfile_unlock(hfsmp
, lockflags
);
643 if (result
== btNotFound
) {
649 /* Copy-out the attribute data to the user buffer */
650 *ap
->a_size
= recp
->attrData
.attrSize
;
651 result
= uiomove((caddr_t
) &recp
->attrData
.attrData
, recp
->attrData
.attrSize
, uio
);
657 /* Extent-Based EAs */
658 case kHFSPlusAttrForkData
: {
659 if (datasize
< sizeof(HFSPlusAttrForkData
)) {
660 printf("hfs_getxattr: vol=%s %d,%s invalid record size %d (expecting %lu)\n",
661 hfsmp
->vcbVN
, target_id
, ap
->a_name
, datasize
, sizeof(HFSPlusAttrForkData
));
665 *ap
->a_size
= recp
->forkData
.theFork
.logicalSize
;
669 if (*ap
->a_size
> (user_size_t
)uio_resid(uio
)) {
673 /* Process overflow extents if necessary. */
674 if (has_overflow_extents(&recp
->forkData
.theFork
)) {
675 HFSPlusExtentDescriptor
*extentbuf
;
676 HFSPlusExtentDescriptor
*extentptr
;
677 size_t extentbufsize
;
678 u_int32_t totalblocks
;
682 totalblocks
= recp
->forkData
.theFork
.totalBlocks
;
683 /* Ignore bogus block counts. */
684 if (totalblocks
> howmany(HFS_XATTR_MAXSIZE
, hfsmp
->blockSize
)) {
688 attrlen
= recp
->forkData
.theFork
.logicalSize
;
690 /* Get a buffer to hold the worst case amount of extents. */
691 extentbufsize
= totalblocks
* sizeof(HFSPlusExtentDescriptor
);
692 extentbufsize
= roundup(extentbufsize
, sizeof(HFSPlusExtentRecord
));
693 extentbuf
= hfs_mallocz(extentbufsize
);
694 extentptr
= extentbuf
;
696 /* Grab the first 8 extents. */
697 bcopy(&recp
->forkData
.theFork
.extents
[0], extentptr
, sizeof(HFSPlusExtentRecord
));
698 extentptr
+= kHFSPlusExtentDensity
;
699 blkcnt
= count_extent_blocks(totalblocks
, recp
->forkData
.theFork
.extents
);
701 /* Now lookup the overflow extents. */
702 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
, HFS_SHARED_LOCK
);
703 while (blkcnt
< totalblocks
) {
704 ((HFSPlusAttrKey
*)&iterator
->key
)->startBlock
= blkcnt
;
705 result
= BTSearchRecord(btfile
, iterator
, &btdata
, &datasize
, NULL
);
707 (recp
->recordType
!= kHFSPlusAttrExtents
) ||
708 (datasize
< sizeof(HFSPlusAttrExtents
))) {
709 printf("hfs_getxattr: %s missing extents, only %d blks of %d found\n",
710 ap
->a_name
, blkcnt
, totalblocks
);
712 break; /* break from while */
714 /* Grab the next 8 extents. */
715 bcopy(&recp
->overflowExtents
.extents
[0], extentptr
, sizeof(HFSPlusExtentRecord
));
716 extentptr
+= kHFSPlusExtentDensity
;
717 blkcnt
+= count_extent_blocks(totalblocks
, recp
->overflowExtents
.extents
);
720 /* Release Attr B-Tree lock */
721 hfs_systemfile_unlock(hfsmp
, lockflags
);
723 if (blkcnt
< totalblocks
) {
726 result
= read_attr_data(hfsmp
, uio
, attrlen
, extentbuf
);
728 hfs_free(extentbuf
, extentbufsize
);
730 } else { /* No overflow extents. */
731 result
= read_attr_data(hfsmp
, uio
, recp
->forkData
.theFork
.logicalSize
, recp
->forkData
.theFork
.extents
);
737 /* We only support Extent or inline EAs. Default to ENOATTR for anything else */
743 hfs_free(iterator
, sizeof(*iterator
));
744 hfs_free(recp
, recp_size
);
752 * Set the data of an extended attribute.
755 hfs_vnop_setxattr(struct vnop_setxattr_args
*ap
)
757 struct vnop_setxattr_args {
758 struct vnodeop_desc *a_desc;
763 vfs_context_t a_context;
767 struct vnode
*vp
= ap
->a_vp
;
768 struct cnode
*cp
= NULL
;
769 struct hfsmount
*hfsmp
;
770 uio_t uio
= ap
->a_uio
;
772 void * user_data_ptr
= NULL
;
774 time_t orig_ctime
=VTOC(vp
)->c_ctime
;
776 if (ap
->a_name
== NULL
|| ap
->a_name
[0] == '\0') {
777 return (EINVAL
); /* invalid name */
780 if (VNODE_IS_RSRC(vp
)) {
785 if (hfs_hides_xattr(ap
->a_context
, VTOC(vp
), ap
->a_name
, 1) ) { /* 1 == don't take the cnode lock */
786 result
= decmpfs_decompress_file(vp
, VTOCMP(vp
), -1, 1, 0);
790 #endif /* HFS_COMPRESSION */
792 nspace_snapshot_event(vp
, orig_ctime
, NAMESPACE_HANDLER_METADATA_WRITE_OP
, NSPACE_REARM_NO_ARG
);
794 /* Set the Finder Info. */
795 if (strcmp(ap
->a_name
, XATTR_FINDERINFO_NAME
) == 0) {
799 struct FndrFileInfo info
;
801 void * finderinfo_start
;
802 u_int8_t
*finfo
= NULL
;
804 u_int32_t dateadded
= 0;
805 u_int32_t write_gen_counter
= 0;
806 u_int32_t document_id
= 0;
808 attrsize
= sizeof(VTOC(vp
)->c_finderinfo
);
810 if ((user_size_t
)uio_resid(uio
) != attrsize
) {
813 /* Grab the new Finder Info data. */
814 if ((result
= uiomove(fi
.cdata
, attrsize
, uio
))) {
818 if ((result
= hfs_lock(VTOC(vp
), HFS_EXCLUSIVE_LOCK
, HFS_LOCK_DEFAULT
))) {
823 /* Symlink's don't have an external type/creator. */
824 if (vnode_islnk(vp
)) {
825 /* Skip over type/creator fields. */
826 finderinfo_start
= &cp
->c_finderinfo
[8];
829 finderinfo_start
= &cp
->c_finderinfo
[0];
831 * Don't allow the external setting of
832 * file type to kHardLinkFileType.
834 if (fi
.info
.fdType
== SWAP_BE32(kHardLinkFileType
)) {
840 /* Grab the current date added from the cnode */
841 dateadded
= hfs_get_dateadded (cp
);
842 if (S_ISREG(cp
->c_attr
.ca_mode
) || S_ISLNK(cp
->c_attr
.ca_mode
)) {
843 struct FndrExtendedFileInfo
*extinfo
= (struct FndrExtendedFileInfo
*)((u_int8_t
*)cp
->c_finderinfo
+ 16);
845 * Grab generation counter directly from the cnode
846 * instead of calling hfs_get_gencount(), because
847 * for zero generation count values hfs_get_gencount()
848 * lies and bumps it up to one.
850 write_gen_counter
= extinfo
->write_gen_counter
;
851 document_id
= extinfo
->document_id
;
852 } else if (S_ISDIR(cp
->c_attr
.ca_mode
)) {
853 struct FndrExtendedDirInfo
*extinfo
= (struct FndrExtendedDirInfo
*)((u_int8_t
*)cp
->c_finderinfo
+ 16);
854 write_gen_counter
= extinfo
->write_gen_counter
;
855 document_id
= extinfo
->document_id
;
859 * Zero out the finder info's reserved fields like date added,
860 * generation counter, and document id to ignore user's attempts
863 hfs_zero_hidden_fields(cp
, fi
.data
);
865 if (bcmp(finderinfo_start
, emptyfinfo
, attrsize
)) {
866 /* attr exists and "create" was specified. */
867 if (ap
->a_options
& XATTR_CREATE
) {
872 /* attr doesn't exists and "replace" was specified. */
873 if (ap
->a_options
& XATTR_REPLACE
) {
880 * Now restore the date added and other reserved fields to the finderinfo to
881 * be written out. Advance to the 2nd half of the finderinfo to write them
882 * out into the buffer.
884 * Make sure to endian swap the date added back into big endian. When we used
885 * hfs_get_dateadded above to retrieve it, it swapped into local endianness
886 * for us. But now that we're writing it out, put it back into big endian.
888 finfo
= &fi
.data
[16];
889 if (S_ISREG(cp
->c_attr
.ca_mode
) || S_ISLNK(cp
->c_attr
.ca_mode
)) {
890 struct FndrExtendedFileInfo
*extinfo
= (struct FndrExtendedFileInfo
*)finfo
;
891 extinfo
->date_added
= OSSwapHostToBigInt32(dateadded
);
892 extinfo
->write_gen_counter
= write_gen_counter
;
893 extinfo
->document_id
= document_id
;
894 } else if (S_ISDIR(cp
->c_attr
.ca_mode
)) {
895 struct FndrExtendedDirInfo
*extinfo
= (struct FndrExtendedDirInfo
*)finfo
;
896 extinfo
->date_added
= OSSwapHostToBigInt32(dateadded
);
897 extinfo
->write_gen_counter
= write_gen_counter
;
898 extinfo
->document_id
= document_id
;
901 /* Set the cnode's Finder Info. */
902 if (attrsize
== sizeof(cp
->c_finderinfo
)) {
903 bcopy(&fi
.data
[0], finderinfo_start
, attrsize
);
905 bcopy(&fi
.data
[8], finderinfo_start
, attrsize
);
908 /* Updating finderInfo updates change time and modified time */
909 cp
->c_touch_chgtime
= TRUE
;
910 cp
->c_flag
|= C_MODIFIED
;
913 * Mirror the invisible bit to the UF_HIDDEN flag.
915 * The fdFlags for files and frFlags for folders are both 8 bytes
916 * into the userInfo (the first 16 bytes of the Finder Info). They
917 * are both 16-bit fields.
919 fdFlags
= *((u_int16_t
*) &cp
->c_finderinfo
[8]);
920 if (fdFlags
& OSSwapHostToBigConstInt16(kFinderInvisibleMask
)) {
921 cp
->c_bsdflags
|= UF_HIDDEN
;
923 cp
->c_bsdflags
&= ~UF_HIDDEN
;
926 result
= hfs_update(vp
, 0);
931 /* Write the Resource Fork. */
932 if (strcmp(ap
->a_name
, XATTR_RESOURCEFORK_NAME
) == 0) {
933 struct vnode
*rvp
= NULL
;
935 int openunlinked
= 0;
937 if (!vnode_isreg(vp
)) {
940 if ((result
= hfs_lock(VTOC(vp
), HFS_EXCLUSIVE_LOCK
, HFS_LOCK_DEFAULT
))) {
944 namelen
= cp
->c_desc
.cd_namelen
;
946 if (hfs_has_rsrc(cp
)) {
947 /* attr exists and "create" was specified. */
948 if (ap
->a_options
& XATTR_CREATE
) {
953 /* attr doesn't exists and "replace" was specified. */
954 if (ap
->a_options
& XATTR_REPLACE
) {
961 * Note that we could be called on to grab the rsrc fork vnode
962 * for a file that has become open-unlinked.
964 if ((cp
->c_flag
& C_DELETED
) && (namelen
== 0)) {
968 result
= hfs_vgetrsrc(hfsmp
, vp
, &rvp
);
973 /* VNOP_WRITE marks cnode as needing a modtime update */
974 result
= VNOP_WRITE(rvp
, uio
, 0, ap
->a_context
);
976 /* if open unlinked, force it inactive */
979 vref
= vnode_ref (rvp
);
985 /* cnode is not open-unlinked, so re-lock cnode to sync */
986 if ((result
= hfs_lock(cp
, HFS_EXCLUSIVE_LOCK
, HFS_LOCK_DEFAULT
))) {
992 /* hfs fsync rsrc fork to force to disk and update modtime */
993 result
= hfs_fsync (rvp
, MNT_NOWAIT
, 0, vfs_context_proc (ap
->a_context
));
1002 * Standard HFS only supports native FinderInfo and Resource Forks.
1004 if (hfsmp
->hfs_flags
& HFS_STANDARD
) {
1008 attrsize
= uio_resid(uio
);
1010 /* Enforce an upper limit. */
1011 if (attrsize
> HFS_XATTR_MAXSIZE
) {
1017 * Attempt to copy the users attr data before taking any locks,
1018 * only if it will be an inline attribute. For larger attributes,
1019 * the data will be directly read from the uio.
1022 hfsmp
->hfs_max_inline_attrsize
!= 0 &&
1023 attrsize
< hfsmp
->hfs_max_inline_attrsize
) {
1024 user_data_ptr
= hfs_malloc(attrsize
);
1026 result
= uiomove((caddr_t
)user_data_ptr
, attrsize
, uio
);
1032 result
= hfs_lock(VTOC(vp
), HFS_EXCLUSIVE_LOCK
, HFS_LOCK_DEFAULT
);
1039 * If we're trying to set a non-finderinfo, non-resourcefork EA, then
1040 * call the breakout function.
1042 result
= hfs_setxattr_internal (cp
, user_data_ptr
, attrsize
, ap
, VTOHFS(vp
), 0);
1048 if (user_data_ptr
) {
1049 hfs_free(user_data_ptr
, attrsize
);
1052 return (result
== btNotFound
? ENOATTR
: MacToVFSError(result
));
1055 // Has same limitations as hfs_setxattr_internal below
1056 int hfs_xattr_write(vnode_t vp
, const char *name
, const void *data
, size_t size
)
1058 struct vnop_setxattr_args args
= {
1063 return hfs_setxattr_internal(VTOC(vp
), data
, size
, &args
, VTOHFS(vp
), 0);
1067 * hfs_setxattr_internal
1069 * Internal function to set non-rsrc, non-finderinfo EAs to either the attribute B-Tree or
1072 * See comments from hfs_getxattr_internal on why we need to pass 'hfsmp' and fileid here.
1073 * The gist is that we could end up writing to the root folder which may not have a cnode.
1076 * 1. cnode 'cp' is locked EXCLUSIVE before calling this function.
1077 * 2. data_ptr contains data to be written. If gathering data from userland, this must be
1078 * done before calling this function.
1079 * 3. If data originates entirely in-kernel, use a null UIO, and ensure the size is less than
1080 * hfsmp->hfs_max_inline_attrsize bytes long.
1082 int hfs_setxattr_internal (struct cnode
*cp
, const void *data_ptr
, size_t attrsize
,
1083 struct vnop_setxattr_args
*ap
, struct hfsmount
*hfsmp
,
1086 uio_t uio
= ap
->a_uio
;
1087 struct vnode
*vp
= ap
->a_vp
;
1088 int started_transaction
= 0;
1089 struct BTreeIterator
* iterator
= NULL
;
1090 struct filefork
*btfile
= NULL
;
1091 FSBufferDescriptor btdata
;
1092 HFSPlusAttrRecord attrdata
; /* 90 bytes */
1093 HFSPlusAttrRecord
*recp
= NULL
;
1094 size_t recp_size
= 0;
1095 HFSPlusExtentDescriptor
*extentptr
= NULL
;
1096 size_t extentbufsize
= 0;
1100 int allocatedblks
= 0;
1101 u_int32_t target_id
;
1104 target_id
= cp
->c_fileid
;
1109 /* Start a transaction for our changes. */
1110 if (hfs_start_transaction(hfsmp
) != 0) {
1114 started_transaction
= 1;
1117 * Once we started the transaction, nobody can compete
1118 * with us, so make sure this file is still there.
1120 if ((cp
) && (cp
->c_flag
& C_NOEXISTS
)) {
1126 * If there isn't an attributes b-tree then create one.
1128 if (hfsmp
->hfs_attribute_vp
== NULL
) {
1129 result
= hfs_create_attr_btree(hfsmp
, ATTRIBUTE_FILE_NODE_SIZE
,
1130 getnodecount(hfsmp
, ATTRIBUTE_FILE_NODE_SIZE
));
1135 if (hfsmp
->hfs_max_inline_attrsize
== 0) {
1136 hfsmp
->hfs_max_inline_attrsize
= getmaxinlineattrsize(hfsmp
->hfs_attribute_vp
);
1139 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
, HFS_EXCLUSIVE_LOCK
);
1141 /* Build the b-tree key. */
1142 iterator
= hfs_mallocz(sizeof(*iterator
));
1143 result
= hfs_buildattrkey(target_id
, ap
->a_name
, (HFSPlusAttrKey
*)&iterator
->key
);
1148 /* Preflight for replace/create semantics. */
1149 btfile
= VTOF(hfsmp
->hfs_attribute_vp
);
1150 btdata
.bufferAddress
= &attrdata
;
1151 btdata
.itemSize
= sizeof(attrdata
);
1152 btdata
.itemCount
= 1;
1153 exists
= BTSearchRecord(btfile
, iterator
, &btdata
, NULL
, NULL
) == 0;
1155 /* Replace requires that the attribute already exists. */
1156 if ((ap
->a_options
& XATTR_REPLACE
) && !exists
) {
1160 /* Create requires that the attribute doesn't exist. */
1161 if ((ap
->a_options
& XATTR_CREATE
) && exists
) {
1166 /* If it won't fit inline then use extent-based attributes. */
1167 if (attrsize
> hfsmp
->hfs_max_inline_attrsize
) {
1170 u_int32_t
*keystartblk
;
1175 * setxattrs originating from in-kernel are not supported if they are bigger
1176 * than the inline max size. Just return ENOATTR and force them to do it with a
1183 /* Get some blocks. */
1184 blkcnt
= howmany(attrsize
, hfsmp
->blockSize
);
1185 extentbufsize
= blkcnt
* sizeof(HFSPlusExtentDescriptor
);
1186 extentbufsize
= roundup(extentbufsize
, sizeof(HFSPlusExtentRecord
));
1187 extentptr
= hfs_mallocz(extentbufsize
);
1188 result
= alloc_attr_blks(hfsmp
, attrsize
, extentbufsize
, extentptr
, &allocatedblks
);
1191 goto exit
; /* no more space */
1193 /* Copy data into the blocks. */
1194 result
= write_attr_data(hfsmp
, uio
, attrsize
, extentptr
);
1197 const char *name
= vnode_getname(vp
);
1198 printf("hfs_setxattr: write_attr_data vol=%s err (%d) %s:%s\n",
1199 hfsmp
->vcbVN
, result
, name
? name
: "", ap
->a_name
);
1201 vnode_putname(name
);
1206 /* Now remove any previous attribute. */
1208 result
= remove_attribute_records(hfsmp
, iterator
);
1211 const char *name
= vnode_getname(vp
);
1212 printf("hfs_setxattr: remove_attribute_records vol=%s err (%d) %s:%s\n",
1213 hfsmp
->vcbVN
, result
, name
? name
: "", ap
->a_name
);
1215 vnode_putname(name
);
1220 /* Create attribute fork data record. */
1221 recp
= hfs_malloc(recp_size
= sizeof(HFSPlusAttrRecord
));
1223 btdata
.bufferAddress
= recp
;
1224 btdata
.itemCount
= 1;
1225 btdata
.itemSize
= sizeof(HFSPlusAttrForkData
);
1227 recp
->recordType
= kHFSPlusAttrForkData
;
1228 recp
->forkData
.reserved
= 0;
1229 recp
->forkData
.theFork
.logicalSize
= attrsize
;
1230 recp
->forkData
.theFork
.clumpSize
= 0;
1231 recp
->forkData
.theFork
.totalBlocks
= blkcnt
;
1232 bcopy(extentptr
, recp
->forkData
.theFork
.extents
, sizeof(HFSPlusExtentRecord
));
1234 (void) hfs_buildattrkey(target_id
, ap
->a_name
, (HFSPlusAttrKey
*)&iterator
->key
);
1236 result
= BTInsertRecord(btfile
, iterator
, &btdata
, btdata
.itemSize
);
1238 printf ("hfs_setxattr: BTInsertRecord(): vol=%s %d,%s err=%d\n",
1239 hfsmp
->vcbVN
, target_id
, ap
->a_name
, result
);
1242 extentblks
= count_extent_blocks(blkcnt
, recp
->forkData
.theFork
.extents
);
1243 blkcnt
-= extentblks
;
1244 keystartblk
= &((HFSPlusAttrKey
*)&iterator
->key
)->startBlock
;
1247 /* Create overflow extents as needed. */
1248 while (blkcnt
> 0) {
1249 /* Initialize the key and record. */
1250 *keystartblk
+= (u_int32_t
)extentblks
;
1251 btdata
.itemSize
= sizeof(HFSPlusAttrExtents
);
1252 recp
->recordType
= kHFSPlusAttrExtents
;
1253 recp
->overflowExtents
.reserved
= 0;
1255 /* Copy the next set of extents. */
1256 i
+= kHFSPlusExtentDensity
;
1257 bcopy(&extentptr
[i
], recp
->overflowExtents
.extents
, sizeof(HFSPlusExtentRecord
));
1259 result
= BTInsertRecord(btfile
, iterator
, &btdata
, btdata
.itemSize
);
1261 printf ("hfs_setxattr: BTInsertRecord() overflow: vol=%s %d,%s err=%d\n",
1262 hfsmp
->vcbVN
, target_id
, ap
->a_name
, result
);
1265 extentblks
= count_extent_blocks(blkcnt
, recp
->overflowExtents
.extents
);
1266 blkcnt
-= extentblks
;
1268 } else { /* Inline data */
1270 result
= remove_attribute_records(hfsmp
, iterator
);
1276 /* Calculate size of record rounded up to multiple of 2 bytes. */
1277 btdata
.itemSize
= sizeof(HFSPlusAttrData
) - 2 + attrsize
+ ((attrsize
& 1) ? 1 : 0);
1278 recp
= hfs_malloc(recp_size
= btdata
.itemSize
);
1280 recp
->recordType
= kHFSPlusAttrInlineData
;
1281 recp
->attrData
.reserved
[0] = 0;
1282 recp
->attrData
.reserved
[1] = 0;
1283 recp
->attrData
.attrSize
= attrsize
;
1285 /* Copy in the attribute data (if any). */
1288 bcopy(data_ptr
, &recp
->attrData
.attrData
, attrsize
);
1291 * A null UIO meant it originated in-kernel. If they didn't supply data_ptr
1292 * then deny the copy operation.
1298 result
= uiomove((caddr_t
)&recp
->attrData
.attrData
, attrsize
, uio
);
1306 (void) hfs_buildattrkey(target_id
, ap
->a_name
, (HFSPlusAttrKey
*)&iterator
->key
);
1308 btdata
.bufferAddress
= recp
;
1309 btdata
.itemCount
= 1;
1310 result
= BTInsertRecord(btfile
, iterator
, &btdata
, btdata
.itemSize
);
1314 if (btfile
&& started_transaction
) {
1315 (void) BTFlushPath(btfile
);
1317 hfs_systemfile_unlock(hfsmp
, lockflags
);
1321 /* Setting an attribute only updates change time and not
1322 * modified time of the file.
1324 cp
->c_touch_chgtime
= TRUE
;
1325 cp
->c_flag
|= C_MODIFIED
;
1326 cp
->c_attr
.ca_recflags
|= kHFSHasAttributesMask
;
1327 if ((strcmp(ap
->a_name
, KAUTH_FILESEC_XATTR
) == 0)) {
1328 cp
->c_attr
.ca_recflags
|= kHFSHasSecurityMask
;
1330 (void) hfs_update(vp
, 0);
1333 if (started_transaction
) {
1334 if (result
&& allocatedblks
) {
1335 free_attr_blks(hfsmp
, allocatedblks
, extentptr
);
1337 hfs_end_transaction(hfsmp
);
1340 hfs_free(recp
, recp_size
);
1341 hfs_free(extentptr
, extentbufsize
);
1342 hfs_free(iterator
, sizeof(*iterator
));
1351 * Remove an extended attribute.
1354 hfs_vnop_removexattr(struct vnop_removexattr_args
*ap
)
1356 struct vnop_removexattr_args {
1357 struct vnodeop_desc *a_desc;
1361 vfs_context_t a_context;
1365 struct vnode
*vp
= ap
->a_vp
;
1366 struct cnode
*cp
= VTOC(vp
);
1367 struct hfsmount
*hfsmp
;
1368 struct BTreeIterator
* iterator
= NULL
;
1371 time_t orig_ctime
=VTOC(vp
)->c_ctime
;
1373 if (ap
->a_name
== NULL
|| ap
->a_name
[0] == '\0') {
1374 return (EINVAL
); /* invalid name */
1377 if (VNODE_IS_RSRC(vp
)) {
1382 if (hfs_hides_xattr(ap
->a_context
, VTOC(vp
), ap
->a_name
, 1) && !(ap
->a_options
& XATTR_SHOWCOMPRESSION
)) {
1385 #endif /* HFS_COMPRESSION */
1387 nspace_snapshot_event(vp
, orig_ctime
, NAMESPACE_HANDLER_METADATA_DELETE_OP
, NSPACE_REARM_NO_ARG
);
1389 /* If Resource Fork is non-empty then truncate it. */
1390 if (strcmp(ap
->a_name
, XATTR_RESOURCEFORK_NAME
) == 0) {
1391 struct vnode
*rvp
= NULL
;
1393 if ( !vnode_isreg(vp
) ) {
1396 if ((result
= hfs_lock(cp
, HFS_EXCLUSIVE_LOCK
, HFS_LOCK_DEFAULT
))) {
1399 if (!hfs_has_rsrc(cp
)) {
1403 result
= hfs_vgetrsrc(hfsmp
, vp
, &rvp
);
1409 hfs_lock_truncate(VTOC(rvp
), HFS_EXCLUSIVE_LOCK
, HFS_LOCK_DEFAULT
);
1411 // Tell UBC now before we take the cnode lock and start the transaction
1412 hfs_ubc_setsize(rvp
, 0, false);
1414 if ((result
= hfs_lock(VTOC(rvp
), HFS_EXCLUSIVE_LOCK
, HFS_LOCK_DEFAULT
))) {
1415 hfs_unlock_truncate(cp
, HFS_LOCK_DEFAULT
);
1420 /* Start a transaction for encapsulating changes in
1421 * hfs_truncate() and hfs_update()
1423 if ((result
= hfs_start_transaction(hfsmp
))) {
1424 hfs_unlock_truncate(cp
, HFS_LOCK_DEFAULT
);
1430 result
= hfs_truncate(rvp
, (off_t
)0, IO_NDELAY
, 0, ap
->a_context
);
1432 cp
->c_touch_chgtime
= TRUE
;
1433 cp
->c_flag
|= C_MODIFIED
;
1434 result
= hfs_update(vp
, 0);
1437 hfs_end_transaction(hfsmp
);
1438 hfs_unlock_truncate(VTOC(rvp
), HFS_LOCK_DEFAULT
);
1439 hfs_unlock(VTOC(rvp
));
1444 /* Clear out the Finder Info. */
1445 if (strcmp(ap
->a_name
, XATTR_FINDERINFO_NAME
) == 0) {
1446 void * finderinfo_start
;
1447 int finderinfo_size
;
1448 u_int8_t finderinfo
[32];
1449 u_int32_t date_added
= 0, write_gen_counter
= 0, document_id
= 0;
1450 u_int8_t
*finfo
= NULL
;
1452 if ((result
= hfs_lock(cp
, HFS_EXCLUSIVE_LOCK
, HFS_LOCK_DEFAULT
))) {
1456 /* Use the local copy to store our temporary changes. */
1457 bcopy(cp
->c_finderinfo
, finderinfo
, sizeof(finderinfo
));
1460 /* Zero out the date added field in the local copy */
1461 hfs_zero_hidden_fields (cp
, finderinfo
);
1463 /* Don't expose a symlink's private type/creator. */
1464 if (vnode_islnk(vp
)) {
1465 struct FndrFileInfo
*fip
;
1467 fip
= (struct FndrFileInfo
*)&finderinfo
;
1472 /* Do the byte compare against the local copy */
1473 if (bcmp(finderinfo
, emptyfinfo
, sizeof(emptyfinfo
)) == 0) {
1479 * If there was other content, zero out everything except
1480 * type/creator and date added. First, save the date added.
1482 finfo
= cp
->c_finderinfo
;
1484 if (S_ISREG(cp
->c_attr
.ca_mode
) || S_ISLNK(cp
->c_attr
.ca_mode
)) {
1485 struct FndrExtendedFileInfo
*extinfo
= (struct FndrExtendedFileInfo
*)finfo
;
1486 date_added
= extinfo
->date_added
;
1487 write_gen_counter
= extinfo
->write_gen_counter
;
1488 document_id
= extinfo
->document_id
;
1489 } else if (S_ISDIR(cp
->c_attr
.ca_mode
)) {
1490 struct FndrExtendedDirInfo
*extinfo
= (struct FndrExtendedDirInfo
*)finfo
;
1491 date_added
= extinfo
->date_added
;
1492 write_gen_counter
= extinfo
->write_gen_counter
;
1493 document_id
= extinfo
->document_id
;
1496 if (vnode_islnk(vp
)) {
1497 /* Ignore type/creator */
1498 finderinfo_start
= &cp
->c_finderinfo
[8];
1499 finderinfo_size
= sizeof(cp
->c_finderinfo
) - 8;
1501 finderinfo_start
= &cp
->c_finderinfo
[0];
1502 finderinfo_size
= sizeof(cp
->c_finderinfo
);
1504 bzero(finderinfo_start
, finderinfo_size
);
1507 /* Now restore the date added */
1508 if (S_ISREG(cp
->c_attr
.ca_mode
) || S_ISLNK(cp
->c_attr
.ca_mode
)) {
1509 struct FndrExtendedFileInfo
*extinfo
= (struct FndrExtendedFileInfo
*)finfo
;
1510 extinfo
->date_added
= date_added
;
1511 extinfo
->write_gen_counter
= write_gen_counter
;
1512 extinfo
->document_id
= document_id
;
1513 } else if (S_ISDIR(cp
->c_attr
.ca_mode
)) {
1514 struct FndrExtendedDirInfo
*extinfo
= (struct FndrExtendedDirInfo
*)finfo
;
1515 extinfo
->date_added
= date_added
;
1516 extinfo
->write_gen_counter
= write_gen_counter
;
1517 extinfo
->document_id
= document_id
;
1520 /* Updating finderInfo updates change time and modified time */
1521 cp
->c_touch_chgtime
= TRUE
;
1522 cp
->c_flag
|= C_MODIFIED
;
1531 * Standard HFS only supports native FinderInfo and Resource Forks.
1533 if (hfsmp
->hfs_flags
& HFS_STANDARD
) {
1537 if (hfsmp
->hfs_attribute_vp
== NULL
) {
1541 iterator
= hfs_mallocz(sizeof(*iterator
));
1543 if ((result
= hfs_lock(cp
, HFS_EXCLUSIVE_LOCK
, HFS_LOCK_DEFAULT
))) {
1547 result
= hfs_buildattrkey(cp
->c_fileid
, ap
->a_name
, (HFSPlusAttrKey
*)&iterator
->key
);
1552 if (hfs_start_transaction(hfsmp
) != 0) {
1556 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
| SFL_BITMAP
, HFS_EXCLUSIVE_LOCK
);
1558 result
= remove_attribute_records(hfsmp
, iterator
);
1560 hfs_systemfile_unlock(hfsmp
, lockflags
);
1563 cp
->c_touch_chgtime
= TRUE
;
1565 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
, HFS_SHARED_LOCK
);
1567 /* If no more attributes exist, clear attribute bit */
1568 result
= file_attribute_exist(hfsmp
, cp
->c_fileid
);
1570 cp
->c_attr
.ca_recflags
&= ~kHFSHasAttributesMask
;
1571 cp
->c_flag
|= C_MODIFIED
;
1573 if (result
== EEXIST
) {
1577 hfs_systemfile_unlock(hfsmp
, lockflags
);
1579 /* If ACL was removed, clear security bit */
1580 if (strcmp(ap
->a_name
, KAUTH_FILESEC_XATTR
) == 0) {
1581 cp
->c_attr
.ca_recflags
&= ~kHFSHasSecurityMask
;
1582 cp
->c_flag
|= C_MODIFIED
;
1584 (void) hfs_update(vp
, 0);
1587 hfs_end_transaction(hfsmp
);
1591 hfs_free(iterator
, sizeof(*iterator
));
1592 return MacToVFSError(result
);
1596 * Removes a non rsrc-fork, non-finderinfo EA from the specified file ID.
1597 * Note that this results in a bit of code duplication for the xattr removal
1598 * path. This is done because it's a bit messy to deal with things without the
1599 * cnode. This function is used by exchangedata to port XATTRS to alternate
1600 * fileIDs while everything is locked, and the cnodes are in a transitional state.
1602 * Assumes that the cnode backing the fileid specified is LOCKED.
1606 hfs_removexattr_by_id (struct hfsmount
*hfsmp
, uint32_t fileid
, const char *xattr_name
) {
1607 struct BTreeIterator iter
; // allocated on the stack to avoid heap allocation mid-txn
1609 int started_txn
= 0;
1612 memset (&iter
, 0, sizeof(iter
));
1614 //position the B-Tree iter key before grabbing locks and starting a txn
1615 ret
= hfs_buildattrkey (fileid
, xattr_name
, (HFSPlusAttrKey
*)&iter
.key
);
1620 //note: this is likely a nested transaction since there is a global transaction cover
1621 if (hfs_start_transaction (hfsmp
) != 0) {
1628 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
| SFL_BITMAP
, HFS_EXCLUSIVE_LOCK
);
1630 //actually remove the EA from the tree
1631 ret
= remove_attribute_records(hfsmp
, &iter
);
1633 hfs_systemfile_unlock(hfsmp
, lockflags
);
1636 * NOTE: Responsibility of the caller to remove the "has XATTRs" bit in the catalog record
1637 * if this was the last EA.
1643 hfs_end_transaction(hfsmp
);
1646 return MacToVFSError(ret
);
1651 /* Check if any attribute record exist for given fileID. This function
1652 * is called by hfs_vnop_removexattr to determine if it should clear the
1653 * attribute bit in the catalog record or not.
1655 * Note - you must acquire a shared lock on the attribute btree before
1656 * calling this function.
1659 * EEXIST - If attribute record was found
1660 * 0 - Attribute was not found
1661 * (other) - Other error (such as EIO)
1664 file_attribute_exist(struct hfsmount
*hfsmp
, uint32_t fileID
)
1666 HFSPlusAttrKey
*key
;
1667 struct BTreeIterator
* iterator
= NULL
;
1668 struct filefork
*btfile
;
1671 // if there's no attribute b-tree we sure as heck
1672 // can't have any attributes!
1673 if (hfsmp
->hfs_attribute_vp
== NULL
) {
1677 iterator
= hfs_mallocz(sizeof(*iterator
));
1679 key
= (HFSPlusAttrKey
*)&iterator
->key
;
1681 result
= hfs_buildattrkey(fileID
, NULL
, key
);
1686 btfile
= VTOF(hfsmp
->hfs_attribute_vp
);
1687 result
= BTSearchRecord(btfile
, iterator
, NULL
, NULL
, NULL
);
1688 if (result
&& (result
!= btNotFound
)) {
1692 result
= BTIterateRecord(btfile
, kBTreeNextRecord
, iterator
, NULL
, NULL
);
1693 /* If no next record was found or fileID for next record did not match,
1694 * no more attributes exist for this fileID
1696 if ((result
&& (result
== btNotFound
)) || (key
->fileID
!= fileID
)) {
1703 hfs_free(iterator
, sizeof(*iterator
));
1709 * Remove all the records for a given attribute.
1711 * - Used by hfs_vnop_removexattr, hfs_vnop_setxattr and hfs_removeallattr.
1712 * - A transaction must have been started.
1713 * - The Attribute b-tree file must be locked exclusive.
1714 * - The Allocation Bitmap file must be locked exclusive.
1715 * - The iterator key must be initialized.
1718 remove_attribute_records(struct hfsmount
*hfsmp
, BTreeIterator
* iterator
)
1720 struct filefork
*btfile
;
1721 FSBufferDescriptor btdata
;
1722 HFSPlusAttrRecord attrdata
; /* 90 bytes */
1726 btfile
= VTOF(hfsmp
->hfs_attribute_vp
);
1728 btdata
.bufferAddress
= &attrdata
;
1729 btdata
.itemSize
= sizeof(attrdata
);
1730 btdata
.itemCount
= 1;
1731 result
= BTSearchRecord(btfile
, iterator
, &btdata
, &datasize
, NULL
);
1733 goto exit
; /* no records. */
1736 * Free the blocks from extent based attributes.
1738 * Note that the block references (btree records) are removed
1739 * before releasing the blocks in the allocation bitmap.
1741 if (attrdata
.recordType
== kHFSPlusAttrForkData
) {
1744 u_int32_t
*keystartblk
;
1746 if (datasize
< sizeof(HFSPlusAttrForkData
)) {
1747 printf("hfs: remove_attribute_records: bad record size %d (expecting %lu)\n", datasize
, sizeof(HFSPlusAttrForkData
));
1749 totalblks
= attrdata
.forkData
.theFork
.totalBlocks
;
1751 /* Process the first 8 extents. */
1752 extentblks
= count_extent_blocks(totalblks
, attrdata
.forkData
.theFork
.extents
);
1753 if (extentblks
> totalblks
)
1754 panic("hfs: remove_attribute_records: corruption...");
1755 if (BTDeleteRecord(btfile
, iterator
) == 0) {
1756 free_attr_blks(hfsmp
, extentblks
, attrdata
.forkData
.theFork
.extents
);
1758 totalblks
-= extentblks
;
1759 keystartblk
= &((HFSPlusAttrKey
*)&iterator
->key
)->startBlock
;
1761 /* Process any overflow extents. */
1763 *keystartblk
+= (u_int32_t
)extentblks
;
1765 result
= BTSearchRecord(btfile
, iterator
, &btdata
, &datasize
, NULL
);
1767 (attrdata
.recordType
!= kHFSPlusAttrExtents
) ||
1768 (datasize
< sizeof(HFSPlusAttrExtents
))) {
1769 printf("hfs: remove_attribute_records: BTSearchRecord: vol=%s, err=%d (%d), totalblks %d\n",
1770 hfsmp
->vcbVN
, MacToVFSError(result
), attrdata
.recordType
!= kHFSPlusAttrExtents
, totalblks
);
1772 break; /* break from while */
1774 /* Process the next 8 extents. */
1775 extentblks
= count_extent_blocks(totalblks
, attrdata
.overflowExtents
.extents
);
1776 if (extentblks
> totalblks
)
1777 panic("hfs: remove_attribute_records: corruption...");
1778 if (BTDeleteRecord(btfile
, iterator
) == 0) {
1779 free_attr_blks(hfsmp
, extentblks
, attrdata
.overflowExtents
.extents
);
1781 totalblks
-= extentblks
;
1784 result
= BTDeleteRecord(btfile
, iterator
);
1786 (void) BTFlushPath(btfile
);
1788 return (result
== btNotFound
? ENOATTR
: MacToVFSError(result
));
1793 * Retrieve the list of extended attribute names.
1796 hfs_vnop_listxattr(struct vnop_listxattr_args
*ap
)
1798 struct vnop_listxattr_args {
1799 struct vnodeop_desc *a_desc;
1804 vfs_context_t a_context;
1807 struct vnode
*vp
= ap
->a_vp
;
1808 struct cnode
*cp
= VTOC(vp
);
1809 struct hfsmount
*hfsmp
;
1810 uio_t uio
= ap
->a_uio
;
1811 struct BTreeIterator
* iterator
= NULL
;
1812 struct filefork
*btfile
;
1813 struct listattr_callback_state state
;
1814 user_addr_t user_start
= 0;
1815 user_size_t user_len
= 0;
1818 u_int8_t finderinfo
[32];
1821 if (VNODE_IS_RSRC(vp
)) {
1826 int compressed
= hfs_file_is_compressed(cp
, 1); /* 1 == don't take the cnode lock */
1827 #endif /* HFS_COMPRESSION */
1833 * Take the truncate lock; this serializes us against the ioctl
1834 * to truncate data & reset the decmpfs state
1835 * in the compressed file handler.
1837 hfs_lock_truncate(cp
, HFS_SHARED_LOCK
, HFS_LOCK_DEFAULT
);
1839 /* Now the regular cnode lock (shared) */
1840 if ((result
= hfs_lock(cp
, HFS_SHARED_LOCK
, HFS_LOCK_DEFAULT
))) {
1841 hfs_unlock_truncate(cp
, HFS_LOCK_DEFAULT
);
1846 * Make a copy of the cnode's finderinfo to a local so we can
1847 * zero out the date added field. Also zero out the private type/creator
1850 bcopy(cp
->c_finderinfo
, finderinfo
, sizeof(finderinfo
));
1851 hfs_zero_hidden_fields (cp
, finderinfo
);
1853 /* Don't expose a symlink's private type/creator. */
1854 if (vnode_islnk(vp
)) {
1855 struct FndrFileInfo
*fip
;
1857 fip
= (struct FndrFileInfo
*)&finderinfo
;
1863 /* If Finder Info is non-empty then export it's name. */
1864 if (bcmp(finderinfo
, emptyfinfo
, sizeof(emptyfinfo
)) != 0) {
1866 *ap
->a_size
+= sizeof(XATTR_FINDERINFO_NAME
);
1867 } else if ((user_size_t
)uio_resid(uio
) < sizeof(XATTR_FINDERINFO_NAME
)) {
1871 result
= uiomove(XATTR_FINDERINFO_NAME
,
1872 sizeof(XATTR_FINDERINFO_NAME
), uio
);
1877 /* If Resource Fork is non-empty then export it's name. */
1878 if (S_ISREG(cp
->c_mode
) && hfs_has_rsrc(cp
)) {
1880 if ((ap
->a_options
& XATTR_SHOWCOMPRESSION
) ||
1882 !decmpfs_hides_rsrc(ap
->a_context
, VTOCMP(vp
))
1884 #endif /* HFS_COMPRESSION */
1887 *ap
->a_size
+= sizeof(XATTR_RESOURCEFORK_NAME
);
1888 } else if ((user_size_t
)uio_resid(uio
) < sizeof(XATTR_RESOURCEFORK_NAME
)) {
1892 result
= uiomove(XATTR_RESOURCEFORK_NAME
,
1893 sizeof(XATTR_RESOURCEFORK_NAME
), uio
);
1901 * Standard HFS only supports native FinderInfo and Resource Forks.
1902 * Return at this point.
1904 if (hfsmp
->hfs_flags
& HFS_STANDARD
) {
1909 /* Bail if we don't have any extended attributes. */
1910 if ((hfsmp
->hfs_attribute_vp
== NULL
) ||
1911 (cp
->c_attr
.ca_recflags
& kHFSHasAttributesMask
) == 0) {
1915 btfile
= VTOF(hfsmp
->hfs_attribute_vp
);
1917 iterator
= hfs_mallocz(sizeof(*iterator
));
1919 result
= hfs_buildattrkey(cp
->c_fileid
, NULL
, (HFSPlusAttrKey
*)&iterator
->key
);
1924 * Lock the user's buffer here so that we won't fault on
1925 * it in uiomove while holding the attributes b-tree lock.
1927 if (uio
&& uio_isuserspace(uio
)) {
1928 user_start
= uio_curriovbase(uio
);
1929 user_len
= uio_curriovlen(uio
);
1931 if ((result
= vslock(user_start
, user_len
)) != 0) {
1936 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
, HFS_SHARED_LOCK
);
1938 result
= BTSearchRecord(btfile
, iterator
, NULL
, NULL
, NULL
);
1939 if (result
&& result
!= btNotFound
) {
1940 hfs_systemfile_unlock(hfsmp
, lockflags
);
1944 state
.fileID
= cp
->c_fileid
;
1949 state
.showcompressed
= !compressed
|| ap
->a_options
& XATTR_SHOWCOMPRESSION
;
1950 state
.ctx
= ap
->a_context
;
1952 #endif /* HFS_COMPRESSION */
1955 * Process entries starting just after iterator->key.
1957 result
= BTIterateRecords(btfile
, kBTreeNextRecord
, iterator
,
1958 (IterateCallBackProcPtr
)listattr_callback
, &state
);
1959 hfs_systemfile_unlock(hfsmp
, lockflags
);
1961 *ap
->a_size
+= state
.size
;
1964 if (state
.result
|| result
== btNotFound
)
1965 result
= state
.result
;
1969 vsunlock(user_start
, user_len
, TRUE
);
1971 hfs_free(iterator
, sizeof(*iterator
));
1973 hfs_unlock_truncate(cp
, HFS_LOCK_DEFAULT
);
1975 return MacToVFSError(result
);
1980 * Callback - called for each attribute record
1983 listattr_callback(const HFSPlusAttrKey
*key
, __unused
const HFSPlusAttrData
*data
, struct listattr_callback_state
*state
)
1985 char attrname
[XATTR_MAXNAMELEN
+ 1];
1989 if (state
->fileID
!= key
->fileID
) {
1991 return (0); /* stop */
1994 * Skip over non-primary keys
1996 if (key
->startBlock
!= 0) {
1997 return (1); /* continue */
2000 /* Convert the attribute name into UTF-8. */
2001 result
= utf8_encodestr(key
->attrName
, key
->attrNameLen
* sizeof(UniChar
),
2002 (u_int8_t
*)attrname
, (size_t *)&bytecount
, sizeof(attrname
), '/', 0);
2004 state
->result
= result
;
2005 return (0); /* stop */
2007 bytecount
++; /* account for null termination char */
2009 if (xattr_protected(attrname
))
2010 return (1); /* continue */
2013 if (!state
->showcompressed
&& decmpfs_hides_xattr(state
->ctx
, VTOCMP(state
->vp
), attrname
) )
2014 return 1; /* continue */
2015 #endif /* HFS_COMPRESSION */
2017 if (state
->uio
== NULL
) {
2018 state
->size
+= bytecount
;
2020 if (bytecount
> uio_resid(state
->uio
)) {
2021 state
->result
= ERANGE
;
2022 return (0); /* stop */
2024 result
= uiomove((caddr_t
) attrname
, bytecount
, state
->uio
);
2026 state
->result
= result
;
2027 return (0); /* stop */
2030 return (1); /* continue */
2034 * Remove all the attributes from a cnode.
2036 * This function creates/ends its own transaction so that each
2037 * attribute is deleted in its own transaction (to avoid having
2038 * a transaction grow too large).
2040 * This function takes the necessary locks on the attribute
2041 * b-tree file and the allocation (bitmap) file.
2043 * NOTE: Upon sucecss, this function will return with an open
2044 * transaction. The reason we do it this way is because when we
2045 * delete the last attribute, we must make sure the flag in the
2046 * catalog record that indicates there are no more records is cleared.
2047 * The caller is responsible for doing this and *must* do it before
2048 * ending the transaction.
2051 hfs_removeallattr(struct hfsmount
*hfsmp
, u_int32_t fileid
,
2052 bool *open_transaction
)
2054 BTreeIterator
*iterator
= NULL
;
2055 HFSPlusAttrKey
*key
;
2056 struct filefork
*btfile
;
2057 int result
, lockflags
= 0;
2059 *open_transaction
= false;
2061 if (hfsmp
->hfs_attribute_vp
== NULL
)
2064 btfile
= VTOF(hfsmp
->hfs_attribute_vp
);
2066 iterator
= hfs_mallocz(sizeof(BTreeIterator
));
2068 key
= (HFSPlusAttrKey
*)&iterator
->key
;
2070 /* Loop until there are no more attributes for this file id */
2072 if (!*open_transaction
)
2073 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
, HFS_SHARED_LOCK
);
2075 (void) hfs_buildattrkey(fileid
, NULL
, key
);
2076 result
= BTIterateRecord(btfile
, kBTreeNextRecord
, iterator
, NULL
, NULL
);
2077 if (result
|| key
->fileID
!= fileid
)
2080 hfs_systemfile_unlock(hfsmp
, lockflags
);
2083 if (*open_transaction
) {
2084 hfs_end_transaction(hfsmp
);
2085 *open_transaction
= false;
2088 if (hfs_start_transaction(hfsmp
) != 0) {
2093 *open_transaction
= true;
2095 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
| SFL_BITMAP
, HFS_EXCLUSIVE_LOCK
);
2097 result
= remove_attribute_records(hfsmp
, iterator
);
2099 #if HFS_XATTR_VERBOSE
2101 printf("hfs_removeallattr: unexpected err %d\n", result
);
2107 hfs_free(iterator
, sizeof(*iterator
));
2110 hfs_systemfile_unlock(hfsmp
, lockflags
);
2112 result
= result
== btNotFound
? 0 : MacToVFSError(result
);
2114 if (result
&& *open_transaction
) {
2115 hfs_end_transaction(hfsmp
);
2116 *open_transaction
= false;
2123 hfs_xattr_init(struct hfsmount
* hfsmp
)
2126 if (ISSET(hfsmp
->hfs_flags
, HFS_STANDARD
))
2131 * If there isn't an attributes b-tree then create one.
2133 if ((hfsmp
->hfs_attribute_vp
== NULL
) &&
2134 !(hfsmp
->hfs_flags
& HFS_READ_ONLY
)) {
2135 (void) hfs_create_attr_btree(hfsmp
, ATTRIBUTE_FILE_NODE_SIZE
,
2136 getnodecount(hfsmp
, ATTRIBUTE_FILE_NODE_SIZE
));
2138 if (hfsmp
->hfs_attribute_vp
)
2139 hfsmp
->hfs_max_inline_attrsize
= getmaxinlineattrsize(hfsmp
->hfs_attribute_vp
);
2143 * Enable/Disable volume attributes stored as EA for root file system.
2144 * Supported attributes are -
2145 * 1. Extent-based Extended Attributes
2148 hfs_set_volxattr(struct hfsmount
*hfsmp
, unsigned int xattrtype
, int state
)
2150 struct BTreeIterator
* iterator
= NULL
;
2151 struct filefork
*btfile
;
2156 if (hfsmp
->hfs_flags
& HFS_STANDARD
) {
2160 if (xattrtype
!= HFSIOC_SET_XATTREXTENTS_STATE
) {
2165 * If there isn't an attributes b-tree then create one.
2167 if (hfsmp
->hfs_attribute_vp
== NULL
) {
2168 result
= hfs_create_attr_btree(hfsmp
, ATTRIBUTE_FILE_NODE_SIZE
,
2169 getnodecount(hfsmp
, ATTRIBUTE_FILE_NODE_SIZE
));
2175 iterator
= hfs_mallocz(sizeof(*iterator
));
2178 * Build a b-tree key.
2179 * We use the root's parent id (1) to hold this volume attribute.
2181 (void) hfs_buildattrkey(kHFSRootParentID
, XATTR_XATTREXTENTS_NAME
,
2182 (HFSPlusAttrKey
*)&iterator
->key
);
2184 /* Start a transaction for our changes. */
2185 if (hfs_start_transaction(hfsmp
) != 0) {
2189 btfile
= VTOF(hfsmp
->hfs_attribute_vp
);
2191 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
, HFS_EXCLUSIVE_LOCK
);
2194 /* Remove the attribute. */
2195 result
= BTDeleteRecord(btfile
, iterator
);
2196 if (result
== btNotFound
)
2199 FSBufferDescriptor btdata
;
2200 HFSPlusAttrData attrdata
;
2203 datasize
= sizeof(attrdata
);
2204 btdata
.bufferAddress
= &attrdata
;
2205 btdata
.itemSize
= datasize
;
2206 btdata
.itemCount
= 1;
2207 attrdata
.recordType
= kHFSPlusAttrInlineData
;
2208 attrdata
.reserved
[0] = 0;
2209 attrdata
.reserved
[1] = 0;
2210 attrdata
.attrSize
= 2;
2211 attrdata
.attrData
[0] = 0;
2212 attrdata
.attrData
[1] = 0;
2214 /* Insert the attribute. */
2215 result
= BTInsertRecord(btfile
, iterator
, &btdata
, datasize
);
2216 if (result
== btExists
)
2219 (void) BTFlushPath(btfile
);
2221 hfs_systemfile_unlock(hfsmp
, lockflags
);
2223 /* Finish the transaction of our changes. */
2224 hfs_end_transaction(hfsmp
);
2226 /* Update the state in the mount point */
2227 hfs_lock_mount (hfsmp
);
2229 hfsmp
->hfs_flags
&= ~HFS_XATTR_EXTENTS
;
2231 hfsmp
->hfs_flags
|= HFS_XATTR_EXTENTS
;
2233 hfs_unlock_mount (hfsmp
);
2236 hfs_free(iterator
, sizeof(*iterator
));
2237 return MacToVFSError(result
);
2242 * hfs_attrkeycompare - compare two attribute b-tree keys.
2244 * The name portion of the key is compared using a 16-bit binary comparison.
2245 * This is called from the b-tree code.
2248 hfs_attrkeycompare(HFSPlusAttrKey
*searchKey
, HFSPlusAttrKey
*trialKey
)
2250 u_int32_t searchFileID
, trialFileID
;
2253 searchFileID
= searchKey
->fileID
;
2254 trialFileID
= trialKey
->fileID
;
2257 if (searchFileID
> trialFileID
) {
2259 } else if (searchFileID
< trialFileID
) {
2262 u_int16_t
* str1
= &searchKey
->attrName
[0];
2263 u_int16_t
* str2
= &trialKey
->attrName
[0];
2264 int length1
= searchKey
->attrNameLen
;
2265 int length2
= trialKey
->attrNameLen
;
2269 if (length1
< length2
) {
2272 } else if (length1
> length2
) {
2295 * Names are equal; compare startBlock
2297 if (searchKey
->startBlock
== trialKey
->startBlock
) {
2300 return (searchKey
->startBlock
< trialKey
->startBlock
? -1 : 1);
2309 * hfs_buildattrkey - build an Attribute b-tree key
2312 hfs_buildattrkey(u_int32_t fileID
, const char *attrname
, HFSPlusAttrKey
*key
)
2315 size_t unicodeBytes
= 0;
2317 if (attrname
!= NULL
) {
2319 * Convert filename from UTF-8 into Unicode
2321 result
= utf8_decodestr((const u_int8_t
*)attrname
, strlen(attrname
), key
->attrName
,
2322 &unicodeBytes
, sizeof(key
->attrName
), 0, 0);
2324 if (result
!= ENAMETOOLONG
)
2325 result
= EINVAL
; /* name has invalid characters */
2328 key
->attrNameLen
= unicodeBytes
/ sizeof(UniChar
);
2329 key
->keyLength
= kHFSPlusAttrKeyMinimumLength
+ unicodeBytes
;
2331 key
->attrNameLen
= 0;
2332 key
->keyLength
= kHFSPlusAttrKeyMinimumLength
;
2335 key
->fileID
= fileID
;
2336 key
->startBlock
= 0;
2342 * getnodecount - calculate starting node count for attributes b-tree.
2345 getnodecount(struct hfsmount
*hfsmp
, size_t nodesize
)
2347 u_int64_t freebytes
;
2348 u_int64_t calcbytes
;
2351 * 10.4: Scale base on current catalog file size (20 %) up to 20 MB.
2352 * 10.5: Attempt to be as big as the catalog clump size.
2354 * Use no more than 10 % of the remaining free space.
2356 freebytes
= (u_int64_t
)hfs_freeblks(hfsmp
, 0) * (u_int64_t
)hfsmp
->blockSize
;
2358 calcbytes
= MIN(hfsmp
->hfs_catalog_cp
->c_datafork
->ff_size
/ 5, 20 * 1024 * 1024);
2360 calcbytes
= MAX(calcbytes
, hfsmp
->hfs_catalog_cp
->c_datafork
->ff_clumpsize
);
2362 calcbytes
= MIN(calcbytes
, freebytes
/ 10);
2364 return (MAX(2, (int)(calcbytes
/ nodesize
)));
2369 * getmaxinlineattrsize - calculate maximum inline attribute size.
2371 * This yields 3,802 bytes for an 8K node size.
2374 getmaxinlineattrsize(struct vnode
* attrvp
)
2376 struct BTreeInfoRec btinfo
;
2377 size_t nodesize
= ATTRIBUTE_FILE_NODE_SIZE
;
2380 if (attrvp
!= NULL
) {
2381 (void) hfs_lock(VTOC(attrvp
), HFS_SHARED_LOCK
, HFS_LOCK_DEFAULT
);
2382 if (BTGetInformation(VTOF(attrvp
), 0, &btinfo
) == 0)
2383 nodesize
= btinfo
.nodeSize
;
2384 hfs_unlock(VTOC(attrvp
));
2387 maxsize
-= sizeof(BTNodeDescriptor
); /* minus node descriptor */
2388 maxsize
-= 3 * sizeof(u_int16_t
); /* minus 3 index slots */
2389 maxsize
/= 2; /* 2 key/rec pairs minumum */
2390 maxsize
-= sizeof(HFSPlusAttrKey
); /* minus maximum key size */
2391 maxsize
-= sizeof(HFSPlusAttrData
) - 2; /* minus data header */
2392 maxsize
&= 0xFFFFFFFE; /* multiple of 2 bytes */
2398 * Initialize vnode for attribute data I/O.
2402 * - the attrdata vnode is initialized as hfsmp->hfs_attrdata_vp
2403 * - an iocount is taken on the attrdata vnode which exists
2404 * for the entire duration of the mount. It is only dropped
2406 * - the attrdata cnode is not locked
2409 * - returns non-zero value
2410 * - the caller does not have to worry about any locks or references
2412 int init_attrdata_vnode(struct hfsmount
*hfsmp
)
2416 struct cat_desc cat_desc
;
2417 struct cat_attr cat_attr
;
2418 struct cat_fork cat_fork
;
2419 int newvnode_flags
= 0;
2421 bzero(&cat_desc
, sizeof(cat_desc
));
2422 cat_desc
.cd_parentcnid
= kHFSRootParentID
;
2423 cat_desc
.cd_nameptr
= (const u_int8_t
*)hfs_attrdatafilename
;
2424 cat_desc
.cd_namelen
= strlen(hfs_attrdatafilename
);
2425 cat_desc
.cd_cnid
= kHFSAttributeDataFileID
;
2426 /* Tag vnode as system file, note that we can still use cluster I/O */
2427 cat_desc
.cd_flags
|= CD_ISMETA
;
2429 bzero(&cat_attr
, sizeof(cat_attr
));
2430 cat_attr
.ca_linkcount
= 1;
2431 cat_attr
.ca_mode
= S_IFREG
;
2432 cat_attr
.ca_fileid
= cat_desc
.cd_cnid
;
2433 cat_attr
.ca_blocks
= hfsmp
->totalBlocks
;
2436 * The attribute data file is a virtual file that spans the
2437 * entire file system space.
2439 * Each extent-based attribute occupies a unique portion of
2440 * in this virtual file. The cluster I/O is done using actual
2441 * allocation block offsets so no additional mapping is needed
2442 * for the VNOP_BLOCKMAP call.
2444 * This approach allows the attribute data to be cached without
2445 * incurring the high cost of using a separate vnode per attribute.
2447 * Since we need to acquire the attribute b-tree file lock anyways,
2448 * the virtual file doesn't introduce any additional serialization.
2450 bzero(&cat_fork
, sizeof(cat_fork
));
2451 cat_fork
.cf_size
= (u_int64_t
)hfsmp
->totalBlocks
* (u_int64_t
)hfsmp
->blockSize
;
2452 cat_fork
.cf_blocks
= hfsmp
->totalBlocks
;
2453 cat_fork
.cf_extents
[0].startBlock
= 0;
2454 cat_fork
.cf_extents
[0].blockCount
= cat_fork
.cf_blocks
;
2456 result
= hfs_getnewvnode(hfsmp
, NULL
, NULL
, &cat_desc
, 0, &cat_attr
,
2457 &cat_fork
, &vp
, &newvnode_flags
);
2459 hfsmp
->hfs_attrdata_vp
= vp
;
2461 vnode_setnoreadahead(hfsmp
->hfs_attrdata_vp
);
2463 hfs_unlock(VTOC(vp
));
2468 /* The following code (inside NEW_XATTR) was ported from apfs. */
2471 * This is the same as fext2cluster_check(), but overflow is asserted against.
2473 * This is useful for places which hold the invariant that `fext' is already
2474 * representable when translated but additionally want to assert that that is
2478 fext2cluster(const HFSPlusExtentDescriptor
*fext
, uint32_t fs_bsize
)
2481 const bool ok
= fext2cluster_check(fext
, fs_bsize
, &off
);
2487 * Translate `fext' to a cluster layer virtual offset, set via `out', that is
2488 * suitable for IO to the single xattr vnode.
2490 * For any particular file extent, this will map to a unique logical offset
2491 * that may be used to key into the ubc. The returned value has the property
2492 * such that file extents on adjacent physical blocks will always be mapped to
2493 * different page sized multiples. Internally, this just multiplies by the
2494 * larger of the pagesize and the blocksize (see xattr_cluster_scale() for the
2495 * derivation). For further details on the importance of this in the overall
2496 * scheme of xattr IO, see uio_set_fext().
2498 * This return trues if `fext' is representable in cluster layer virtual
2499 * offset. It may return false for corrupted file extents:
2500 * - extents that likely extend beyond the size of the underlying drive
2502 * The second point should not happen under normal circumstances even for large
2503 * drives. Large drives (by the logic in hfs_newfs()) are automatically
2504 * formatted to use large block sizes: drives sized >= 16TB use 16kiB fs
2505 * blocksize, at which point the virtual offset computed is equal to the device
2506 * offset (even on a 16kiB pagesize system).
2507 * So as long as a drive does not exceed 2^63 bytes in capacity (which is the
2508 * precision of `off_t'), the internal multiplication should not overflow.
2511 fext2cluster_check(const HFSPlusExtentDescriptor
*fext
, uint32_t fs_bsize
,
2514 const uint64_t pbn
= fext
->startBlock
;
2516 // xattrs has invalid start block
2523 if (__builtin_mul_overflow(pbn
, xattr_cluster_scale(fs_bsize
), &off
)) {
2529 // the whole fext should be in range
2530 if (__builtin_add_overflow(off
, fext
->blockCount
* fs_bsize
, &off
)) {
2534 // don't exceed signed 64bit precision
2535 if (off
> INT64_MAX
) {
2543 * Return the scale factor for translating xattr physical block numbers into
2544 * a logical offset into the single xattr vnode's ubc.
2546 * Translated blocks have two key requirements:
2547 * - They must not overlap.
2548 * Otherwise two different blocks' contents will collide.
2549 * - They must not fall within the same page.
2550 * Otherwise reading a whole page may pull in multiple xattr's blocks, but
2551 * only decrypt with one of the xattr's keys.
2553 * A table of all possible configurations:
2554 * pagesize, fs blocksize, scale
2565 * This may be expressed as
2566 * scale = max(pagesize, fs blocksize)
2569 xattr_cluster_scale(uint32_t fs_bsize
)
2571 return MAX(PAGE_SIZE
, fs_bsize
);
2575 * See off_in_range().
2578 off_in_range2(uint64_t off
, uint64_t start
, uint64_t len
)
2580 return (start
<= off
) && (off
< (start
+ len
));
2584 * Return true if `off' returned from fext2cluster() was produced from
2588 cluster_off_in_fext(uint64_t off
, const HFSPlusExtentDescriptor
*fext
,
2591 return off_in_range2(off
, fext2cluster(fext
, fs_bsize
), fext
->blockCount
* fs_bsize
);
2595 * Allocate space to save a file extent reference for xattr IO.
2597 * This provides a mechanism to communicate file extents from top level, stream
2598 * based xattr IO functions down into lower level IO functions
2599 * (_blockmap() and _strategy()).
2601 * This doesn't really return a file extent, it returns a reference into
2602 * `info->xattr_fexts' which may be pointed to a file extent reference.
2603 * Alternatively this could just return an integral index index, but then we'd
2604 * need some way to signal failure.
2606 * Note: the returned reference cannot be assigned directly; it must be set
2607 * via xattr_fext_set() to correctly synchronize with a racing call to
2608 * xattr_fext_find().
2610 * This call will not block; it will return NULL if no free spaces are
2611 * available. On success, follow with a call to xattr_fext_free().
2613 * In terms of the implementation, this is a basic zone allocator for thread
2614 * local storage in disguise. it supports only one file extent to be
2615 * set by up to 64 threads.
2616 * For further details, see the documentation for each field above the
2617 * definition of `xattr_io_info_t'.
2619 static const HFSPlusExtentDescriptor
**
2620 xattr_fext_alloc(xattr_io_info_t
*info
)
2622 const HFSPlusExtentDescriptor
**ret
;
2625 // search for the first free bit
2626 lck_spin_lock(&info
->lock
);
2627 if (!xbm_find_free(info
, &i
)) {
2633 // mark that position as allocated
2634 xbm_set_used(info
, i
);
2635 ret
= &info
->xattr_fexts
[i
];
2636 xattr_assert(!*ret
);
2639 lck_spin_unlock(&info
->lock
);
2644 * Free the memory associated with an xattr fext referenced return from
2645 * xattr_fext_alloc().
2646 * This simply amounts to clearing the bit within `info->free_bm' that
2647 * corresponds to `xfext'. While not strictly neccessary, we also clear out the
2648 * xattr fext itself to hold the invariant that a clear bit within the free
2649 * bitmap has a corresponding NULL fext reference.
2652 xattr_fext_free(xattr_io_info_t
*info
, const HFSPlusExtentDescriptor
**xfext
)
2654 lck_spin_lock(&info
->lock
);
2655 const size_t i
= xattr_fext_index(info
, xfext
);
2656 xbm_clear_used(info
, i
);
2657 info
->xattr_fexts
[i
] = NULL
;
2658 lck_spin_unlock(&info
->lock
);
2662 * Given an allocated xattr fext from xattr_fext_alloc() assign it to reference
2663 * `fext'. A copy of this fext may be returned by a subsequent call to
2664 * xattr_fext_find().
2666 * This may be called multiple times for the same value of `xfext'.
2667 * `fext' will be borrowed until a subsequent call to xattr_fext_set() for a
2668 * different file extent or xattr_fext_free() for `xfext'. It must have
2669 * lifetime that spans at least as long from when it's first set to when it's
2670 * cleared either by xattr_fext_free() or xattr_fext_clear().
2672 * Note: `fext' may be introspected by other threads via xattr_fext_find()
2673 * (and in terms of getxattr(), two threads may use each other's file extents
2674 * if they race to read the same xattr).
2677 xattr_fext_set(xattr_io_info_t
*info
, const HFSPlusExtentDescriptor
**xfext
,
2678 const HFSPlusExtentDescriptor
*fext
)
2680 xattr_assert(xbm_valid_index(info
, xattr_fext_index(info
, xfext
)));
2681 lck_spin_lock(&info
->lock
);
2683 lck_spin_unlock(&info
->lock
);
2687 * Given a cluster layer virtual offset, attempt to look up a file extent set
2688 * via a previous call to xattr_fext_set().
2690 * If such a fext is found, its value is copied to `out' and true is returned.
2691 * Note: `out' should reference wired memory: it will be stored to while a spin
2692 * lock is held; accesses must not fault.
2694 * off_out will contain the "unvirtualized" offset
2697 hfs_xattr_fext_find(xattr_io_info_t
*info
, uint32_t fs_bsize
, uint64_t off
,
2698 HFSPlusExtentDescriptor
*out
, uint64_t *off_out
)
2701 lck_spin_lock(&info
->lock
);
2703 // search through all in-use fexts
2704 xbm_iter_t iter
= xbm_make_iter(info
->free_bm
, xbm_size(info
));
2705 while(xbm_iter_next(&iter
)) {
2706 const HFSPlusExtentDescriptor
*fext
= info
->xattr_fexts
[xbm_iter_peek(&iter
)];
2707 if (!fext
|| !cluster_off_in_fext(off
, fext
, fs_bsize
)) {
2710 // `off' intersects; return `fext'
2716 lck_spin_unlock(&info
->lock
);
2719 *off_out
= ((uint64_t)out
->startBlock
* fs_bsize
) + off
- fext2cluster(out
, fs_bsize
);
2726 * Given an allocated xattr fext, clear its reference to any `fext' passed to a
2727 * previous call to xattr_fext_set().
2729 * This will end the lifetime of such a fext and prevent xattr_fext_find() from
2730 * taking a reference to it. From here, its backing memory can be deallocated.
2732 * Unlike xattr_fext_free(), `xfext' will remain allocated and it may passed to
2733 * xattr_fext_set() again.
2736 xattr_fext_clear(xattr_io_info_t
*info
, const HFSPlusExtentDescriptor
**xfext
)
2738 xattr_assert(xbm_valid_index(info
, xattr_fext_index(info
, xfext
)));
2739 lck_spin_lock(&info
->lock
);
2741 lck_spin_unlock(&info
->lock
);
2745 * For an xattr file extent, `xfext', returned from a previous call to
2746 * xattr_fext_alloc(), return its index within `info->xattr_fexts'.
2749 xattr_fext_index(const xattr_io_info_t
*info
, const HFSPlusExtentDescriptor
**xfext
)
2751 xattr_assert((info
->xattr_fexts
<= xfext
) &&
2752 (xfext
< &info
->xattr_fexts
[xbm_size(info
)]));
2753 return ((uintptr_t)xfext
- (uintptr_t)info
->xattr_fexts
) / sizeof(*xfext
);
2757 bitmap_set_range(uint64_t *bm
, int64_t index
, int64_t count
)
2759 int dstshift0
, dstshift1
;
2760 uint64_t dstmask0
, dstmask1
;
2763 dstshift0
= index
% 64;
2764 dstshift1
= 64 - (index
% 64);
2765 dstmask0
= ~0ULL << (index
% 64);
2766 dstmask1
= (dstshift1
== 64) ? 0ULL : (~0ULL >> (64 - (index
% 64)));
2769 while (count
>= 64) {
2770 bm
[bmi
] = (bm
[bmi
] & ~dstmask0
) | ((~0ULL << dstshift0
) & dstmask0
);
2772 bm
[bmi
+ 1] = (bm
[bmi
+ 1] & ~dstmask1
) | ((~0ULL >> dstshift1
) & dstmask1
);
2777 // adjust dstmask to cover just the bits remaining
2778 dstmask0
= ((1ULL << count
) - 1) << (index
% 64);
2779 dstmask1
= (dstshift1
== 64) ? 0ULL : (((1ULL << count
) - 1) >> (64 - (index
% 64)));
2780 bm
[bmi
] = (bm
[bmi
] & ~dstmask0
) | ((~0ULL << dstshift0
) & dstmask0
);
2781 if ((count
> (64 - dstshift0
)) && dstmask1
)
2782 bm
[bmi
+ 1] = (bm
[bmi
+ 1] & ~dstmask1
) | ((~0ULL >> dstshift1
) & dstmask1
);
2787 bitmap_clear_range(uint64_t *bm
, int64_t index
, int64_t count
)
2789 int dstshift0
, dstshift1
;
2790 uint64_t dstmask0
, dstmask1
;
2793 dstshift0
= index
% 64;
2794 dstshift1
= 64 - (index
% 64);
2795 dstmask0
= ~0ULL << (index
% 64);
2796 dstmask1
= (dstshift1
== 64) ? 0ULL : (~0ULL >> (64 - (index
% 64)));
2799 while (count
>= 64) {
2800 bm
[bmi
] = (bm
[bmi
] & ~dstmask0
) | ((0ULL << dstshift0
) & dstmask0
);
2802 bm
[bmi
+ 1] = (bm
[bmi
+ 1] & ~dstmask1
) | ((0ULL >> dstshift1
) & dstmask1
);
2807 // adjust dstmask to cover just the bits remaining
2808 dstmask0
= ((1ULL << count
) - 1) << (index
% 64);
2809 dstmask1
= (dstshift1
== 64) ? 0ULL : (((1ULL << count
) - 1) >> (64 - (index
% 64)));
2810 bm
[bmi
] = (bm
[bmi
] & ~dstmask0
) | ((0ULL << dstshift0
) & dstmask0
);
2811 if ((count
> (64 - dstshift0
)) && dstmask1
)
2812 bm
[bmi
+ 1] = (bm
[bmi
+ 1] & ~dstmask1
) | ((0ULL >> dstshift1
) & dstmask1
);
2819 return (val
== 0) ? 64 : __builtin_ctzll(val
);
2822 // search forwards through range and return index of first "bit" (0 or 1) encountered
2824 bitmap_range_find_first(int bit
, const uint64_t *bm
, int64_t index
, int64_t count
, int64_t *clear_index
)
2826 int64_t bmi
, check_count
;
2832 check_count
= 64 - (index
% 64);
2833 if (check_count
> count
)
2834 check_count
= count
;
2835 val
= bm
[bmi
] >> (index
% 64);
2839 if (pos
< check_count
) {
2840 *clear_index
= index
+ pos
;
2843 index
+= check_count
;
2844 count
-= check_count
;
2851 * Search for the first free (clear) bit within `info's free xattr fext bitmap.
2852 * Return false if no bits are clear.
2853 * If some bit is found, its index is set to `*out' and true is returned.
2856 xbm_find_free(const xattr_io_info_t
*info
, size_t *out
)
2858 return bm_find(info
->free_bm
, 0, xbm_size(info
), false, out
);
2862 * Set the bit at index `i' within `info's underlying free xattr fext bitmap.
2864 * info->lock must be held.
2865 * It only makes sense to operate on one bit at a time, so this wraps
2866 * bitmap_set_range().
2869 xbm_set_used(xattr_io_info_t
*info
, size_t i
)
2871 xattr_assert(xbm_valid_index(info
, i
));
2872 bitmap_set_range(info
->free_bm
, i
, 1);
2876 * Clear the bit at index `i' within `info's underlying free xattr fext bitmap.
2877 * This is the opposite of xbm_set_used().
2880 xbm_clear_used(xattr_io_info_t
*info
, size_t i
)
2882 xattr_assert(xbm_valid_index(info
, i
));
2883 bitmap_clear_range(info
->free_bm
, i
, 1);
2887 * Return whether the given bitmap index is a valid index into `info's free
2891 xbm_valid_index(const xattr_io_info_t
*info
, int64_t i
)
2893 return bm_valid_index(i
, xbm_size(info
));
2897 #define ARRAY_SIZE(A) (sizeof(A) / sizeof(A[0]))
2901 * Return the total number of *bits* in `info's free xattr fext bitmap.
2904 xbm_size(const xattr_io_info_t
*info
)
2906 // one bit per xattr fext
2907 return ARRAY_SIZE(info
->xattr_fexts
);
2911 * Factory for an iterator over all the set (true value) bits in `bitmap'.
2912 * `len' is the length in *bits* of `bitmap'.
2914 * The iterator is created in an uninitialized state, a call to xbm_iter_next()
2915 * is required to find the first set bit (this is different than
2916 * make_fext_iterator()). As a consequence of this, an iterator may iterate
2917 * zero times if no bits within `bitmap' are set. After each successful call to
2918 * xbm_iter_next(), xbm_iter_peek() will return the zero-based index of each
2921 * The intended use is along the lines of:
2922 * uint64_t *bm = ...; // a bitmap 123 bits (two uint64_ts) long
2923 * xbm_iterator_t iter = xbm_make_iter(bm, 123);
2924 * while(xbm_iter_next(&iter)) {
2925 * size_t i = xbm_iter_peek(&iter);
2926 * printf("index %lu is set\n", i);
2929 * In terms of the iterator internals, we hold the invariant that a valid
2930 * iterator always has `i' in [0, len). A valid iterator is one for which
2931 * xbm_iter_peek() will return an index in range of `bitmap'. To bootstrap the
2932 * first iteration, `i' is set to SIZE_MAX; further details are in
2936 xbm_make_iter(const uint64_t *bitmap
, size_t len
)
2939 return (xbm_iter_t
) {
2941 .i
= SIZE_MAX
, // This will overflow-to-zero on the first call to xbm_iter_next()
2947 * Advance `iter' to internally reference the next set bit within the bitmap.
2948 * If there are no more set bits, this returns false.
2950 * On success, the index of the next set bit can be retrieved by
2953 * Internally, this searches for the first bit set at bit index >= iter->i+1.
2954 * For a new iterator, the value of `i' is initialized to SIZE_MAX so `i'+1
2955 * will unsigned integer overflow (which is well defined) to zero.
2958 xbm_iter_next(xbm_iter_t
*iter
)
2961 // find the next set bit > `i'
2962 const bool found
= bm_find(iter
->bitmap
, iter
->i
+ 1, iter
->len
, true, &i
);
2964 // if no bit is found, invalidate the iterator by setting i=len
2965 iter
->i
= found
? i
: iter
->len
;
2970 * Return the index a the set bit after a successful call to xbm_iter_next().
2973 xbm_iter_peek(const xbm_iter_t
*iter
)
2975 xattr_assert(iter
->i
< iter
->len
);
2980 * Search for the first bit with `value' within bitmap `bm' >= bit index `from'
2981 * for at most `len' *bits*. Whether such a bit exists is returned and if
2982 * that's the case, its bit index is written via `out'.
2984 * This is just a fancy wrapper around bitmap_range_find_first().
2987 bm_find(const uint64_t *bm
, size_t from
, size_t len
, bool value
, size_t *out
)
2989 xattr_assert(bm_valid_index(from
, len
));
2991 // search for `value' in [from, len)
2993 if (!bitmap_range_find_first(value
,
2994 const_cast(uint64_t *, bm
), from
, len
, &i
)) {
2998 // some bit found; check the returned index is valid
2999 xattr_assert(bm_valid_index(i
, len
));
3005 * Return true if `i' is a valid index into a bit of `len' bits.
3007 * The underlying bitmap_ routines operate on `int64_t' indices. This is
3008 * mainly to safely convert to `size_t'.
3011 bm_valid_index(int64_t i
, size_t len
)
3013 return (i
>= 0) && ((uint64_t)i
< len
);
3017 * Virtualize `uio' offset to target xattr `fext' before a call to
3020 * The computation of the IO offset is somewhat subtle. The reason for this
3021 * fact largely has to do with how exactly the single xattr vnode
3022 * (hfsmp->hfs_attrdata_vp) caches data for multiple xattrs. First,
3023 * some discussion on the motivation for the single xattr vnode design. At the
3024 * top level, xattr apis are quite different from normal file data apis. Some
3025 * key properties are:
3026 * - xattr IO apis do no support editing or random reads
3027 * - xattrs may not be mmapped
3028 * - any one file may have an arbitrary number of xattrs
3029 * To contrast with a normal file, each file has a corresponding vnode which in
3030 * turn has its own private ubc. The only way in which xattrs are actually like
3031 * files is that they the have the same size limits and in
3032 * terms of their implementation, they use the same on-disk structures.
3033 * The result of this is that it is too high overhead to have one vnode per
3034 * xattr, but to instead to maintain a disk block-type cache for xattr data.
3035 * This cache is implemented as the ubc of a virtual file known as the single
3036 * xattr vnode. Reads and writes are serviced by the cluster layer. The cluster
3037 * layer operates in units of the vm pagesize. On a system for which
3038 * pagesize > fs blocksize, then the naïve approach of using an identity
3039 * mapping between ubc logical offset and device offset poses a challenge.
3040 * Consider the following scenario:
3044 * On disk, we have two xattrs that reside on adjacent blocks:
3046 * [aaaa|aaaa|bbbb|bbbb]
3049 * Suppose we want to just read xattr A -- pbn 4 and 5 -- the cluster layer can
3050 * issue just an 8k IO, but it will store it in memory as a whole page, so we
3055 * [aaaa|aaaa|0000|0000]
3058 * [aaaa|aaaa|bbbb|bbbb]
3061 * A subsequent read for pbn 6 or 7, as a part of xattr B, will find the page
3062 * already cached in the ubc and erroneously return zeros.
3063 * Instead, we could have the cluster layer issue the full 16k IO, but then we
3064 * run into encryption issues on per-file (really per-xattr) key volumes
3068 * [aaaa|aaaa|O!#W|JF%R]
3071 * [asdf|ghjk|ZXCV|BNM,] encrypted
3072 * [aaaa|aaaa|bbbb|bbbb] unencrypted
3075 * In this case, the issue is that we have the crypto state available to read
3076 * xattr A, but we do not have the crypto state for xattr B, so we would
3077 * incorrectly decrypt pbn 6, 7 in the same IO.
3079 * The solution to this is to use a scaled mapping between ubc logical offset
3080 * and device offset. In this case, we use
3081 * logical offset = physical block number * pagesize
3082 * and result looks like
3086 * [aaaa|aaaa|0000|0000] ... [bbbb|bbbb|0000|0000]
3090 * [asdf|ghjk|ZXCV|BNM,] encrypted
3091 * [aaaa|aaaa|bbbb|bbbb] unencrypted
3094 * In memory, xattr A occupies the virtual range of [64k, 96k), but its
3095 * contents are representable in the first 8k out of [64k, 80k). Note that the
3096 * mapping here is not per block but rather per xattr file extent. The contents
3097 * tracked by an individual file extent are logically contiguous in memory. In
3098 * the example above, xattr A has one file extent spanning [0, 8k). Suppose it
3099 * instead had two file extents -- [0, 4k) at pbn 4 and [4k, 8k) at pbn 5 --
3100 * the above diagram would instead look like
3102 * [aaaa|0000|0000|0000][aaaa|0000|0000|0000]
3105 * [aaaa|aaaa] unencrypted
3108 * This scaled mapping approach guarantees that xattrs are always on different
3109 * pages from other xattrs, but it comes at an increased memory cost for
3110 * non-page multiple sized xattrs.
3113 * If `fext' is not representable as a virtual offset (e.g. its phys_block_num
3114 * is corrupt), this function returns false.
3117 uio_set_fext(uio_t uio
, const HFSPlusExtentDescriptor
*fext
, uint32_t fs_bsize
)
3120 if (!fext2cluster_check(fext
, fs_bsize
, &off
)) {
3121 // `fext' is out of range
3125 uio_setoffset(uio
, off
);
3130 * Read an extent based attribute.
3133 read_attr_data(struct hfsmount
*hfsmp
, uio_t uio
, size_t datasize
, HFSPlusExtentDescriptor
*extents
)
3135 vnode_t evp
= hfsmp
->hfs_attrdata_vp
;
3144 hfs_lock_truncate(VTOC(evp
), HFS_SHARED_LOCK
, HFS_LOCK_DEFAULT
);
3146 bufsize
= (int)uio_resid(uio
);
3147 attrsize
= (int)datasize
;
3148 blksize
= (int)hfsmp
->blockSize
;
3149 filesize
= VTOF(evp
)->ff_size
;
3152 // allocate an xattr fext for tls through the cluster layer
3153 const HFSPlusExtentDescriptor
**xattr_fext
;
3154 if (!(xattr_fext
= xattr_fext_alloc(&hfsmp
->hfs_xattr_io
))) {
3160 * Read the attribute data one extent at a time.
3161 * For the typical case there is only one extent.
3163 for (i
= 0; (attrsize
> 0) && (bufsize
> 0) && (extents
[i
].startBlock
!= 0); ++i
) {
3164 iosize
= extents
[i
].blockCount
* blksize
;
3165 iosize
= MIN(iosize
, attrsize
);
3166 iosize
= MIN(iosize
, bufsize
);
3167 uio_setresid(uio
, iosize
);
3169 // virtualize the IO offset to target this fext
3170 if (!uio_set_fext(uio
, &extents
[i
], blksize
)) {
3171 // `fext' is corrupted
3176 // stage the next xattr fext for IO
3177 xattr_fext_set(&hfsmp
->hfs_xattr_io
, xattr_fext
, &extents
[i
]);
3179 // Set filesize to end of data read to prevent cluster read-ahead
3180 filesize
= uio_offset(uio
) + iosize
;
3182 uio_setoffset(uio
, (u_int64_t
)extents
[i
].startBlock
* (u_int64_t
)blksize
);
3184 result
= cluster_read(evp
, uio
, filesize
, IO_SYNC
| IO_UNIT
);
3187 // post IO, unstage this xattr fext
3188 xattr_fext_clear(&hfsmp
->hfs_xattr_io
, xattr_fext
);
3191 #if HFS_XATTR_VERBOSE
3192 printf("hfs: read_attr_data: cr iosize %lld [%d, %d] (%d)\n",
3193 iosize
, extents
[i
].startBlock
, extents
[i
].blockCount
, result
);
3200 uio_setresid(uio
, bufsize
);
3201 uio_setoffset(uio
, datasize
);
3204 xattr_fext_free(&hfsmp
->hfs_xattr_io
, xattr_fext
);
3208 hfs_unlock_truncate(VTOC(evp
), HFS_LOCK_DEFAULT
);
3213 * Write an extent based attribute.
3216 write_attr_data(struct hfsmount
*hfsmp
, uio_t uio
, size_t datasize
, HFSPlusExtentDescriptor
*extents
)
3218 vnode_t evp
= hfsmp
->hfs_attrdata_vp
;
3227 hfs_lock_truncate(VTOC(evp
), HFS_SHARED_LOCK
, HFS_LOCK_DEFAULT
);
3229 bufsize
= uio_resid(uio
);
3230 attrsize
= (int) datasize
;
3231 blksize
= (int) hfsmp
->blockSize
;
3232 filesize
= VTOF(evp
)->ff_size
;
3235 // allocate an xattr fext for tls through the cluster layer
3236 const HFSPlusExtentDescriptor
**xattr_fext
;
3237 if (!(xattr_fext
= xattr_fext_alloc(&hfsmp
->hfs_xattr_io
))) {
3244 * Write the attribute data one extent at a time.
3246 for (i
= 0; (attrsize
> 0) && (bufsize
> 0) && (extents
[i
].startBlock
!= 0); ++i
) {
3247 iosize
= extents
[i
].blockCount
* blksize
;
3248 iosize
= MIN(iosize
, attrsize
);
3249 iosize
= MIN(iosize
, bufsize
);
3250 uio_setresid(uio
, iosize
);
3252 // virtualize the IO offset to target this fext
3253 if (!uio_set_fext(uio
, &extents
[i
], blksize
)) {
3254 // `fext' is corrupted
3259 // stage the next xattr fext for IO
3260 xattr_fext_set(&hfsmp
->hfs_xattr_io
, xattr_fext
, &extents
[i
]);
3262 filesize
= uio_offset(uio
) + iosize
;
3264 uio_setoffset(uio
, (u_int64_t
)extents
[i
].startBlock
* (u_int64_t
)blksize
);
3266 result
= cluster_write(evp
, uio
, filesize
, filesize
, filesize
,
3267 (off_t
) 0, IO_SYNC
| IO_UNIT
);
3270 // post IO, unstage this xattr fext
3271 xattr_fext_clear(&hfsmp
->hfs_xattr_io
, xattr_fext
);
3274 #if HFS_XATTR_VERBOSE
3275 printf("hfs: write_attr_data: cw iosize %lld [%d, %d] (%d)\n",
3276 iosize
, extents
[i
].startBlock
, extents
[i
].blockCount
, result
);
3283 uio_setresid(uio
, bufsize
);
3284 uio_setoffset(uio
, datasize
);
3287 xattr_fext_free(&hfsmp
->hfs_xattr_io
, xattr_fext
);
3291 hfs_unlock_truncate(VTOC(evp
), HFS_LOCK_DEFAULT
);
3296 * Allocate blocks for an extent based attribute.
3299 alloc_attr_blks(struct hfsmount
*hfsmp
, size_t attrsize
, size_t extentbufsize
, HFSPlusExtentDescriptor
*extents
, int *blocks
)
3308 startblk
= hfsmp
->hfs_metazone_end
;
3309 blkcnt
= howmany(attrsize
, hfsmp
->blockSize
);
3310 if (blkcnt
> (int)hfs_freeblks(hfsmp
, 0)) {
3314 maxextents
= extentbufsize
/ sizeof(HFSPlusExtentDescriptor
);
3316 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_BITMAP
, HFS_EXCLUSIVE_LOCK
);
3318 for (i
= 0; (blkcnt
> 0) && (i
< maxextents
); i
++) {
3319 /* Try allocating and see if we find something decent */
3320 result
= BlockAllocate(hfsmp
, startblk
, blkcnt
, blkcnt
, 0,
3321 &extents
[i
].startBlock
, &extents
[i
].blockCount
);
3323 * If we couldn't find anything, then re-try the allocation but allow
3326 if (result
== dskFulErr
) {
3327 result
= BlockAllocate(hfsmp
, startblk
, blkcnt
, blkcnt
, HFS_ALLOC_FLUSHTXN
,
3328 &extents
[i
].startBlock
, &extents
[i
].blockCount
);
3332 #if HFS_XATTR_VERBOSE
3333 printf("hfs: alloc_attr_blks: BA blkcnt %d [%d, %d] (%d)\n",
3334 blkcnt
, extents
[i
].startBlock
, extents
[i
].blockCount
, result
);
3337 extents
[i
].startBlock
= 0;
3338 extents
[i
].blockCount
= 0;
3341 blkcnt
-= extents
[i
].blockCount
;
3342 startblk
= extents
[i
].startBlock
+ extents
[i
].blockCount
;
3345 * If it didn't fit in the extents buffer then bail.
3350 #if HFS_XATTR_VERBOSE
3351 printf("hfs: alloc_attr_blks: unexpected failure, %d blocks unallocated\n", blkcnt
);
3353 for (; i
>= 0; i
--) {
3354 if ((blkcnt
= extents
[i
].blockCount
) != 0) {
3355 (void) BlockDeallocate(hfsmp
, extents
[i
].startBlock
, blkcnt
, 0);
3356 extents
[i
].startBlock
= 0;
3357 extents
[i
].blockCount
= 0;
3362 hfs_systemfile_unlock(hfsmp
, lockflags
);
3363 return MacToVFSError(result
);
3367 * Release blocks from an extent based attribute.
3370 free_attr_blks(struct hfsmount
*hfsmp
, int blkcnt
, HFSPlusExtentDescriptor
*extents
)
3372 vnode_t evp
= hfsmp
->hfs_attrdata_vp
;
3373 int remblks
= blkcnt
;
3377 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_BITMAP
, HFS_EXCLUSIVE_LOCK
);
3379 for (i
= 0; (remblks
> 0) && (extents
[i
].blockCount
!= 0); i
++) {
3380 if (extents
[i
].blockCount
> (u_int32_t
)blkcnt
) {
3381 #if HFS_XATTR_VERBOSE
3382 printf("hfs: free_attr_blks: skipping bad extent [%d, %d]\n",
3383 extents
[i
].startBlock
, extents
[i
].blockCount
);
3385 extents
[i
].blockCount
= 0;
3388 if (extents
[i
].startBlock
== 0) {
3391 (void)BlockDeallocate(hfsmp
, extents
[i
].startBlock
, extents
[i
].blockCount
, 0);
3392 remblks
-= extents
[i
].blockCount
;
3393 extents
[i
].startBlock
= 0;
3394 extents
[i
].blockCount
= 0;
3396 #if HFS_XATTR_VERBOSE
3397 printf("hfs: free_attr_blks: BlockDeallocate [%d, %d]\n",
3398 extents
[i
].startBlock
, extents
[i
].blockCount
);
3400 /* Discard any resident pages for this block range. */
3404 start
= (u_int64_t
)extents
[i
].startBlock
* (u_int64_t
)hfsmp
->blockSize
;
3405 end
= start
+ (u_int64_t
)extents
[i
].blockCount
* (u_int64_t
)hfsmp
->blockSize
;
3406 (void) ubc_msync(hfsmp
->hfs_attrdata_vp
, start
, end
, &start
, UBC_INVALIDATE
);
3410 hfs_systemfile_unlock(hfsmp
, lockflags
);
3414 has_overflow_extents(HFSPlusForkData
*forkdata
)
3418 if (forkdata
->extents
[7].blockCount
== 0)
3421 blocks
= forkdata
->extents
[0].blockCount
+
3422 forkdata
->extents
[1].blockCount
+
3423 forkdata
->extents
[2].blockCount
+
3424 forkdata
->extents
[3].blockCount
+
3425 forkdata
->extents
[4].blockCount
+
3426 forkdata
->extents
[5].blockCount
+
3427 forkdata
->extents
[6].blockCount
+
3428 forkdata
->extents
[7].blockCount
;
3430 return (forkdata
->totalBlocks
> blocks
);
3434 count_extent_blocks(int maxblks
, HFSPlusExtentRecord extents
)
3439 for (i
= 0, blocks
= 0; i
< kHFSPlusExtentDensity
; ++i
) {
3440 /* Ignore obvious bogus extents. */
3441 if (extents
[i
].blockCount
> (u_int32_t
)maxblks
)
3443 if (extents
[i
].startBlock
== 0 || extents
[i
].blockCount
== 0)
3445 blocks
+= extents
[i
].blockCount
;