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 FSBufferDescriptor btdata
;
173 u_int16_t datasize
= 0;
174 u_int32_t target_id
= 0;
177 target_id
= cp
->c_fileid
;
179 target_id
= kHFSRootParentID
;
182 /* Bail if we don't have an EA B-Tree. */
183 if ((hfsmp
->hfs_attribute_vp
== NULL
) ||
184 ((cp
) && (cp
->c_attr
.ca_recflags
& kHFSHasAttributesMask
) == 0)) {
189 /* Initialize the B-Tree iterator for searching for the proper EA */
190 btfile
= VTOF(hfsmp
->hfs_attribute_vp
);
192 iterator
= hfs_mallocz(sizeof(*iterator
));
194 /* Allocate memory for reading in the attribute record. This buffer is
195 * big enough to read in all types of attribute records. It is not big
196 * enough to read inline attribute data which is read in later.
198 recp
= hfs_malloc(sizeof(HFSPlusAttrRecord
));
199 btdata
.bufferAddress
= recp
;
200 btdata
.itemSize
= sizeof(HFSPlusAttrRecord
);
201 btdata
.itemCount
= 1;
203 result
= hfs_buildattrkey(target_id
, attr_name
, (HFSPlusAttrKey
*)&iterator
->key
);
208 /* Lookup the attribute in the Attribute B-Tree */
209 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
, HFS_SHARED_LOCK
);
210 result
= BTSearchRecord(btfile
, iterator
, &btdata
, &datasize
, NULL
);
211 hfs_systemfile_unlock(hfsmp
, lockflags
);
214 if (result
== btNotFound
) {
221 * Operate differently if we have inline EAs that can fit in the attribute B-Tree or if
222 * we have extent based EAs.
224 switch (recp
->recordType
) {
226 /* Attribute fits in the Attribute B-Tree */
227 case kHFSPlusAttrInlineData
: {
229 * Sanity check record size. It's not required to have any
230 * user data, so the minimum size is 2 bytes less that the
231 * size of HFSPlusAttrData (since HFSPlusAttrData struct
232 * has 2 bytes set aside for attribute data).
234 if (datasize
< (sizeof(HFSPlusAttrData
) - 2)) {
235 LFHFS_LOG(LEVEL_DEBUG
, "hfs_getxattr: vol=%s %d,%s invalid record size %d (expecting %lu)\n",
236 hfsmp
->vcbVN
, target_id
, attr_name
, datasize
, sizeof(HFSPlusAttrData
));
240 *actual_size
= recp
->attrData
.attrSize
;
241 if (buf
&& recp
->attrData
.attrSize
!= 0) {
242 if (*actual_size
> bufsize
) {
243 /* User provided buffer is not large enough for the xattr data */
246 /* Previous BTreeSearchRecord() read in only the attribute record,
247 * and not the attribute data. Now allocate enough memory for
248 * both attribute record and data, and read the attribute record again.
250 attrsize
= sizeof(HFSPlusAttrData
) - 2 + recp
->attrData
.attrSize
;
252 recp
= hfs_malloc(attrsize
);
254 btdata
.bufferAddress
= recp
;
255 btdata
.itemSize
= attrsize
;
256 btdata
.itemCount
= 1;
258 bzero(iterator
, sizeof(*iterator
));
259 result
= hfs_buildattrkey(target_id
, attr_name
, (HFSPlusAttrKey
*)&iterator
->key
);
264 /* Lookup the attribute record and inline data */
265 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
, HFS_SHARED_LOCK
);
266 result
= BTSearchRecord(btfile
, iterator
, &btdata
, &datasize
, NULL
);
267 hfs_systemfile_unlock(hfsmp
, lockflags
);
269 if (result
== btNotFound
) {
275 /* Copy-out the attribute data to the user buffer */
276 *actual_size
= recp
->attrData
.attrSize
;
277 memcpy(buf
, (caddr_t
) &recp
->attrData
.attrData
, recp
->attrData
.attrSize
);
283 /* Extent-Based EAs */
284 case kHFSPlusAttrForkData
: {
285 if (datasize
< sizeof(HFSPlusAttrForkData
)) {
286 LFHFS_LOG(LEVEL_DEBUG
, "hfs_getxattr: vol=%s %d,%s invalid record size %d (expecting %lu)\n",
287 hfsmp
->vcbVN
, target_id
, attr_name
, datasize
, sizeof(HFSPlusAttrForkData
));
291 *actual_size
= recp
->forkData
.theFork
.logicalSize
;
295 if (*actual_size
> bufsize
) {
299 /* Process overflow extents if necessary. */
300 if (has_overflow_extents(&recp
->forkData
.theFork
)) {
301 HFSPlusExtentDescriptor
*extentbuf
;
302 HFSPlusExtentDescriptor
*extentptr
;
303 size_t extentbufsize
;
304 u_int32_t totalblocks
;
308 totalblocks
= recp
->forkData
.theFork
.totalBlocks
;
309 /* Ignore bogus block counts. */
310 if (totalblocks
> howmany(HFS_XATTR_MAXSIZE
, hfsmp
->blockSize
)) {
314 attrlen
= recp
->forkData
.theFork
.logicalSize
;
316 /* Get a buffer to hold the worst case amount of extents. */
317 extentbufsize
= totalblocks
* sizeof(HFSPlusExtentDescriptor
);
318 extentbufsize
= roundup(extentbufsize
, sizeof(HFSPlusExtentRecord
));
319 extentbuf
= hfs_mallocz(extentbufsize
);
320 extentptr
= extentbuf
;
322 /* Grab the first 8 extents. */
323 bcopy(&recp
->forkData
.theFork
.extents
[0], extentptr
, sizeof(HFSPlusExtentRecord
));
324 extentptr
+= kHFSPlusExtentDensity
;
325 blkcnt
= count_extent_blocks(totalblocks
, recp
->forkData
.theFork
.extents
);
327 /* Now lookup the overflow extents. */
328 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
, HFS_SHARED_LOCK
);
329 while (blkcnt
< totalblocks
) {
330 ((HFSPlusAttrKey
*)&iterator
->key
)->startBlock
= blkcnt
;
331 result
= BTSearchRecord(btfile
, iterator
, &btdata
, &datasize
, NULL
);
333 (recp
->recordType
!= kHFSPlusAttrExtents
) ||
334 (datasize
< sizeof(HFSPlusAttrExtents
))) {
335 LFHFS_LOG(LEVEL_DEBUG
, "hfs_getxattr: %s missing extents, only %d blks of %d found\n",
336 attr_name
, blkcnt
, totalblocks
);
337 break; /* break from while */
339 /* Grab the next 8 extents. */
340 bcopy(&recp
->overflowExtents
.extents
[0], extentptr
, sizeof(HFSPlusExtentRecord
));
341 extentptr
+= kHFSPlusExtentDensity
;
342 blkcnt
+= count_extent_blocks(totalblocks
, recp
->overflowExtents
.extents
);
345 /* Release Attr B-Tree lock */
346 hfs_systemfile_unlock(hfsmp
, lockflags
);
348 if (blkcnt
< totalblocks
) {
351 result
= read_attr_data(hfsmp
, buf
, attrlen
, extentbuf
);
355 } else { /* No overflow extents. */
356 result
= read_attr_data(hfsmp
, buf
, recp
->forkData
.theFork
.logicalSize
, recp
->forkData
.theFork
.extents
);
362 /* We only support inline EAs. Default to ENOATTR for anything else */
372 return MacToVFSError(result
);
376 * Set the data of an extended attribute.
379 hfs_vnop_setxattr(vnode_t vp
, const char *attr_name
, const void *buf
, size_t bufsize
, UVFSXattrHow option
)
381 struct cnode
*cp
= NULL
;
382 struct hfsmount
*hfsmp
;
386 if (attr_name
== NULL
|| attr_name
[0] == '\0') {
387 return (EINVAL
); /* invalid name */
389 if (strlen(attr_name
) > XATTR_MAXNAMELEN
) {
390 return (ENAMETOOLONG
);
395 if (VNODE_IS_RSRC(vp
)) {
401 /* Set the Finder Info. */
402 if (strcmp(attr_name
, XATTR_FINDERINFO_NAME
) == 0) {
406 struct FndrFileInfo info
;
408 void * finderinfo_start
;
409 u_int8_t
*finfo
= NULL
;
411 u_int32_t dateadded
= 0;
412 u_int32_t write_gen_counter
= 0;
413 u_int32_t document_id
= 0;
415 attrsize
= sizeof(VTOC(vp
)->c_finderinfo
);
417 if (bufsize
!= attrsize
) {
420 /* Grab the new Finder Info data. */
421 memcpy(fi
.cdata
, buf
, attrsize
);
423 if ((result
= hfs_lock(VTOC(vp
), HFS_EXCLUSIVE_LOCK
, HFS_LOCK_DEFAULT
))) {
428 /* Symlink's don't have an external type/creator. */
429 if (vnode_islnk(vp
)) {
430 /* Skip over type/creator fields. */
431 finderinfo_start
= &cp
->c_finderinfo
[8];
434 finderinfo_start
= &cp
->c_finderinfo
[0];
436 * Don't allow the external setting of
437 * file type to kHardLinkFileType.
439 if (fi
.info
.fdType
== SWAP_BE32(kHardLinkFileType
)) {
445 /* Grab the current date added from the cnode */
446 dateadded
= hfs_get_dateadded (cp
);
447 if (S_ISREG(cp
->c_attr
.ca_mode
) || S_ISLNK(cp
->c_attr
.ca_mode
)) {
448 struct FndrExtendedFileInfo
*extinfo
= (struct FndrExtendedFileInfo
*)((u_int8_t
*)cp
->c_finderinfo
+ 16);
450 * Grab generation counter directly from the cnode
451 * instead of calling hfs_get_gencount(), because
452 * for zero generation count values hfs_get_gencount()
453 * lies and bumps it up to one.
455 write_gen_counter
= extinfo
->write_gen_counter
;
456 document_id
= extinfo
->document_id
;
457 } else if (S_ISDIR(cp
->c_attr
.ca_mode
)) {
458 struct FndrExtendedDirInfo
*extinfo
= (struct FndrExtendedDirInfo
*)((u_int8_t
*)cp
->c_finderinfo
+ 16);
459 write_gen_counter
= extinfo
->write_gen_counter
;
460 document_id
= extinfo
->document_id
;
464 * Zero out the finder info's reserved fields like date added,
465 * generation counter, and document id to ignore user's attempts
468 hfs_zero_hidden_fields(cp
, fi
.data
);
470 if (bcmp(finderinfo_start
, emptyfinfo
, attrsize
)) {
471 /* attr exists and "create" was specified. */
472 if (option
== UVFSXattrHowCreate
) {
477 /* attr doesn't exists and "replace" was specified. */
478 if (option
== UVFSXattrHowReplace
) {
485 * Now restore the date added and other reserved fields to the finderinfo to
486 * be written out. Advance to the 2nd half of the finderinfo to write them
487 * out into the buffer.
489 * Make sure to endian swap the date added back into big endian. When we used
490 * hfs_get_dateadded above to retrieve it, it swapped into local endianness
491 * for us. But now that we're writing it out, put it back into big endian.
493 finfo
= &fi
.data
[16];
494 if (S_ISREG(cp
->c_attr
.ca_mode
) || S_ISLNK(cp
->c_attr
.ca_mode
)) {
495 struct FndrExtendedFileInfo
*extinfo
= (struct FndrExtendedFileInfo
*)finfo
;
496 extinfo
->date_added
= OSSwapHostToBigInt32(dateadded
);
497 extinfo
->write_gen_counter
= write_gen_counter
;
498 extinfo
->document_id
= document_id
;
499 } else if (S_ISDIR(cp
->c_attr
.ca_mode
)) {
500 struct FndrExtendedDirInfo
*extinfo
= (struct FndrExtendedDirInfo
*)finfo
;
501 extinfo
->date_added
= OSSwapHostToBigInt32(dateadded
);
502 extinfo
->write_gen_counter
= write_gen_counter
;
503 extinfo
->document_id
= document_id
;
506 /* Set the cnode's Finder Info. */
507 if (attrsize
== sizeof(cp
->c_finderinfo
)) {
508 bcopy(&fi
.data
[0], finderinfo_start
, attrsize
);
510 bcopy(&fi
.data
[8], finderinfo_start
, attrsize
);
513 /* Updating finderInfo updates change time and modified time */
514 cp
->c_touch_chgtime
= TRUE
;
515 cp
->c_flag
|= C_MODIFIED
;
518 * Mirror the invisible bit to the UF_HIDDEN flag.
520 * The fdFlags for files and frFlags for folders are both 8 bytes
521 * into the userInfo (the first 16 bytes of the Finder Info). They
522 * are both 16-bit fields.
524 fdFlags
= *((u_int16_t
*) &cp
->c_finderinfo
[8]);
525 if (fdFlags
& OSSwapHostToBigConstInt16(kFinderInvisibleMask
)) {
526 cp
->c_bsdflags
|= UF_HIDDEN
;
528 cp
->c_bsdflags
&= ~UF_HIDDEN
;
531 result
= hfs_update(vp
, 0);
537 /* Write the Resource Fork. */
538 if (strcmp(attr_name
, XATTR_RESOURCEFORK_NAME
) == 0) {
544 result
= hfs_lock(VTOC(vp
), HFS_EXCLUSIVE_LOCK
, HFS_LOCK_DEFAULT
);
551 * If we're trying to set a non-finderinfo, non-resourcefork EA, then
552 * call the breakout function - hfs_setxattr_internal.
554 int started_transaction
= 0;
555 BTreeIterator
* iterator
= NULL
;
556 struct filefork
*btfile
= NULL
;
557 FSBufferDescriptor btdata
;
558 HFSPlusAttrRecord attrdata
; /* 90 bytes */
559 HFSPlusAttrRecord
*recp
= NULL
;
560 HFSPlusExtentDescriptor
*extentptr
= NULL
;
561 size_t extentbufsize
= 0;
564 int allocatedblks
= 0;
568 target_id
= cp
->c_fileid
;
570 target_id
= kHFSRootParentID
;
573 /* Start a transaction for our changes. */
574 if (hfs_start_transaction(hfsmp
) != 0) {
578 started_transaction
= 1;
581 * Once we started the transaction, nobody can compete
582 * with us, so make sure this file is still there.
584 if ((cp
) && (cp
->c_flag
& C_NOEXISTS
)) {
590 * If there isn't an attributes b-tree then create one.
592 if (hfsmp
->hfs_attribute_vp
== NULL
) {
593 result
= hfs_create_attr_btree(hfsmp
, ATTRIBUTE_FILE_NODE_SIZE
,
594 getnodecount(hfsmp
, ATTRIBUTE_FILE_NODE_SIZE
));
599 if (hfsmp
->hfs_max_inline_attrsize
== 0) {
600 hfsmp
->hfs_max_inline_attrsize
= getmaxinlineattrsize(hfsmp
->hfs_attribute_vp
);
603 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
, HFS_EXCLUSIVE_LOCK
);
605 /* Build the b-tree key. */
606 iterator
= hfs_mallocz(sizeof(*iterator
));
607 result
= hfs_buildattrkey(target_id
, attr_name
, (HFSPlusAttrKey
*)&iterator
->key
);
612 /* Preflight for replace/create semantics. */
613 btfile
= VTOF(hfsmp
->hfs_attribute_vp
);
614 btdata
.bufferAddress
= &attrdata
;
615 btdata
.itemSize
= sizeof(attrdata
);
616 btdata
.itemCount
= 1;
617 exists
= BTSearchRecord(btfile
, iterator
, &btdata
, NULL
, NULL
) == 0;
619 /* Replace requires that the attribute already exists. */
620 if ((option
== UVFSXattrHowReplace
) && !exists
) {
624 /* Create requires that the attribute doesn't exist. */
625 if ((option
== UVFSXattrHowCreate
) && exists
) {
630 /* Enforce an upper limit. */
631 if (attrsize
> HFS_XATTR_MAXSIZE
) {
636 /* If it won't fit inline then use extent-based attributes. */
637 if (attrsize
> hfsmp
->hfs_max_inline_attrsize
) {
640 u_int32_t
*keystartblk
;
643 /* Get some blocks. */
644 blkcnt
= (int)howmany(attrsize
, hfsmp
->blockSize
);
645 extentbufsize
= blkcnt
* sizeof(HFSPlusExtentDescriptor
);
646 extentbufsize
= roundup(extentbufsize
, sizeof(HFSPlusExtentRecord
));
647 extentptr
= hfs_mallocz(extentbufsize
);
648 result
= alloc_attr_blks(hfsmp
, attrsize
, extentbufsize
, extentptr
, &allocatedblks
);
651 goto exit_lock
; /* no more space */
653 /* Copy data into the blocks. */
654 result
= write_attr_data(hfsmp
, (void*)buf
, attrsize
, extentptr
);
657 LFHFS_LOG(LEVEL_DEBUG
, "hfs_setxattr: write_attr_data vol=%s err (%d) :%s\n",
658 hfsmp
->vcbVN
, result
, attr_name
);
663 /* Now remove any previous attribute. */
665 result
= remove_attribute_records(hfsmp
, iterator
);
668 LFHFS_LOG(LEVEL_DEBUG
, "hfs_setxattr: remove_attribute_records vol=%s err (%d) %s:%s\n",
669 hfsmp
->vcbVN
, result
, "", attr_name
);
674 /* Create attribute fork data record. */
675 recp
= hfs_malloc(sizeof(HFSPlusAttrRecord
));
677 btdata
.bufferAddress
= recp
;
678 btdata
.itemCount
= 1;
679 btdata
.itemSize
= sizeof(HFSPlusAttrForkData
);
681 recp
->recordType
= kHFSPlusAttrForkData
;
682 recp
->forkData
.reserved
= 0;
683 recp
->forkData
.theFork
.logicalSize
= attrsize
;
684 recp
->forkData
.theFork
.clumpSize
= 0;
685 recp
->forkData
.theFork
.totalBlocks
= blkcnt
;
686 bcopy(extentptr
, recp
->forkData
.theFork
.extents
, sizeof(HFSPlusExtentRecord
));
688 (void) hfs_buildattrkey(target_id
, attr_name
, (HFSPlusAttrKey
*)&iterator
->key
);
690 result
= BTInsertRecord(btfile
, iterator
, &btdata
, btdata
.itemSize
);
692 LFHFS_LOG(LEVEL_DEBUG
, "hfs_setxattr: BTInsertRecord(): vol=%s %d,%s err=%d\n",
693 hfsmp
->vcbVN
, target_id
, attr_name
, result
);
696 extentblks
= count_extent_blocks(blkcnt
, recp
->forkData
.theFork
.extents
);
697 blkcnt
-= extentblks
;
698 keystartblk
= &((HFSPlusAttrKey
*)&iterator
->key
)->startBlock
;
701 /* Create overflow extents as needed. */
703 /* Initialize the key and record. */
704 *keystartblk
+= (u_int32_t
)extentblks
;
705 btdata
.itemSize
= sizeof(HFSPlusAttrExtents
);
706 recp
->recordType
= kHFSPlusAttrExtents
;
707 recp
->overflowExtents
.reserved
= 0;
709 /* Copy the next set of extents. */
710 i
+= kHFSPlusExtentDensity
;
711 bcopy(&extentptr
[i
], recp
->overflowExtents
.extents
, sizeof(HFSPlusExtentRecord
));
713 result
= BTInsertRecord(btfile
, iterator
, &btdata
, btdata
.itemSize
);
715 LFHFS_LOG(LEVEL_DEBUG
, "hfs_setxattr: BTInsertRecord() overflow: vol=%s %d,%s err=%d\n",
716 hfsmp
->vcbVN
, target_id
, attr_name
, result
);
719 extentblks
= count_extent_blocks(blkcnt
, recp
->overflowExtents
.extents
);
720 blkcnt
-= extentblks
;
722 } else { /* Inline data */
724 result
= remove_attribute_records(hfsmp
, iterator
);
730 /* Calculate size of record rounded up to multiple of 2 bytes. */
731 btdata
.itemSize
= sizeof(HFSPlusAttrData
) - 2 + attrsize
+ ((attrsize
& 1) ? 1 : 0);
732 recp
= hfs_malloc(btdata
.itemSize
);
734 recp
->recordType
= kHFSPlusAttrInlineData
;
735 recp
->attrData
.reserved
[0] = 0;
736 recp
->attrData
.reserved
[1] = 0;
737 recp
->attrData
.attrSize
= (u_int32_t
)attrsize
;
739 /* Copy in the attribute data (if any). */
741 bcopy(buf
, &recp
->attrData
.attrData
, attrsize
);
744 (void) hfs_buildattrkey(target_id
, attr_name
, (HFSPlusAttrKey
*)&iterator
->key
);
746 btdata
.bufferAddress
= recp
;
747 btdata
.itemCount
= 1;
748 result
= BTInsertRecord(btfile
, iterator
, &btdata
, btdata
.itemSize
);
752 if (btfile
&& started_transaction
) {
753 (void) BTFlushPath(btfile
);
755 hfs_systemfile_unlock(hfsmp
, lockflags
);
759 /* Setting an attribute only updates change time and not
760 * modified time of the file.
762 cp
->c_touch_chgtime
= TRUE
;
763 cp
->c_flag
|= C_MODIFIED
;
764 cp
->c_attr
.ca_recflags
|= kHFSHasAttributesMask
;
765 if ((strcmp(attr_name
, KAUTH_FILESEC_XATTR
) == 0)) {
766 cp
->c_attr
.ca_recflags
|= kHFSHasSecurityMask
;
768 (void) hfs_update(vp
, 0);
771 if (started_transaction
) {
772 if (result
&& allocatedblks
) {
773 free_attr_blks(hfsmp
, allocatedblks
, extentptr
);
775 hfs_end_transaction(hfsmp
);
787 return (result
== btNotFound
? ENOATTR
: MacToVFSError(result
));
791 * Remove an extended attribute.
794 hfs_vnop_removexattr(vnode_t vp
, const char *attr_name
)
796 struct cnode
*cp
= VTOC(vp
);
797 struct hfsmount
*hfsmp
;
798 BTreeIterator
* iterator
= NULL
;
802 if (attr_name
== NULL
|| attr_name
[0] == '\0') {
803 return (EINVAL
); /* invalid name */
806 if (VNODE_IS_RSRC(vp
)) {
810 /* Write the Resource Fork. */
811 if (strcmp(attr_name
, XATTR_RESOURCEFORK_NAME
) == 0) {
815 /* Clear out the Finder Info. */
816 if (strcmp(attr_name
, XATTR_FINDERINFO_NAME
) == 0) {
817 void * finderinfo_start
;
819 u_int8_t finderinfo
[32];
820 u_int32_t date_added
= 0, write_gen_counter
= 0, document_id
= 0;
821 u_int8_t
*finfo
= NULL
;
823 if ((result
= hfs_lock(cp
, HFS_EXCLUSIVE_LOCK
, HFS_LOCK_DEFAULT
))) {
827 /* Use the local copy to store our temporary changes. */
828 bcopy(cp
->c_finderinfo
, finderinfo
, sizeof(finderinfo
));
830 /* Zero out the date added field in the local copy */
831 hfs_zero_hidden_fields (cp
, finderinfo
);
833 /* Don't expose a symlink's private type/creator. */
834 if (vnode_islnk(vp
)) {
835 struct FndrFileInfo
*fip
;
837 fip
= (struct FndrFileInfo
*)&finderinfo
;
842 /* Do the byte compare against the local copy */
843 if (bcmp(finderinfo
, emptyfinfo
, sizeof(emptyfinfo
)) == 0) {
849 * If there was other content, zero out everything except
850 * type/creator and date added. First, save the date added.
852 finfo
= cp
->c_finderinfo
;
854 if (S_ISREG(cp
->c_attr
.ca_mode
) || S_ISLNK(cp
->c_attr
.ca_mode
)) {
855 struct FndrExtendedFileInfo
*extinfo
= (struct FndrExtendedFileInfo
*)finfo
;
856 date_added
= extinfo
->date_added
;
857 write_gen_counter
= extinfo
->write_gen_counter
;
858 document_id
= extinfo
->document_id
;
859 } else if (S_ISDIR(cp
->c_attr
.ca_mode
)) {
860 struct FndrExtendedDirInfo
*extinfo
= (struct FndrExtendedDirInfo
*)finfo
;
861 date_added
= extinfo
->date_added
;
862 write_gen_counter
= extinfo
->write_gen_counter
;
863 document_id
= extinfo
->document_id
;
866 if (vnode_islnk(vp
)) {
867 /* Ignore type/creator */
868 finderinfo_start
= &cp
->c_finderinfo
[8];
869 finderinfo_size
= sizeof(cp
->c_finderinfo
) - 8;
871 finderinfo_start
= &cp
->c_finderinfo
[0];
872 finderinfo_size
= sizeof(cp
->c_finderinfo
);
874 bzero(finderinfo_start
, finderinfo_size
);
876 /* Now restore the date added */
877 if (S_ISREG(cp
->c_attr
.ca_mode
) || S_ISLNK(cp
->c_attr
.ca_mode
)) {
878 struct FndrExtendedFileInfo
*extinfo
= (struct FndrExtendedFileInfo
*)finfo
;
879 extinfo
->date_added
= date_added
;
880 extinfo
->write_gen_counter
= write_gen_counter
;
881 extinfo
->document_id
= document_id
;
882 } else if (S_ISDIR(cp
->c_attr
.ca_mode
)) {
883 struct FndrExtendedDirInfo
*extinfo
= (struct FndrExtendedDirInfo
*)finfo
;
884 extinfo
->date_added
= date_added
;
885 extinfo
->write_gen_counter
= write_gen_counter
;
886 extinfo
->document_id
= document_id
;
889 /* Updating finderInfo updates change time and modified time */
890 cp
->c_touch_chgtime
= TRUE
;
891 cp
->c_flag
|= C_MODIFIED
;
899 if (hfsmp
->hfs_attribute_vp
== NULL
) {
903 iterator
= hfs_mallocz(sizeof(*iterator
));
905 if ((result
= hfs_lock(cp
, HFS_EXCLUSIVE_LOCK
, HFS_LOCK_DEFAULT
))) {
909 result
= hfs_buildattrkey(cp
->c_fileid
, attr_name
, (HFSPlusAttrKey
*)&iterator
->key
);
914 if (hfs_start_transaction(hfsmp
) != 0) {
918 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
| SFL_BITMAP
, HFS_EXCLUSIVE_LOCK
);
920 result
= remove_attribute_records(hfsmp
, iterator
);
922 hfs_systemfile_unlock(hfsmp
, lockflags
);
925 cp
->c_touch_chgtime
= TRUE
;
927 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
, HFS_SHARED_LOCK
);
929 /* If no more attributes exist, clear attribute bit */
930 result
= file_attribute_exist(hfsmp
, cp
->c_fileid
);
932 cp
->c_attr
.ca_recflags
&= ~kHFSHasAttributesMask
;
933 cp
->c_flag
|= C_MODIFIED
;
935 if (result
== EEXIST
) {
939 hfs_systemfile_unlock(hfsmp
, lockflags
);
941 /* If ACL was removed, clear security bit */
942 if (strcmp(attr_name
, KAUTH_FILESEC_XATTR
) == 0) {
943 cp
->c_attr
.ca_recflags
&= ~kHFSHasSecurityMask
;
944 cp
->c_flag
|= C_MODIFIED
;
946 (void) hfs_update(vp
, 0);
949 hfs_end_transaction(hfsmp
);
954 return MacToVFSError(result
);
958 * Initialize vnode for attribute data I/O.
962 * - the attrdata vnode is initialized as hfsmp->hfs_attrdata_vp
963 * - an iocount is taken on the attrdata vnode which exists
964 * for the entire duration of the mount. It is only dropped
966 * - the attrdata cnode is not locked
969 * - returns non-zero value
970 * - the caller does not have to worry about any locks or references
972 int init_attrdata_vnode(struct hfsmount
*hfsmp
)
976 struct cat_desc cat_desc
;
977 struct cat_attr cat_attr
;
978 struct cat_fork cat_fork
;
979 int newvnode_flags
= 0;
981 bzero(&cat_desc
, sizeof(cat_desc
));
982 cat_desc
.cd_parentcnid
= kHFSRootParentID
;
983 cat_desc
.cd_nameptr
= (const u_int8_t
*)hfs_attrdatafilename
;
984 cat_desc
.cd_namelen
= strlen(hfs_attrdatafilename
);
985 cat_desc
.cd_cnid
= kHFSAttributeDataFileID
;
986 /* Tag vnode as system file, note that we can still use cluster I/O */
987 cat_desc
.cd_flags
|= CD_ISMETA
;
989 bzero(&cat_attr
, sizeof(cat_attr
));
990 cat_attr
.ca_linkcount
= 1;
991 cat_attr
.ca_mode
= S_IFREG
;
992 cat_attr
.ca_fileid
= cat_desc
.cd_cnid
;
993 cat_attr
.ca_blocks
= hfsmp
->totalBlocks
;
996 * The attribute data file is a virtual file that spans the
997 * entire file system space.
999 * Each extent-based attribute occupies a unique portion of
1000 * in this virtual file. The cluster I/O is done using actual
1001 * allocation block offsets so no additional mapping is needed
1002 * for the VNOP_BLOCKMAP call.
1004 * This approach allows the attribute data to be cached without
1005 * incurring the high cost of using a separate vnode per attribute.
1007 * Since we need to acquire the attribute b-tree file lock anyways,
1008 * the virtual file doesn't introduce any additional serialization.
1010 bzero(&cat_fork
, sizeof(cat_fork
));
1011 cat_fork
.cf_size
= (u_int64_t
)hfsmp
->totalBlocks
* (u_int64_t
)hfsmp
->blockSize
;
1012 cat_fork
.cf_blocks
= hfsmp
->totalBlocks
;
1013 cat_fork
.cf_extents
[0].startBlock
= 0;
1014 cat_fork
.cf_extents
[0].blockCount
= cat_fork
.cf_blocks
;
1016 result
= hfs_getnewvnode(hfsmp
, NULL
, NULL
, &cat_desc
, 0, &cat_attr
,
1017 &cat_fork
, &vp
, &newvnode_flags
);
1019 hfsmp
->hfs_attrdata_vp
= vp
;
1020 hfs_unlock(VTOC(vp
));
1025 /* Check if any attribute record exist for given fileID. This function
1026 * is called by hfs_vnop_removexattr to determine if it should clear the
1027 * attribute bit in the catalog record or not.
1029 * Note - you must acquire a shared lock on the attribute btree before
1030 * calling this function.
1033 * EEXIST - If attribute record was found
1034 * 0 - Attribute was not found
1035 * (other) - Other error (such as EIO)
1038 file_attribute_exist(struct hfsmount
*hfsmp
, uint32_t fileID
)
1040 HFSPlusAttrKey
*key
;
1041 BTreeIterator
* iterator
= NULL
;
1042 struct filefork
*btfile
;
1045 // if there's no attribute b-tree we sure as heck
1046 // can't have any attributes!
1047 if (hfsmp
->hfs_attribute_vp
== NULL
) {
1051 iterator
= hfs_mallocz(sizeof(BTreeIterator
));
1052 if (iterator
== NULL
) return ENOMEM
;
1054 key
= (HFSPlusAttrKey
*)&iterator
->key
;
1056 result
= hfs_buildattrkey(fileID
, NULL
, key
);
1061 btfile
= VTOF(hfsmp
->hfs_attribute_vp
);
1062 result
= BTSearchRecord(btfile
, iterator
, NULL
, NULL
, NULL
);
1063 if (result
&& (result
!= btNotFound
)) {
1067 result
= BTIterateRecord(btfile
, kBTreeNextRecord
, iterator
, NULL
, NULL
);
1068 /* If no next record was found or fileID for next record did not match,
1069 * no more attributes exist for this fileID
1071 if ((result
&& (result
== btNotFound
)) || (key
->fileID
!= fileID
)) {
1083 * Read an extent based attribute.
1086 read_attr_data(struct hfsmount
*hfsmp
, void *buf
, size_t datasize
, HFSPlusExtentDescriptor
*extents
)
1088 vnode_t evp
= hfsmp
->hfs_attrdata_vp
;
1092 uint64_t alreadyread
;
1096 hfs_lock_truncate(VTOC(evp
), HFS_SHARED_LOCK
, HFS_LOCK_DEFAULT
);
1098 attrsize
= (uint64_t)datasize
;
1099 blksize
= (uint64_t)hfsmp
->blockSize
;
1103 * Read the attribute data one extent at a time.
1104 * For the typical case there is only one extent.
1106 for (i
= 0; (attrsize
> 0) && (extents
[i
].startBlock
!= 0); ++i
) {
1107 iosize
= extents
[i
].blockCount
* blksize
;
1108 iosize
= MIN(iosize
, attrsize
);
1110 uint64_t actualread
= 0;
1112 result
= raw_readwrite_read_internal( evp
, extents
[i
].startBlock
, extents
[i
].blockCount
* blksize
,
1113 alreadyread
, iosize
, buf
, &actualread
);
1114 #if HFS_XATTR_VERBOSE
1115 LFHFS_LOG(LEVEL_DEBUG
, "hfs: read_attr_data: cr iosize %lld [%d, %d] (%d)\n",
1116 actualread
, extents
[i
].startBlock
, extents
[i
].blockCount
, result
);
1121 // read the remaining part after sector boundary if we have such
1122 if (iosize
!= actualread
)
1124 result
= raw_readwrite_read_internal( evp
, extents
[i
].startBlock
, extents
[i
].blockCount
* blksize
,
1125 alreadyread
+ actualread
, iosize
- actualread
,
1126 (uint8_t*)buf
+ actualread
, &actualread
);
1127 #if HFS_XATTR_VERBOSE
1128 LFHFS_LOG(LEVEL_DEBUG
, "hfs: read_attr_data: cr iosize %lld [%d, %d] (%d)\n",
1129 actualread
, extents
[i
].startBlock
, extents
[i
].blockCount
, result
);
1137 alreadyread
+= iosize
;
1138 buf
= (uint8_t*)buf
+ iosize
;
1141 hfs_unlock_truncate(VTOC(evp
), HFS_LOCK_DEFAULT
);
1146 * Write an extent based attribute.
1149 write_attr_data(struct hfsmount
*hfsmp
, void *buf
, size_t datasize
, HFSPlusExtentDescriptor
*extents
)
1151 vnode_t evp
= hfsmp
->hfs_attrdata_vp
;
1155 uint64_t alreadywritten
;
1159 hfs_lock_truncate(VTOC(evp
), HFS_SHARED_LOCK
, HFS_LOCK_DEFAULT
);
1161 attrsize
= (uint64_t)datasize
;
1162 blksize
= (uint64_t)hfsmp
->blockSize
;
1166 * Write the attribute data one extent at a time.
1168 for (i
= 0; (attrsize
> 0) && (extents
[i
].startBlock
!= 0); ++i
) {
1169 iosize
= extents
[i
].blockCount
* blksize
;
1170 iosize
= MIN(iosize
, attrsize
);
1172 uint64_t actualwritten
= 0;
1174 result
= raw_readwrite_write_internal( evp
, extents
[i
].startBlock
, extents
[i
].blockCount
* blksize
,
1175 alreadywritten
, iosize
, buf
, &actualwritten
);
1176 #if HFS_XATTR_VERBOSE
1177 LFHFS_LOG(LEVEL_DEBUG
, "hfs: write_attr_data: cw iosize %lld [%d, %d] (%d)\n",
1178 actualwritten
, extents
[i
].startBlock
, extents
[i
].blockCount
, result
);
1183 // write the remaining part after sector boundary if we have such
1184 if (iosize
!= actualwritten
)
1186 result
= raw_readwrite_write_internal( evp
, extents
[i
].startBlock
, extents
[i
].blockCount
* blksize
,
1187 alreadywritten
+ actualwritten
, iosize
- actualwritten
,
1188 (uint8_t*)buf
+ actualwritten
, &actualwritten
);
1189 #if HFS_XATTR_VERBOSE
1190 LFHFS_LOG(LEVEL_DEBUG
, "hfs: write_attr_data: cw iosize %lld [%d, %d] (%d)\n",
1191 actualwritten
, extents
[i
].startBlock
, extents
[i
].blockCount
, result
);
1199 alreadywritten
+= iosize
;
1200 buf
= (uint8_t*)buf
+ iosize
;
1203 hfs_unlock_truncate(VTOC(evp
), HFS_LOCK_DEFAULT
);
1208 * Allocate blocks for an extent based attribute.
1211 alloc_attr_blks(struct hfsmount
*hfsmp
, size_t attrsize
, size_t extentbufsize
, HFSPlusExtentDescriptor
*extents
, int *blocks
)
1220 startblk
= hfsmp
->hfs_metazone_end
;
1221 blkcnt
= (int)howmany(attrsize
, hfsmp
->blockSize
);
1222 if (blkcnt
> (int)hfs_freeblks(hfsmp
, 0)) {
1226 maxextents
= (int)extentbufsize
/ sizeof(HFSPlusExtentDescriptor
);
1228 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_BITMAP
, HFS_EXCLUSIVE_LOCK
);
1230 for (i
= 0; (blkcnt
> 0) && (i
< maxextents
); i
++) {
1231 /* Try allocating and see if we find something decent */
1232 result
= BlockAllocate(hfsmp
, startblk
, blkcnt
, blkcnt
, 0,
1233 &extents
[i
].startBlock
, &extents
[i
].blockCount
);
1235 * If we couldn't find anything, then re-try the allocation but allow
1238 if (result
== dskFulErr
) {
1239 result
= BlockAllocate(hfsmp
, startblk
, blkcnt
, blkcnt
, HFS_ALLOC_FLUSHTXN
,
1240 &extents
[i
].startBlock
, &extents
[i
].blockCount
);
1242 #if HFS_XATTR_VERBOSE
1243 LFHFS_LOG(LEVEL_DEBUG
,"hfs: alloc_attr_blks: BA blkcnt %d [%d, %d] (%d)\n",
1244 blkcnt
, extents
[i
].startBlock
, extents
[i
].blockCount
, result
);
1247 extents
[i
].startBlock
= 0;
1248 extents
[i
].blockCount
= 0;
1251 blkcnt
-= extents
[i
].blockCount
;
1252 startblk
= extents
[i
].startBlock
+ extents
[i
].blockCount
;
1255 * If it didn't fit in the extents buffer then bail.
1259 #if HFS_XATTR_VERBOSE
1260 LFHFS_LOG(LEVEL_DEBUG
, "hfs: alloc_attr_blks: unexpected failure, %d blocks unallocated\n", blkcnt
);
1262 for (; i
>= 0; i
--) {
1263 if ((blkcnt
= extents
[i
].blockCount
) != 0) {
1264 (void) BlockDeallocate(hfsmp
, extents
[i
].startBlock
, blkcnt
, 0);
1265 extents
[i
].startBlock
= 0;
1266 extents
[i
].blockCount
= 0;
1271 hfs_systemfile_unlock(hfsmp
, lockflags
);
1272 return MacToVFSError(result
);
1276 * Release blocks from an extent based attribute.
1279 free_attr_blks(struct hfsmount
*hfsmp
, int blkcnt
, HFSPlusExtentDescriptor
*extents
)
1281 vnode_t evp
= hfsmp
->hfs_attrdata_vp
;
1282 int remblks
= blkcnt
;
1286 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_BITMAP
, HFS_EXCLUSIVE_LOCK
);
1288 for (i
= 0; (remblks
> 0) && (extents
[i
].blockCount
!= 0); i
++) {
1289 if (extents
[i
].blockCount
> (u_int32_t
)blkcnt
) {
1290 #if HFS_XATTR_VERBOSE
1291 LFHFS_LOG(LEVEL_DEBUG
, "hfs: free_attr_blks: skipping bad extent [%d, %d]\n",
1292 extents
[i
].startBlock
, extents
[i
].blockCount
);
1294 extents
[i
].blockCount
= 0;
1297 if (extents
[i
].startBlock
== 0) {
1300 (void)BlockDeallocate(hfsmp
, extents
[i
].startBlock
, extents
[i
].blockCount
, 0);
1301 remblks
-= extents
[i
].blockCount
;
1302 #if HFS_XATTR_VERBOSE
1303 LFHFS_LOG(LEVEL_DEBUG
, "hfs: free_attr_blks: BlockDeallocate [%d, %d]\n",
1304 extents
[i
].startBlock
, extents
[i
].blockCount
);
1306 extents
[i
].startBlock
= 0;
1307 extents
[i
].blockCount
= 0;
1309 /* Discard any resident pages for this block range. */
1311 #if LF_HFS_FULL_VNODE_SUPPORT
1313 start
= (u_int64_t
)extents
[i
].startBlock
* (u_int64_t
)hfsmp
->blockSize
;
1314 end
= start
+ (u_int64_t
)extents
[i
].blockCount
* (u_int64_t
)hfsmp
->blockSize
;
1315 //TBD - Need to update this vnode
1316 (void) ubc_msync(hfsmp
->hfs_attrdata_vp
, start
, end
, &start
, UBC_INVALIDATE
);
1321 hfs_systemfile_unlock(hfsmp
, lockflags
);
1325 has_overflow_extents(HFSPlusForkData
*forkdata
)
1329 if (forkdata
->extents
[7].blockCount
== 0)
1332 blocks
= forkdata
->extents
[0].blockCount
+
1333 forkdata
->extents
[1].blockCount
+
1334 forkdata
->extents
[2].blockCount
+
1335 forkdata
->extents
[3].blockCount
+
1336 forkdata
->extents
[4].blockCount
+
1337 forkdata
->extents
[5].blockCount
+
1338 forkdata
->extents
[6].blockCount
+
1339 forkdata
->extents
[7].blockCount
;
1341 return (forkdata
->totalBlocks
> blocks
);
1345 count_extent_blocks(int maxblks
, HFSPlusExtentRecord extents
)
1350 for (i
= 0, blocks
= 0; i
< kHFSPlusExtentDensity
; ++i
) {
1351 /* Ignore obvious bogus extents. */
1352 if (extents
[i
].blockCount
> (u_int32_t
)maxblks
)
1354 if (extents
[i
].startBlock
== 0 || extents
[i
].blockCount
== 0)
1356 blocks
+= extents
[i
].blockCount
;
1362 * Remove all the records for a given attribute.
1364 * - Used by hfs_vnop_removexattr, hfs_vnop_setxattr and hfs_removeallattr.
1365 * - A transaction must have been started.
1366 * - The Attribute b-tree file must be locked exclusive.
1367 * - The Allocation Bitmap file must be locked exclusive.
1368 * - The iterator key must be initialized.
1371 remove_attribute_records(struct hfsmount
*hfsmp
, BTreeIterator
* iterator
)
1373 struct filefork
*btfile
;
1374 FSBufferDescriptor btdata
;
1375 HFSPlusAttrRecord attrdata
; /* 90 bytes */
1379 btfile
= VTOF(hfsmp
->hfs_attribute_vp
);
1381 btdata
.bufferAddress
= &attrdata
;
1382 btdata
.itemSize
= sizeof(attrdata
);
1383 btdata
.itemCount
= 1;
1384 result
= BTSearchRecord(btfile
, iterator
, &btdata
, &datasize
, NULL
);
1386 goto exit
; /* no records. */
1389 * Free the blocks from extent based attributes.
1391 * Note that the block references (btree records) are removed
1392 * before releasing the blocks in the allocation bitmap.
1394 if (attrdata
.recordType
== kHFSPlusAttrForkData
) {
1397 u_int32_t
*keystartblk
;
1399 if (datasize
< sizeof(HFSPlusAttrForkData
)) {
1400 LFHFS_LOG(LEVEL_DEBUG
, "remove_attribute_records: bad record size %d (expecting %lu)\n", datasize
, sizeof(HFSPlusAttrForkData
));
1402 totalblks
= attrdata
.forkData
.theFork
.totalBlocks
;
1404 /* Process the first 8 extents. */
1405 extentblks
= count_extent_blocks(totalblks
, attrdata
.forkData
.theFork
.extents
);
1406 if (extentblks
> totalblks
)
1408 LFHFS_LOG(LEVEL_ERROR
, "remove_attribute_records: corruption (1)...");
1411 if (BTDeleteRecord(btfile
, iterator
) == 0) {
1412 free_attr_blks(hfsmp
, extentblks
, attrdata
.forkData
.theFork
.extents
);
1414 totalblks
-= extentblks
;
1415 keystartblk
= &((HFSPlusAttrKey
*)&iterator
->key
)->startBlock
;
1417 /* Process any overflow extents. */
1419 *keystartblk
+= (u_int32_t
)extentblks
;
1421 result
= BTSearchRecord(btfile
, iterator
, &btdata
, &datasize
, NULL
);
1423 (attrdata
.recordType
!= kHFSPlusAttrExtents
) ||
1424 (datasize
< sizeof(HFSPlusAttrExtents
))) {
1425 LFHFS_LOG(LEVEL_ERROR
, "remove_attribute_records: BTSearchRecord: vol=%s, err=%d (%d), totalblks %d\n",
1426 hfsmp
->vcbVN
, MacToVFSError(result
), attrdata
.recordType
!= kHFSPlusAttrExtents
, totalblks
);
1428 break; /* break from while */
1430 /* Process the next 8 extents. */
1431 extentblks
= count_extent_blocks(totalblks
, attrdata
.overflowExtents
.extents
);
1432 if (extentblks
> totalblks
)
1434 LFHFS_LOG(LEVEL_ERROR
, "remove_attribute_records: corruption (2)...");
1437 if (BTDeleteRecord(btfile
, iterator
) == 0) {
1438 free_attr_blks(hfsmp
, extentblks
, attrdata
.overflowExtents
.extents
);
1440 totalblks
-= extentblks
;
1443 result
= BTDeleteRecord(btfile
, iterator
);
1445 (void) BTFlushPath(btfile
);
1447 return (result
== btNotFound
? ENOATTR
: MacToVFSError(result
));
1451 * Retrieve the list of extended attribute names.
1454 hfs_vnop_listxattr(vnode_t vp
, void *buf
, size_t bufsize
, size_t *actual_size
)
1456 struct cnode
*cp
= VTOC(vp
);
1457 struct hfsmount
*hfsmp
;
1458 BTreeIterator
* iterator
= NULL
;
1459 struct filefork
*btfile
;
1460 struct listattr_callback_state state
;
1463 u_int8_t finderinfo
[32];
1465 if (actual_size
== NULL
) {
1468 if (VNODE_IS_RSRC(vp
)) {
1476 * Take the truncate lock; this serializes us against the ioctl
1477 * to truncate data & reset the decmpfs state
1478 * in the compressed file handler.
1480 hfs_lock_truncate(cp
, HFS_SHARED_LOCK
, HFS_LOCK_DEFAULT
);
1482 /* Now the regular cnode lock (shared) */
1483 if ((result
= hfs_lock(cp
, HFS_SHARED_LOCK
, HFS_LOCK_DEFAULT
))) {
1484 hfs_unlock_truncate(cp
, HFS_LOCK_DEFAULT
);
1489 * Make a copy of the cnode's finderinfo to a local so we can
1490 * zero out the date added field. Also zero out the private type/creator
1493 bcopy(cp
->c_finderinfo
, finderinfo
, sizeof(finderinfo
));
1494 hfs_zero_hidden_fields (cp
, finderinfo
);
1496 /* Don't expose a symlink's private type/creator. */
1497 if (vnode_islnk(vp
)) {
1498 struct FndrFileInfo
*fip
;
1500 fip
= (struct FndrFileInfo
*)&finderinfo
;
1506 /* If Finder Info is non-empty then export it's name. */
1507 if (bcmp(finderinfo
, emptyfinfo
, sizeof(emptyfinfo
)) != 0) {
1509 *actual_size
+= sizeof(XATTR_FINDERINFO_NAME
);
1510 } else if (bufsize
< sizeof(XATTR_FINDERINFO_NAME
)) {
1514 *actual_size
+= sizeof(XATTR_FINDERINFO_NAME
);
1515 strcpy((char*)buf
, XATTR_FINDERINFO_NAME
);
1519 /* Bail if we don't have any extended attributes. */
1520 if ((hfsmp
->hfs_attribute_vp
== NULL
) ||
1521 (cp
->c_attr
.ca_recflags
& kHFSHasAttributesMask
) == 0) {
1525 btfile
= VTOF(hfsmp
->hfs_attribute_vp
);
1527 iterator
= hfs_mallocz(sizeof(*iterator
));
1529 result
= hfs_buildattrkey(cp
->c_fileid
, NULL
, (HFSPlusAttrKey
*)&iterator
->key
);
1534 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
, HFS_SHARED_LOCK
);
1536 result
= BTSearchRecord(btfile
, iterator
, NULL
, NULL
, NULL
);
1537 if (result
&& result
!= btNotFound
) {
1538 hfs_systemfile_unlock(hfsmp
, lockflags
);
1542 state
.fileID
= cp
->c_fileid
;
1544 state
.buf
= (buf
== NULL
? NULL
: ((u_int8_t
*)buf
+ *actual_size
));
1545 state
.bufsize
= bufsize
- *actual_size
;
1549 * Process entries starting just after iterator->key.
1551 result
= BTIterateRecords(btfile
, kBTreeNextRecord
, iterator
,
1552 (IterateCallBackProcPtr
)listattr_callback
, &state
);
1553 hfs_systemfile_unlock(hfsmp
, lockflags
);
1555 *actual_size
+= state
.size
;
1557 if (state
.result
|| result
== btNotFound
) {
1558 result
= state
.result
;
1564 hfs_unlock_truncate(cp
, HFS_LOCK_DEFAULT
);
1566 return MacToVFSError(result
);
1570 * Callback - called for each attribute record
1573 listattr_callback(const HFSPlusAttrKey
*key
, __unused
const HFSPlusAttrData
*data
, struct listattr_callback_state
*state
)
1575 char attrname
[XATTR_MAXNAMELEN
+ 1];
1579 if (state
->fileID
!= key
->fileID
) {
1581 return (0); /* stop */
1584 * Skip over non-primary keys
1586 if (key
->startBlock
!= 0) {
1587 return (1); /* continue */
1590 /* Convert the attribute name into UTF-8. */
1591 result
= utf8_encodestr(key
->attrName
, key
->attrNameLen
* sizeof(UniChar
),
1592 (u_int8_t
*)attrname
, (size_t *)&bytecount
, sizeof(attrname
), '/', UTF_ADD_NULL_TERM
);
1594 state
->result
= result
;
1595 return (0); /* stop */
1597 bytecount
++; /* account for null termination char */
1599 state
->size
+= bytecount
;
1601 if (state
->buf
!= NULL
) {
1602 if ((size_t)bytecount
> state
->bufsize
) {
1603 state
->result
= ERANGE
;
1604 return (0); /* stop */
1607 memcpy(state
->buf
, attrname
, bytecount
);
1609 state
->buf
= (state
->buf
== NULL
? NULL
: ((u_int8_t
*)state
->buf
+ bytecount
));
1610 state
->bufsize
-= bytecount
;
1612 return (1); /* continue */
1616 * Remove all the attributes from a cnode.
1618 * This function creates/ends its own transaction so that each
1619 * attribute is deleted in its own transaction (to avoid having
1620 * a transaction grow too large).
1622 * This function takes the necessary locks on the attribute
1623 * b-tree file and the allocation (bitmap) file.
1625 * NOTE: Upon sucecss, this function will return with an open
1626 * transaction. The reason we do it this way is because when we
1627 * delete the last attribute, we must make sure the flag in the
1628 * catalog record that indicates there are no more records is cleared.
1629 * The caller is responsible for doing this and *must* do it before
1630 * ending the transaction.
1633 hfs_removeallattr(struct hfsmount
*hfsmp
, u_int32_t fileid
, bool *open_transaction
)
1635 BTreeIterator
*iterator
= NULL
;
1636 HFSPlusAttrKey
*key
;
1637 struct filefork
*btfile
;
1638 int result
, lockflags
= 0;
1640 *open_transaction
= false;
1642 if (hfsmp
->hfs_attribute_vp
== NULL
)
1645 btfile
= VTOF(hfsmp
->hfs_attribute_vp
);
1647 iterator
= hfs_mallocz(sizeof(BTreeIterator
));
1648 if (iterator
== NULL
)
1651 key
= (HFSPlusAttrKey
*)&iterator
->key
;
1653 /* Loop until there are no more attributes for this file id */
1655 if (!*open_transaction
)
1656 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
, HFS_SHARED_LOCK
);
1658 (void) hfs_buildattrkey(fileid
, NULL
, key
);
1659 result
= BTIterateRecord(btfile
, kBTreeNextRecord
, iterator
, NULL
, NULL
);
1660 if (result
|| key
->fileID
!= fileid
)
1663 hfs_systemfile_unlock(hfsmp
, lockflags
);
1666 if (*open_transaction
) {
1667 hfs_end_transaction(hfsmp
);
1668 *open_transaction
= false;
1671 if (hfs_start_transaction(hfsmp
) != 0) {
1676 *open_transaction
= true;
1678 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_ATTRIBUTE
| SFL_BITMAP
, HFS_EXCLUSIVE_LOCK
);
1680 result
= remove_attribute_records(hfsmp
, iterator
);
1688 hfs_systemfile_unlock(hfsmp
, lockflags
);
1690 result
= result
== btNotFound
? 0 : MacToVFSError(result
);
1692 if (result
&& *open_transaction
) {
1693 hfs_end_transaction(hfsmp
);
1694 *open_transaction
= false;
1701 * hfs_attrkeycompare - compare two attribute b-tree keys.
1703 * The name portion of the key is compared using a 16-bit binary comparison.
1704 * This is called from the b-tree code.
1707 hfs_attrkeycompare(HFSPlusAttrKey
*searchKey
, HFSPlusAttrKey
*trialKey
)
1709 u_int32_t searchFileID
, trialFileID
;
1712 searchFileID
= searchKey
->fileID
;
1713 trialFileID
= trialKey
->fileID
;
1716 if (searchFileID
> trialFileID
) {
1718 } else if (searchFileID
< trialFileID
) {
1721 u_int16_t
* str1
= &searchKey
->attrName
[0];
1722 u_int16_t
* str2
= &trialKey
->attrName
[0];
1723 int length1
= searchKey
->attrNameLen
;
1724 int length2
= trialKey
->attrNameLen
;
1728 if (length1
< length2
) {
1731 } else if (length1
> length2
) {
1754 * Names are equal; compare startBlock
1756 if (searchKey
->startBlock
== trialKey
->startBlock
) {
1759 return (searchKey
->startBlock
< trialKey
->startBlock
? -1 : 1);
1767 * hfs_buildattrkey - build an Attribute b-tree key
1770 hfs_buildattrkey(u_int32_t fileID
, const char *attrname
, HFSPlusAttrKey
*key
)
1773 size_t unicodeBytes
= 0;
1775 if (attrname
!= NULL
) {
1777 * Convert filename from UTF-8 into Unicode
1779 result
= utf8_decodestr((const u_int8_t
*)attrname
, strlen(attrname
), key
->attrName
,
1780 &unicodeBytes
, sizeof(key
->attrName
), 0, 0);
1782 if (result
!= ENAMETOOLONG
)
1783 result
= EINVAL
; /* name has invalid characters */
1786 key
->attrNameLen
= unicodeBytes
/ sizeof(UniChar
);
1787 key
->keyLength
= kHFSPlusAttrKeyMinimumLength
+ unicodeBytes
;
1789 key
->attrNameLen
= 0;
1790 key
->keyLength
= kHFSPlusAttrKeyMinimumLength
;
1793 key
->fileID
= fileID
;
1794 key
->startBlock
= 0;
1800 * getnodecount - calculate starting node count for attributes b-tree.
1803 getnodecount(struct hfsmount
*hfsmp
, size_t nodesize
)
1805 u_int64_t freebytes
;
1806 u_int64_t calcbytes
;
1809 * 10.4: Scale base on current catalog file size (20 %) up to 20 MB.
1810 * 10.5: Attempt to be as big as the catalog clump size.
1812 * Use no more than 10 % of the remaining free space.
1814 freebytes
= (u_int64_t
)hfs_freeblks(hfsmp
, 0) * (u_int64_t
)hfsmp
->blockSize
;
1816 calcbytes
= MIN(hfsmp
->hfs_catalog_cp
->c_datafork
->ff_size
/ 5, 20 * 1024 * 1024);
1818 calcbytes
= MAX(calcbytes
, hfsmp
->hfs_catalog_cp
->c_datafork
->ff_clumpsize
);
1820 calcbytes
= MIN(calcbytes
, freebytes
/ 10);
1822 return (MAX(2, (int)(calcbytes
/ nodesize
)));
1826 * getmaxinlineattrsize - calculate maximum inline attribute size.
1828 * This yields 3,802 bytes for an 8K node size.
1831 getmaxinlineattrsize(struct vnode
* attrvp
)
1833 BTreeInfoRec btinfo
;
1834 size_t nodesize
= ATTRIBUTE_FILE_NODE_SIZE
;
1837 if (attrvp
!= NULL
) {
1838 (void) hfs_lock(VTOC(attrvp
), HFS_SHARED_LOCK
, HFS_LOCK_DEFAULT
);
1839 if (BTGetInformation(VTOF(attrvp
), 0, &btinfo
) == 0)
1840 nodesize
= btinfo
.nodeSize
;
1841 hfs_unlock(VTOC(attrvp
));
1844 maxsize
-= sizeof(BTNodeDescriptor
); /* minus node descriptor */
1845 maxsize
-= 3 * sizeof(u_int16_t
); /* minus 3 index slots */
1846 maxsize
/= 2; /* 2 key/rec pairs minumum */
1847 maxsize
-= sizeof(HFSPlusAttrKey
); /* minus maximum key size */
1848 maxsize
-= sizeof(HFSPlusAttrData
) - 2; /* minus data header */
1849 maxsize
&= 0xFFFFFFFE; /* multiple of 2 bytes */