]> git.saurik.com Git - apple/hfs.git/blob - core/hfs_xattr.c
hfs-556.100.11.tar.gz
[apple/hfs.git] / core / hfs_xattr.c
1 /*
2 * Copyright (c) 2004-2020 Apple Inc. All rights reserved.
3 *
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. 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.
14 *
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
17 *
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.
25 *
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27 */
28
29 #include <sys/param.h>
30 #include <sys/systm.h>
31 #include <sys/kernel.h>
32 #include <sys/malloc.h>
33 #include <sys/ubc.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>
40
41 #include "hfs.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"
49
50 #include "BTreesInternal.h"
51
52 #define HFS_XATTR_VERBOSE 0
53
54 #define ATTRIBUTE_FILE_NODE_SIZE 8192
55
56
57 /* State information for the listattr_callback callback function. */
58 struct listattr_callback_state {
59 u_int32_t fileID;
60 int result;
61 uio_t uio;
62 size_t size;
63 #if HFS_COMPRESSION
64 int showcompressed;
65 vfs_context_t ctx;
66 vnode_t vp;
67 #endif /* HFS_COMPRESSION */
68 };
69
70
71 /* HFS Internal Names */
72 #define XATTR_XATTREXTENTS_NAME "system.xattrextents"
73
74 static u_int32_t emptyfinfo[8] = {0};
75
76 static int hfs_zero_hidden_fields (struct cnode *cp, u_int8_t *finderinfo);
77
78 const char hfs_attrdatafilename[] = "Attribute Data";
79
80 static int listattr_callback(const HFSPlusAttrKey *key, const HFSPlusAttrData *data,
81 struct listattr_callback_state *state);
82
83 static int remove_attribute_records(struct hfsmount *hfsmp, BTreeIterator * iterator);
84
85 static int getnodecount(struct hfsmount *hfsmp, size_t nodesize);
86
87 static size_t getmaxinlineattrsize(struct vnode * attrvp);
88
89 static int read_attr_data(struct hfsmount *hfsmp, uio_t uio, size_t datasize, HFSPlusExtentDescriptor *extents);
90
91 static int write_attr_data(struct hfsmount *hfsmp, uio_t uio, size_t datasize, HFSPlusExtentDescriptor *extents);
92
93 static int alloc_attr_blks(struct hfsmount *hfsmp, size_t attrsize, size_t extentbufsize, HFSPlusExtentDescriptor *extents, int *blocks);
94
95 static void free_attr_blks(struct hfsmount *hfsmp, int blkcnt, HFSPlusExtentDescriptor *extents);
96
97 static int has_overflow_extents(HFSPlusForkData *forkdata);
98
99 static int count_extent_blocks(int maxblks, HFSPlusExtentRecord extents);
100
101 #if NEW_XATTR
102
103 /*
104 * Iterator over set bits within an xattr free fext bitmap.
105 *
106 * See xbm_make_iter() for details on creation.
107 */
108 typedef struct {
109 const uint64_t *bitmap;
110 size_t i;
111 size_t len;
112 } xbm_iter_t;
113
114 // xattr IO subsystem assertion
115 #define xattr_assert(cond) if(!(cond)) do { \
116 panic("xattr_assert() failed: %s",#cond); \
117 } while(0)
118
119 #define xattr_infomsg(...) // errmsg(__VA_ARGS__)
120
121 /*
122 * Purely for documentation purposes, simulate C++ const_cast<>() in the C
123 * language.
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.
127 * E.g.
128 *
129 * ancient_sum(a, count)
130 * int *a; int count;
131 * {
132 * int s;
133 * s = 0;
134 * while (count--) {
135 * s = s + a[i];
136 * }
137 * return s;
138 * }
139 *
140 * int modern_sum(const int *a, int count) {
141 * return ancient_sum(const_cast(int *, a), count);
142 * }
143 */
144 #define const_cast(type, expr) ((type)(expr))
145
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,
148 uint32_t fs_bsize);
149
150 static bool fext2cluster_check(const HFSPlusExtentDescriptor *fext, uint32_t fs_bsize,
151 uint64_t *out);
152 static uint32_t xattr_cluster_scale(uint32_t fs_bsize);
153
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);
163
164 static size_t xattr_fext_index(const xattr_io_info_t *info,
165 const HFSPlusExtentDescriptor **xfext);
166
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);
171
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);
174
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);
179
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);
184 #endif
185
186 #if NAMEDSTREAMS
187 /*
188 * Obtain the vnode for a stream.
189 */
190 int
191 hfs_vnop_getnamedstream(struct vnop_getnamedstream_args* ap)
192 {
193 vnode_t vp = ap->a_vp;
194 vnode_t *svpp = ap->a_svpp;
195 struct cnode *cp;
196 int error = 0;
197
198 *svpp = NULL;
199
200 /*
201 * We only support the "com.apple.ResourceFork" stream.
202 */
203 if (strcmp(ap->a_name, XATTR_RESOURCEFORK_NAME) != 0) {
204 return (ENOATTR);
205 }
206 cp = VTOC(vp);
207 if ( !S_ISREG(cp->c_mode) ) {
208 return (EPERM);
209 }
210 #if HFS_COMPRESSION
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))) {
214 return (error);
215 }
216 if ((!hfs_has_rsrc(cp)
217 #if HFS_COMPRESSION
218 || hide_rsrc
219 #endif /* HFS_COMPRESSION */
220 ) && (ap->a_operation != NS_OPEN)) {
221 hfs_unlock(cp);
222 return (ENOATTR);
223 }
224 error = hfs_vgetrsrc(VTOHFS(vp), vp, svpp);
225 hfs_unlock(cp);
226
227 return (error);
228 }
229
230 /*
231 * Create a stream.
232 */
233 int
234 hfs_vnop_makenamedstream(struct vnop_makenamedstream_args* ap)
235 {
236 vnode_t vp = ap->a_vp;
237 vnode_t *svpp = ap->a_svpp;
238 struct cnode *cp;
239 int error = 0;
240
241 *svpp = NULL;
242
243 /*
244 * We only support the "com.apple.ResourceFork" stream.
245 */
246 if (strcmp(ap->a_name, XATTR_RESOURCEFORK_NAME) != 0) {
247 return (ENOATTR);
248 }
249 cp = VTOC(vp);
250 if ( !S_ISREG(cp->c_mode) ) {
251 return (EPERM);
252 }
253 #if HFS_COMPRESSION
254 if (hfs_hides_rsrc(ap->a_context, VTOC(vp), 1)) {
255 if (VNODE_IS_RSRC(vp)) {
256 return EINVAL;
257 } else {
258 error = decmpfs_decompress_file(vp, VTOCMP(vp), -1, 1, 0);
259 if (error != 0)
260 return error;
261 }
262 }
263 #endif /* HFS_COMPRESSION */
264 if ((error = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) {
265 return (error);
266 }
267 error = hfs_vgetrsrc(VTOHFS(vp), vp, svpp);
268 hfs_unlock(cp);
269
270 return (error);
271 }
272
273 /*
274 * Remove a stream.
275 */
276 int
277 hfs_vnop_removenamedstream(struct vnop_removenamedstream_args* ap)
278 {
279 vnode_t svp = ap->a_svp;
280 cnode_t *scp = VTOC(svp);
281 int error = 0;
282
283 /*
284 * We only support the "com.apple.ResourceFork" stream.
285 */
286 if (strcmp(ap->a_name, XATTR_RESOURCEFORK_NAME) != 0) {
287 return (ENOATTR);
288 }
289 #if HFS_COMPRESSION
290 if (hfs_hides_rsrc(ap->a_context, scp, 1)) {
291 /* do nothing */
292 return 0;
293 }
294 #endif /* HFS_COMPRESSION */
295
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);
300 }
301 hfs_unlock_truncate(scp, HFS_LOCK_DEFAULT);
302
303 return error;
304 }
305 #endif
306
307
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)
310 {
311 u_int8_t *finfo = finderinfo;
312
313 /* Advance finfo by 16 bytes to the 2nd half of the finderinfo */
314 finfo = finfo + 16;
315
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;
326 } else {
327 /* Return an error */
328 return -1;
329 }
330 return 0;
331
332 }
333
334 /*
335 * Retrieve the data of an extended attribute.
336 */
337 int
338 hfs_vnop_getxattr(struct vnop_getxattr_args *ap)
339 /*
340 struct vnop_getxattr_args {
341 struct vnodeop_desc *a_desc;
342 vnode_t a_vp;
343 char * a_name;
344 uio_t a_uio;
345 size_t *a_size;
346 int a_options;
347 vfs_context_t a_context;
348 };
349 */
350 {
351 struct vnode *vp = ap->a_vp;
352 struct cnode *cp;
353 struct hfsmount *hfsmp;
354 uio_t uio = ap->a_uio;
355 size_t bufsize;
356 int result;
357
358 cp = VTOC(vp);
359 if (vp == cp->c_vp) {
360 #if HFS_COMPRESSION
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))
363 return ENOATTR;
364 #endif /* HFS_COMPRESSION */
365
366 /* Get the Finder Info. */
367 if (strcmp(ap->a_name, XATTR_FINDERINFO_NAME) == 0) {
368 u_int8_t finderinfo[32];
369 bufsize = 32;
370
371 if ((result = hfs_lock(cp, HFS_SHARED_LOCK, HFS_LOCK_DEFAULT))) {
372 return (result);
373 }
374 /* Make a copy since we may not export all of it. */
375 bcopy(cp->c_finderinfo, finderinfo, sizeof(finderinfo));
376 hfs_unlock(cp);
377
378 /* Zero out the date added field in the local copy */
379 hfs_zero_hidden_fields (cp, finderinfo);
380
381 /* Don't expose a symlink's private type/creator. */
382 if (vnode_islnk(vp)) {
383 struct FndrFileInfo *fip;
384
385 fip = (struct FndrFileInfo *)&finderinfo;
386 fip->fdType = 0;
387 fip->fdCreator = 0;
388 }
389 /* If Finder Info is empty then it doesn't exist. */
390 if (bcmp(finderinfo, emptyfinfo, sizeof(emptyfinfo)) == 0) {
391 return (ENOATTR);
392 }
393 if (uio == NULL) {
394 *ap->a_size = bufsize;
395 return (0);
396 }
397 if ((user_size_t)uio_resid(uio) < bufsize)
398 return (ERANGE);
399
400 result = uiomove((caddr_t)&finderinfo , bufsize, uio);
401
402 return (result);
403 }
404 /* Read the Resource Fork. */
405 if (strcmp(ap->a_name, XATTR_RESOURCEFORK_NAME) == 0) {
406 struct vnode *rvp = NULL;
407 int openunlinked = 0;
408 int namelen = 0;
409
410 if ( !S_ISREG(cp->c_mode) ) {
411 return (EPERM);
412 }
413 if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) {
414 return (result);
415 }
416 namelen = cp->c_desc.cd_namelen;
417
418 if (!hfs_has_rsrc(cp)) {
419 hfs_unlock(cp);
420 return (ENOATTR);
421 }
422 hfsmp = VTOHFS(vp);
423 if ((cp->c_flag & C_DELETED) && (namelen == 0)) {
424 openunlinked = 1;
425 }
426
427 result = hfs_vgetrsrc(hfsmp, vp, &rvp);
428 hfs_unlock(cp);
429 if (result) {
430 return (result);
431 }
432 if (uio == NULL) {
433 *ap->a_size = (size_t)VTOF(rvp)->ff_size;
434 } else {
435 #if HFS_COMPRESSION
436 user_ssize_t uio_size = 0;
437 if (decmpfs_hide)
438 uio_size = uio_resid(uio);
439 #endif /* HFS_COMPRESSION */
440 result = VNOP_READ(rvp, uio, 0, ap->a_context);
441 #if HFS_COMPRESSION
442 if (decmpfs_hide &&
443 (result == 0) &&
444 (uio_resid(uio) == uio_size)) {
445 /*
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.
450 *
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)
454 *
455 * In that case, we try again with the decmpfs_ctx context
456 * to get the actual data
457 */
458 result = VNOP_READ(rvp, uio, 0, decmpfs_ctx);
459 }
460 #endif /* HFS_COMPRESSION */
461 }
462 /* force the rsrc fork vnode to recycle right away */
463 if (openunlinked) {
464 int vref;
465 vref = vnode_ref (rvp);
466 if (vref == 0) {
467 vnode_rele (rvp);
468 }
469 vnode_recycle(rvp);
470 }
471 vnode_put(rvp);
472 return (result);
473 }
474 }
475 hfsmp = VTOHFS(vp);
476 #if CONFIG_HFS_STD
477 /*
478 * Standard HFS only supports native FinderInfo and Resource Forks.
479 */
480 if (hfsmp->hfs_flags & HFS_STANDARD) {
481 return (EPERM);
482 }
483 #endif
484
485 if ((result = hfs_lock(cp, HFS_SHARED_LOCK, HFS_LOCK_DEFAULT))) {
486 return (result);
487 }
488
489 /* Check for non-rsrc, non-finderinfo EAs */
490 result = hfs_getxattr_internal (cp, ap, VTOHFS(cp->c_vp), 0);
491
492 hfs_unlock(cp);
493
494 return MacToVFSError(result);
495 }
496
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)
499 {
500 uio_t uio = uio_create(1, 0, UIO_SYSSPACE, UIO_READ);
501
502 uio_addiov(uio, CAST_USER_ADDR_T(data), *size);
503
504 struct vnop_getxattr_args args = {
505 .a_uio = uio,
506 .a_name = name,
507 .a_size = size
508 };
509
510 int ret = hfs_getxattr_internal(VTOC(vp), &args, VTOHFS(vp), 0);
511
512 uio_free(uio);
513
514 return ret;
515 }
516
517 /*
518 * getxattr_internal
519 *
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.
525 *
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
530 *
531 * NOTE: This function assumes the cnode lock for 'cp' is held exclusive or shared.
532 */
533 int hfs_getxattr_internal (struct cnode *cp, struct vnop_getxattr_args *ap,
534 struct hfsmount *hfsmp, u_int32_t fileid)
535 {
536
537 struct filefork *btfile;
538 struct BTreeIterator * iterator = NULL;
539 size_t bufsize = 0;
540 HFSPlusAttrRecord *recp = NULL;
541 size_t recp_size = 0;
542 FSBufferDescriptor btdata;
543 int lockflags = 0;
544 int result = 0;
545 u_int16_t datasize = 0;
546 uio_t uio = ap->a_uio;
547 u_int32_t target_id = 0;
548
549 if (cp) {
550 target_id = cp->c_fileid;
551 } else {
552 target_id = fileid;
553 }
554
555
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)) {
559 result = ENOATTR;
560 goto exit;
561 }
562
563 /* Initialize the B-Tree iterator for searching for the proper EA */
564 btfile = VTOF(hfsmp->hfs_attribute_vp);
565
566 iterator = hfs_mallocz(sizeof(*iterator));
567
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.
571 */
572 recp = hfs_malloc(recp_size = sizeof(HFSPlusAttrRecord));
573 btdata.bufferAddress = recp;
574 btdata.itemSize = sizeof(HFSPlusAttrRecord);
575 btdata.itemCount = 1;
576
577 result = hfs_buildattrkey(target_id, ap->a_name, (HFSPlusAttrKey *)&iterator->key);
578 if (result) {
579 goto exit;
580 }
581
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);
586
587 if (result) {
588 if (result == btNotFound) {
589 result = ENOATTR;
590 }
591 goto exit;
592 }
593
594 /*
595 * Operate differently if we have inline EAs that can fit in the attribute B-Tree or if
596 * we have extent based EAs.
597 */
598 switch (recp->recordType) {
599
600 /* Attribute fits in the Attribute B-Tree */
601 case kHFSPlusAttrInlineData: {
602 /*
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).
607 */
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));
611 result = ENOATTR;
612 break;
613 }
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 */
618 result = ERANGE;
619 } else {
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.
623 */
624 bufsize = sizeof(HFSPlusAttrData) - 2 + recp->attrData.attrSize;
625 hfs_free(recp, recp_size);
626 recp = hfs_malloc(recp_size = bufsize);
627
628 btdata.bufferAddress = recp;
629 btdata.itemSize = bufsize;
630 btdata.itemCount = 1;
631
632 bzero(iterator, sizeof(*iterator));
633 result = hfs_buildattrkey(target_id, ap->a_name, (HFSPlusAttrKey *)&iterator->key);
634 if (result) {
635 goto exit;
636 }
637
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);
642 if (result) {
643 if (result == btNotFound) {
644 result = ENOATTR;
645 }
646 goto exit;
647 }
648
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);
652 }
653 }
654 break;
655 }
656
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));
662 result = ENOATTR;
663 break;
664 }
665 *ap->a_size = recp->forkData.theFork.logicalSize;
666 if (uio == NULL) {
667 break;
668 }
669 if (*ap->a_size > (user_size_t)uio_resid(uio)) {
670 result = ERANGE;
671 break;
672 }
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;
679 u_int32_t blkcnt;
680 u_int32_t attrlen;
681
682 totalblocks = recp->forkData.theFork.totalBlocks;
683 /* Ignore bogus block counts. */
684 if (totalblocks > howmany(HFS_XATTR_MAXSIZE, hfsmp->blockSize)) {
685 result = ERANGE;
686 break;
687 }
688 attrlen = recp->forkData.theFork.logicalSize;
689
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;
695
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);
700
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);
706 if (result ||
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);
711 result = ENOATTR;
712 break; /* break from while */
713 }
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);
718 }
719
720 /* Release Attr B-Tree lock */
721 hfs_systemfile_unlock(hfsmp, lockflags);
722
723 if (blkcnt < totalblocks) {
724 result = ENOATTR;
725 } else {
726 result = read_attr_data(hfsmp, uio, attrlen, extentbuf);
727 }
728 hfs_free(extentbuf, extentbufsize);
729
730 } else { /* No overflow extents. */
731 result = read_attr_data(hfsmp, uio, recp->forkData.theFork.logicalSize, recp->forkData.theFork.extents);
732 }
733 break;
734 }
735
736 default:
737 /* We only support Extent or inline EAs. Default to ENOATTR for anything else */
738 result = ENOATTR;
739 break;
740 }
741
742 exit:
743 hfs_free(iterator, sizeof(*iterator));
744 hfs_free(recp, recp_size);
745
746 return result;
747
748 }
749
750
751 /*
752 * Set the data of an extended attribute.
753 */
754 int
755 hfs_vnop_setxattr(struct vnop_setxattr_args *ap)
756 /*
757 struct vnop_setxattr_args {
758 struct vnodeop_desc *a_desc;
759 vnode_t a_vp;
760 char * a_name;
761 uio_t a_uio;
762 int a_options;
763 vfs_context_t a_context;
764 };
765 */
766 {
767 struct vnode *vp = ap->a_vp;
768 struct cnode *cp = NULL;
769 struct hfsmount *hfsmp;
770 uio_t uio = ap->a_uio;
771 size_t attrsize;
772 void * user_data_ptr = NULL;
773 int result;
774 time_t orig_ctime=VTOC(vp)->c_ctime;
775
776 if (ap->a_name == NULL || ap->a_name[0] == '\0') {
777 return (EINVAL); /* invalid name */
778 }
779 hfsmp = VTOHFS(vp);
780 if (VNODE_IS_RSRC(vp)) {
781 return (EPERM);
782 }
783
784 #if HFS_COMPRESSION
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);
787 if (result != 0)
788 return result;
789 }
790 #endif /* HFS_COMPRESSION */
791
792 nspace_snapshot_event(vp, orig_ctime, NAMESPACE_HANDLER_METADATA_WRITE_OP, NSPACE_REARM_NO_ARG);
793
794 /* Set the Finder Info. */
795 if (strcmp(ap->a_name, XATTR_FINDERINFO_NAME) == 0) {
796 union {
797 uint8_t data[32];
798 char cdata[32];
799 struct FndrFileInfo info;
800 } fi;
801 void * finderinfo_start;
802 u_int8_t *finfo = NULL;
803 u_int16_t fdFlags;
804 u_int32_t dateadded = 0;
805 u_int32_t write_gen_counter = 0;
806 u_int32_t document_id = 0;
807
808 attrsize = sizeof(VTOC(vp)->c_finderinfo);
809
810 if ((user_size_t)uio_resid(uio) != attrsize) {
811 return (ERANGE);
812 }
813 /* Grab the new Finder Info data. */
814 if ((result = uiomove(fi.cdata, attrsize, uio))) {
815 return (result);
816 }
817
818 if ((result = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) {
819 return (result);
820 }
821 cp = VTOC(vp);
822
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];
827 attrsize -= 8;
828 } else {
829 finderinfo_start = &cp->c_finderinfo[0];
830 /*
831 * Don't allow the external setting of
832 * file type to kHardLinkFileType.
833 */
834 if (fi.info.fdType == SWAP_BE32(kHardLinkFileType)) {
835 hfs_unlock(cp);
836 return (EPERM);
837 }
838 }
839
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);
844 /*
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.
849 */
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;
856 }
857
858 /*
859 * Zero out the finder info's reserved fields like date added,
860 * generation counter, and document id to ignore user's attempts
861 * to set it
862 */
863 hfs_zero_hidden_fields(cp, fi.data);
864
865 if (bcmp(finderinfo_start, emptyfinfo, attrsize)) {
866 /* attr exists and "create" was specified. */
867 if (ap->a_options & XATTR_CREATE) {
868 hfs_unlock(cp);
869 return (EEXIST);
870 }
871 } else { /* empty */
872 /* attr doesn't exists and "replace" was specified. */
873 if (ap->a_options & XATTR_REPLACE) {
874 hfs_unlock(cp);
875 return (ENOATTR);
876 }
877 }
878
879 /*
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.
883 *
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.
887 */
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;
899 }
900
901 /* Set the cnode's Finder Info. */
902 if (attrsize == sizeof(cp->c_finderinfo)) {
903 bcopy(&fi.data[0], finderinfo_start, attrsize);
904 } else {
905 bcopy(&fi.data[8], finderinfo_start, attrsize);
906 }
907
908 /* Updating finderInfo updates change time and modified time */
909 cp->c_touch_chgtime = TRUE;
910 cp->c_flag |= C_MODIFIED;
911
912 /*
913 * Mirror the invisible bit to the UF_HIDDEN flag.
914 *
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.
918 */
919 fdFlags = *((u_int16_t *) &cp->c_finderinfo[8]);
920 if (fdFlags & OSSwapHostToBigConstInt16(kFinderInvisibleMask)) {
921 cp->c_bsdflags |= UF_HIDDEN;
922 } else {
923 cp->c_bsdflags &= ~UF_HIDDEN;
924 }
925
926 result = hfs_update(vp, 0);
927
928 hfs_unlock(cp);
929 return (result);
930 }
931 /* Write the Resource Fork. */
932 if (strcmp(ap->a_name, XATTR_RESOURCEFORK_NAME) == 0) {
933 struct vnode *rvp = NULL;
934 int namelen = 0;
935 int openunlinked = 0;
936
937 if (!vnode_isreg(vp)) {
938 return (EPERM);
939 }
940 if ((result = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) {
941 return (result);
942 }
943 cp = VTOC(vp);
944 namelen = cp->c_desc.cd_namelen;
945
946 if (hfs_has_rsrc(cp)) {
947 /* attr exists and "create" was specified. */
948 if (ap->a_options & XATTR_CREATE) {
949 hfs_unlock(cp);
950 return (EEXIST);
951 }
952 } else {
953 /* attr doesn't exists and "replace" was specified. */
954 if (ap->a_options & XATTR_REPLACE) {
955 hfs_unlock(cp);
956 return (ENOATTR);
957 }
958 }
959
960 /*
961 * Note that we could be called on to grab the rsrc fork vnode
962 * for a file that has become open-unlinked.
963 */
964 if ((cp->c_flag & C_DELETED) && (namelen == 0)) {
965 openunlinked = 1;
966 }
967
968 result = hfs_vgetrsrc(hfsmp, vp, &rvp);
969 hfs_unlock(cp);
970 if (result) {
971 return (result);
972 }
973 /* VNOP_WRITE marks cnode as needing a modtime update */
974 result = VNOP_WRITE(rvp, uio, 0, ap->a_context);
975
976 /* if open unlinked, force it inactive */
977 if (openunlinked) {
978 int vref;
979 vref = vnode_ref (rvp);
980 if (vref == 0) {
981 vnode_rele(rvp);
982 }
983 vnode_recycle (rvp);
984 } else {
985 /* cnode is not open-unlinked, so re-lock cnode to sync */
986 if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) {
987 vnode_recycle (rvp);
988 vnode_put(rvp);
989 return result;
990 }
991
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));
994 hfs_unlock (cp);
995 }
996
997 vnode_put(rvp);
998 return (result);
999 }
1000 #if CONFIG_HFS_STD
1001 /*
1002 * Standard HFS only supports native FinderInfo and Resource Forks.
1003 */
1004 if (hfsmp->hfs_flags & HFS_STANDARD) {
1005 return (EPERM);
1006 }
1007 #endif
1008 attrsize = uio_resid(uio);
1009
1010 /* Enforce an upper limit. */
1011 if (attrsize > HFS_XATTR_MAXSIZE) {
1012 result = E2BIG;
1013 goto exit;
1014 }
1015
1016 /*
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.
1020 */
1021 if (attrsize > 0 &&
1022 hfsmp->hfs_max_inline_attrsize != 0 &&
1023 attrsize < hfsmp->hfs_max_inline_attrsize) {
1024 user_data_ptr = hfs_malloc(attrsize);
1025
1026 result = uiomove((caddr_t)user_data_ptr, attrsize, uio);
1027 if (result) {
1028 goto exit;
1029 }
1030 }
1031
1032 result = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT);
1033 if (result) {
1034 goto exit;
1035 }
1036 cp = VTOC(vp);
1037
1038 /*
1039 * If we're trying to set a non-finderinfo, non-resourcefork EA, then
1040 * call the breakout function.
1041 */
1042 result = hfs_setxattr_internal (cp, user_data_ptr, attrsize, ap, VTOHFS(vp), 0);
1043
1044 exit:
1045 if (cp) {
1046 hfs_unlock(cp);
1047 }
1048 if (user_data_ptr) {
1049 hfs_free(user_data_ptr, attrsize);
1050 }
1051
1052 return (result == btNotFound ? ENOATTR : MacToVFSError(result));
1053 }
1054
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)
1057 {
1058 struct vnop_setxattr_args args = {
1059 .a_vp = vp,
1060 .a_name = name,
1061 };
1062
1063 return hfs_setxattr_internal(VTOC(vp), data, size, &args, VTOHFS(vp), 0);
1064 }
1065
1066 /*
1067 * hfs_setxattr_internal
1068 *
1069 * Internal function to set non-rsrc, non-finderinfo EAs to either the attribute B-Tree or
1070 * extent-based EAs.
1071 *
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.
1074 *
1075 * Assumptions:
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.
1081 */
1082 int hfs_setxattr_internal (struct cnode *cp, const void *data_ptr, size_t attrsize,
1083 struct vnop_setxattr_args *ap, struct hfsmount *hfsmp,
1084 u_int32_t fileid)
1085 {
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;
1097 int result = 0;
1098 int lockflags = 0;
1099 int exists = 0;
1100 int allocatedblks = 0;
1101 u_int32_t target_id;
1102
1103 if (cp) {
1104 target_id = cp->c_fileid;
1105 } else {
1106 target_id = fileid;
1107 }
1108
1109 /* Start a transaction for our changes. */
1110 if (hfs_start_transaction(hfsmp) != 0) {
1111 result = EINVAL;
1112 goto exit;
1113 }
1114 started_transaction = 1;
1115
1116 /*
1117 * Once we started the transaction, nobody can compete
1118 * with us, so make sure this file is still there.
1119 */
1120 if ((cp) && (cp->c_flag & C_NOEXISTS)) {
1121 result = ENOENT;
1122 goto exit;
1123 }
1124
1125 /*
1126 * If there isn't an attributes b-tree then create one.
1127 */
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));
1131 if (result) {
1132 goto exit;
1133 }
1134 }
1135 if (hfsmp->hfs_max_inline_attrsize == 0) {
1136 hfsmp->hfs_max_inline_attrsize = getmaxinlineattrsize(hfsmp->hfs_attribute_vp);
1137 }
1138
1139 lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_EXCLUSIVE_LOCK);
1140
1141 /* Build the b-tree key. */
1142 iterator = hfs_mallocz(sizeof(*iterator));
1143 result = hfs_buildattrkey(target_id, ap->a_name, (HFSPlusAttrKey *)&iterator->key);
1144 if (result) {
1145 goto exit;
1146 }
1147
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;
1154
1155 /* Replace requires that the attribute already exists. */
1156 if ((ap->a_options & XATTR_REPLACE) && !exists) {
1157 result = ENOATTR;
1158 goto exit;
1159 }
1160 /* Create requires that the attribute doesn't exist. */
1161 if ((ap->a_options & XATTR_CREATE) && exists) {
1162 result = EEXIST;
1163 goto exit;
1164 }
1165
1166 /* If it won't fit inline then use extent-based attributes. */
1167 if (attrsize > hfsmp->hfs_max_inline_attrsize) {
1168 int blkcnt;
1169 int extentblks;
1170 u_int32_t *keystartblk;
1171 int i;
1172
1173 if (uio == NULL) {
1174 /*
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
1177 * smaller EA.
1178 */
1179 result = EPERM;
1180 goto exit;
1181 }
1182
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);
1189 if (result) {
1190 allocatedblks = 0;
1191 goto exit; /* no more space */
1192 }
1193 /* Copy data into the blocks. */
1194 result = write_attr_data(hfsmp, uio, attrsize, extentptr);
1195 if (result) {
1196 if (vp) {
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);
1200 if (name)
1201 vnode_putname(name);
1202 }
1203 goto exit;
1204 }
1205
1206 /* Now remove any previous attribute. */
1207 if (exists) {
1208 result = remove_attribute_records(hfsmp, iterator);
1209 if (result) {
1210 if (vp) {
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);
1214 if (name)
1215 vnode_putname(name);
1216 }
1217 goto exit;
1218 }
1219 }
1220 /* Create attribute fork data record. */
1221 recp = hfs_malloc(recp_size = sizeof(HFSPlusAttrRecord));
1222
1223 btdata.bufferAddress = recp;
1224 btdata.itemCount = 1;
1225 btdata.itemSize = sizeof(HFSPlusAttrForkData);
1226
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));
1233
1234 (void) hfs_buildattrkey(target_id, ap->a_name, (HFSPlusAttrKey *)&iterator->key);
1235
1236 result = BTInsertRecord(btfile, iterator, &btdata, btdata.itemSize);
1237 if (result) {
1238 printf ("hfs_setxattr: BTInsertRecord(): vol=%s %d,%s err=%d\n",
1239 hfsmp->vcbVN, target_id, ap->a_name, result);
1240 goto exit;
1241 }
1242 extentblks = count_extent_blocks(blkcnt, recp->forkData.theFork.extents);
1243 blkcnt -= extentblks;
1244 keystartblk = &((HFSPlusAttrKey *)&iterator->key)->startBlock;
1245 i = 0;
1246
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;
1254
1255 /* Copy the next set of extents. */
1256 i += kHFSPlusExtentDensity;
1257 bcopy(&extentptr[i], recp->overflowExtents.extents, sizeof(HFSPlusExtentRecord));
1258
1259 result = BTInsertRecord(btfile, iterator, &btdata, btdata.itemSize);
1260 if (result) {
1261 printf ("hfs_setxattr: BTInsertRecord() overflow: vol=%s %d,%s err=%d\n",
1262 hfsmp->vcbVN, target_id, ap->a_name, result);
1263 goto exit;
1264 }
1265 extentblks = count_extent_blocks(blkcnt, recp->overflowExtents.extents);
1266 blkcnt -= extentblks;
1267 }
1268 } else { /* Inline data */
1269 if (exists) {
1270 result = remove_attribute_records(hfsmp, iterator);
1271 if (result) {
1272 goto exit;
1273 }
1274 }
1275
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);
1279
1280 recp->recordType = kHFSPlusAttrInlineData;
1281 recp->attrData.reserved[0] = 0;
1282 recp->attrData.reserved[1] = 0;
1283 recp->attrData.attrSize = attrsize;
1284
1285 /* Copy in the attribute data (if any). */
1286 if (attrsize > 0) {
1287 if (data_ptr) {
1288 bcopy(data_ptr, &recp->attrData.attrData, attrsize);
1289 } else {
1290 /*
1291 * A null UIO meant it originated in-kernel. If they didn't supply data_ptr
1292 * then deny the copy operation.
1293 */
1294 if (uio == NULL) {
1295 result = EPERM;
1296 goto exit;
1297 }
1298 result = uiomove((caddr_t)&recp->attrData.attrData, attrsize, uio);
1299 }
1300
1301 if (result) {
1302 goto exit;
1303 }
1304 }
1305
1306 (void) hfs_buildattrkey(target_id, ap->a_name, (HFSPlusAttrKey *)&iterator->key);
1307
1308 btdata.bufferAddress = recp;
1309 btdata.itemCount = 1;
1310 result = BTInsertRecord(btfile, iterator, &btdata, btdata.itemSize);
1311 }
1312
1313 exit:
1314 if (btfile && started_transaction) {
1315 (void) BTFlushPath(btfile);
1316 }
1317 hfs_systemfile_unlock(hfsmp, lockflags);
1318 if (result == 0) {
1319 if (vp) {
1320 cp = VTOC(vp);
1321 /* Setting an attribute only updates change time and not
1322 * modified time of the file.
1323 */
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;
1329 }
1330 (void) hfs_update(vp, 0);
1331 }
1332 }
1333 if (started_transaction) {
1334 if (result && allocatedblks) {
1335 free_attr_blks(hfsmp, allocatedblks, extentptr);
1336 }
1337 hfs_end_transaction(hfsmp);
1338 }
1339
1340 hfs_free(recp, recp_size);
1341 hfs_free(extentptr, extentbufsize);
1342 hfs_free(iterator, sizeof(*iterator));
1343
1344 return result;
1345 }
1346
1347
1348
1349
1350 /*
1351 * Remove an extended attribute.
1352 */
1353 int
1354 hfs_vnop_removexattr(struct vnop_removexattr_args *ap)
1355 /*
1356 struct vnop_removexattr_args {
1357 struct vnodeop_desc *a_desc;
1358 vnode_t a_vp;
1359 char * a_name;
1360 int a_options;
1361 vfs_context_t a_context;
1362 };
1363 */
1364 {
1365 struct vnode *vp = ap->a_vp;
1366 struct cnode *cp = VTOC(vp);
1367 struct hfsmount *hfsmp;
1368 struct BTreeIterator * iterator = NULL;
1369 int lockflags;
1370 int result;
1371 time_t orig_ctime=VTOC(vp)->c_ctime;
1372
1373 if (ap->a_name == NULL || ap->a_name[0] == '\0') {
1374 return (EINVAL); /* invalid name */
1375 }
1376 hfsmp = VTOHFS(vp);
1377 if (VNODE_IS_RSRC(vp)) {
1378 return (EPERM);
1379 }
1380
1381 #if HFS_COMPRESSION
1382 if (hfs_hides_xattr(ap->a_context, VTOC(vp), ap->a_name, 1) && !(ap->a_options & XATTR_SHOWCOMPRESSION)) {
1383 return ENOATTR;
1384 }
1385 #endif /* HFS_COMPRESSION */
1386
1387 nspace_snapshot_event(vp, orig_ctime, NAMESPACE_HANDLER_METADATA_DELETE_OP, NSPACE_REARM_NO_ARG);
1388
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;
1392
1393 if ( !vnode_isreg(vp) ) {
1394 return (EPERM);
1395 }
1396 if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) {
1397 return (result);
1398 }
1399 if (!hfs_has_rsrc(cp)) {
1400 hfs_unlock(cp);
1401 return (ENOATTR);
1402 }
1403 result = hfs_vgetrsrc(hfsmp, vp, &rvp);
1404 hfs_unlock(cp);
1405 if (result) {
1406 return (result);
1407 }
1408
1409 hfs_lock_truncate(VTOC(rvp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT);
1410
1411 // Tell UBC now before we take the cnode lock and start the transaction
1412 hfs_ubc_setsize(rvp, 0, false);
1413
1414 if ((result = hfs_lock(VTOC(rvp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) {
1415 hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT);
1416 vnode_put(rvp);
1417 return (result);
1418 }
1419
1420 /* Start a transaction for encapsulating changes in
1421 * hfs_truncate() and hfs_update()
1422 */
1423 if ((result = hfs_start_transaction(hfsmp))) {
1424 hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT);
1425 hfs_unlock(cp);
1426 vnode_put(rvp);
1427 return (result);
1428 }
1429
1430 result = hfs_truncate(rvp, (off_t)0, IO_NDELAY, 0, ap->a_context);
1431 if (result == 0) {
1432 cp->c_touch_chgtime = TRUE;
1433 cp->c_flag |= C_MODIFIED;
1434 result = hfs_update(vp, 0);
1435 }
1436
1437 hfs_end_transaction(hfsmp);
1438 hfs_unlock_truncate(VTOC(rvp), HFS_LOCK_DEFAULT);
1439 hfs_unlock(VTOC(rvp));
1440
1441 vnode_put(rvp);
1442 return (result);
1443 }
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;
1451
1452 if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) {
1453 return (result);
1454 }
1455
1456 /* Use the local copy to store our temporary changes. */
1457 bcopy(cp->c_finderinfo, finderinfo, sizeof(finderinfo));
1458
1459
1460 /* Zero out the date added field in the local copy */
1461 hfs_zero_hidden_fields (cp, finderinfo);
1462
1463 /* Don't expose a symlink's private type/creator. */
1464 if (vnode_islnk(vp)) {
1465 struct FndrFileInfo *fip;
1466
1467 fip = (struct FndrFileInfo *)&finderinfo;
1468 fip->fdType = 0;
1469 fip->fdCreator = 0;
1470 }
1471
1472 /* Do the byte compare against the local copy */
1473 if (bcmp(finderinfo, emptyfinfo, sizeof(emptyfinfo)) == 0) {
1474 hfs_unlock(cp);
1475 return (ENOATTR);
1476 }
1477
1478 /*
1479 * If there was other content, zero out everything except
1480 * type/creator and date added. First, save the date added.
1481 */
1482 finfo = cp->c_finderinfo;
1483 finfo = finfo + 16;
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;
1494 }
1495
1496 if (vnode_islnk(vp)) {
1497 /* Ignore type/creator */
1498 finderinfo_start = &cp->c_finderinfo[8];
1499 finderinfo_size = sizeof(cp->c_finderinfo) - 8;
1500 } else {
1501 finderinfo_start = &cp->c_finderinfo[0];
1502 finderinfo_size = sizeof(cp->c_finderinfo);
1503 }
1504 bzero(finderinfo_start, finderinfo_size);
1505
1506
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;
1518 }
1519
1520 /* Updating finderInfo updates change time and modified time */
1521 cp->c_touch_chgtime = TRUE;
1522 cp->c_flag |= C_MODIFIED;
1523 hfs_update(vp, 0);
1524
1525 hfs_unlock(cp);
1526
1527 return (0);
1528 }
1529 #if CONFIG_HFS_STD
1530 /*
1531 * Standard HFS only supports native FinderInfo and Resource Forks.
1532 */
1533 if (hfsmp->hfs_flags & HFS_STANDARD) {
1534 return (EPERM);
1535 }
1536 #endif
1537 if (hfsmp->hfs_attribute_vp == NULL) {
1538 return (ENOATTR);
1539 }
1540
1541 iterator = hfs_mallocz(sizeof(*iterator));
1542
1543 if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) {
1544 goto exit_nolock;
1545 }
1546
1547 result = hfs_buildattrkey(cp->c_fileid, ap->a_name, (HFSPlusAttrKey *)&iterator->key);
1548 if (result) {
1549 goto exit;
1550 }
1551
1552 if (hfs_start_transaction(hfsmp) != 0) {
1553 result = EINVAL;
1554 goto exit;
1555 }
1556 lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE | SFL_BITMAP, HFS_EXCLUSIVE_LOCK);
1557
1558 result = remove_attribute_records(hfsmp, iterator);
1559
1560 hfs_systemfile_unlock(hfsmp, lockflags);
1561
1562 if (result == 0) {
1563 cp->c_touch_chgtime = TRUE;
1564
1565 lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK);
1566
1567 /* If no more attributes exist, clear attribute bit */
1568 result = file_attribute_exist(hfsmp, cp->c_fileid);
1569 if (result == 0) {
1570 cp->c_attr.ca_recflags &= ~kHFSHasAttributesMask;
1571 cp->c_flag |= C_MODIFIED;
1572 }
1573 if (result == EEXIST) {
1574 result = 0;
1575 }
1576
1577 hfs_systemfile_unlock(hfsmp, lockflags);
1578
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;
1583 }
1584 (void) hfs_update(vp, 0);
1585 }
1586
1587 hfs_end_transaction(hfsmp);
1588 exit:
1589 hfs_unlock(cp);
1590 exit_nolock:
1591 hfs_free(iterator, sizeof(*iterator));
1592 return MacToVFSError(result);
1593 }
1594
1595 /*
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.
1601 *
1602 * Assumes that the cnode backing the fileid specified is LOCKED.
1603 */
1604
1605 int
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
1608 int ret = 0;
1609 int started_txn = 0;
1610 int lockflags;
1611
1612 memset (&iter, 0, sizeof(iter));
1613
1614 //position the B-Tree iter key before grabbing locks and starting a txn
1615 ret = hfs_buildattrkey (fileid, xattr_name, (HFSPlusAttrKey*)&iter.key);
1616 if (ret) {
1617 goto xattr_out;
1618 }
1619
1620 //note: this is likely a nested transaction since there is a global transaction cover
1621 if (hfs_start_transaction (hfsmp) != 0) {
1622 ret = EINVAL;
1623 goto xattr_out;
1624 }
1625 started_txn = 1;
1626
1627
1628 lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE | SFL_BITMAP, HFS_EXCLUSIVE_LOCK);
1629
1630 //actually remove the EA from the tree
1631 ret = remove_attribute_records(hfsmp, &iter);
1632
1633 hfs_systemfile_unlock(hfsmp, lockflags);
1634
1635 /*
1636 * NOTE: Responsibility of the caller to remove the "has XATTRs" bit in the catalog record
1637 * if this was the last EA.
1638 */
1639
1640
1641 xattr_out:
1642 if (started_txn) {
1643 hfs_end_transaction(hfsmp);
1644 }
1645
1646 return MacToVFSError(ret);
1647
1648 }
1649
1650
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.
1654 *
1655 * Note - you must acquire a shared lock on the attribute btree before
1656 * calling this function.
1657 *
1658 * Output:
1659 * EEXIST - If attribute record was found
1660 * 0 - Attribute was not found
1661 * (other) - Other error (such as EIO)
1662 */
1663 int
1664 file_attribute_exist(struct hfsmount *hfsmp, uint32_t fileID)
1665 {
1666 HFSPlusAttrKey *key;
1667 struct BTreeIterator * iterator = NULL;
1668 struct filefork *btfile;
1669 int result = 0;
1670
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) {
1674 return false;
1675 }
1676
1677 iterator = hfs_mallocz(sizeof(*iterator));
1678
1679 key = (HFSPlusAttrKey *)&iterator->key;
1680
1681 result = hfs_buildattrkey(fileID, NULL, key);
1682 if (result) {
1683 goto out;
1684 }
1685
1686 btfile = VTOF(hfsmp->hfs_attribute_vp);
1687 result = BTSearchRecord(btfile, iterator, NULL, NULL, NULL);
1688 if (result && (result != btNotFound)) {
1689 goto out;
1690 }
1691
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
1695 */
1696 if ((result && (result == btNotFound)) || (key->fileID != fileID)) {
1697 result = 0;
1698 } else {
1699 result = EEXIST;
1700 }
1701
1702 out:
1703 hfs_free(iterator, sizeof(*iterator));
1704 return result;
1705 }
1706
1707
1708 /*
1709 * Remove all the records for a given attribute.
1710 *
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.
1716 */
1717 int
1718 remove_attribute_records(struct hfsmount *hfsmp, BTreeIterator * iterator)
1719 {
1720 struct filefork *btfile;
1721 FSBufferDescriptor btdata;
1722 HFSPlusAttrRecord attrdata; /* 90 bytes */
1723 u_int16_t datasize;
1724 int result;
1725
1726 btfile = VTOF(hfsmp->hfs_attribute_vp);
1727
1728 btdata.bufferAddress = &attrdata;
1729 btdata.itemSize = sizeof(attrdata);
1730 btdata.itemCount = 1;
1731 result = BTSearchRecord(btfile, iterator, &btdata, &datasize, NULL);
1732 if (result) {
1733 goto exit; /* no records. */
1734 }
1735 /*
1736 * Free the blocks from extent based attributes.
1737 *
1738 * Note that the block references (btree records) are removed
1739 * before releasing the blocks in the allocation bitmap.
1740 */
1741 if (attrdata.recordType == kHFSPlusAttrForkData) {
1742 int totalblks;
1743 int extentblks;
1744 u_int32_t *keystartblk;
1745
1746 if (datasize < sizeof(HFSPlusAttrForkData)) {
1747 printf("hfs: remove_attribute_records: bad record size %d (expecting %lu)\n", datasize, sizeof(HFSPlusAttrForkData));
1748 }
1749 totalblks = attrdata.forkData.theFork.totalBlocks;
1750
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);
1757 }
1758 totalblks -= extentblks;
1759 keystartblk = &((HFSPlusAttrKey *)&iterator->key)->startBlock;
1760
1761 /* Process any overflow extents. */
1762 while (totalblks) {
1763 *keystartblk += (u_int32_t)extentblks;
1764
1765 result = BTSearchRecord(btfile, iterator, &btdata, &datasize, NULL);
1766 if (result ||
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);
1771 result = ENOATTR;
1772 break; /* break from while */
1773 }
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);
1780 }
1781 totalblks -= extentblks;
1782 }
1783 } else {
1784 result = BTDeleteRecord(btfile, iterator);
1785 }
1786 (void) BTFlushPath(btfile);
1787 exit:
1788 return (result == btNotFound ? ENOATTR : MacToVFSError(result));
1789 }
1790
1791
1792 /*
1793 * Retrieve the list of extended attribute names.
1794 */
1795 int
1796 hfs_vnop_listxattr(struct vnop_listxattr_args *ap)
1797 /*
1798 struct vnop_listxattr_args {
1799 struct vnodeop_desc *a_desc;
1800 vnode_t a_vp;
1801 uio_t a_uio;
1802 size_t *a_size;
1803 int a_options;
1804 vfs_context_t a_context;
1805 */
1806 {
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;
1816 int lockflags;
1817 int result;
1818 u_int8_t finderinfo[32];
1819
1820
1821 if (VNODE_IS_RSRC(vp)) {
1822 return (EPERM);
1823 }
1824
1825 #if HFS_COMPRESSION
1826 int compressed = hfs_file_is_compressed(cp, 1); /* 1 == don't take the cnode lock */
1827 #endif /* HFS_COMPRESSION */
1828
1829 hfsmp = VTOHFS(vp);
1830 *ap->a_size = 0;
1831
1832 /*
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.
1836 */
1837 hfs_lock_truncate(cp, HFS_SHARED_LOCK, HFS_LOCK_DEFAULT);
1838
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);
1842 return (result);
1843 }
1844
1845 /*
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
1848 * for symlinks.
1849 */
1850 bcopy(cp->c_finderinfo, finderinfo, sizeof(finderinfo));
1851 hfs_zero_hidden_fields (cp, finderinfo);
1852
1853 /* Don't expose a symlink's private type/creator. */
1854 if (vnode_islnk(vp)) {
1855 struct FndrFileInfo *fip;
1856
1857 fip = (struct FndrFileInfo *)&finderinfo;
1858 fip->fdType = 0;
1859 fip->fdCreator = 0;
1860 }
1861
1862
1863 /* If Finder Info is non-empty then export it's name. */
1864 if (bcmp(finderinfo, emptyfinfo, sizeof(emptyfinfo)) != 0) {
1865 if (uio == NULL) {
1866 *ap->a_size += sizeof(XATTR_FINDERINFO_NAME);
1867 } else if ((user_size_t)uio_resid(uio) < sizeof(XATTR_FINDERINFO_NAME)) {
1868 result = ERANGE;
1869 goto exit;
1870 } else {
1871 result = uiomove(XATTR_FINDERINFO_NAME,
1872 sizeof(XATTR_FINDERINFO_NAME), uio);
1873 if (result)
1874 goto exit;
1875 }
1876 }
1877 /* If Resource Fork is non-empty then export it's name. */
1878 if (S_ISREG(cp->c_mode) && hfs_has_rsrc(cp)) {
1879 #if HFS_COMPRESSION
1880 if ((ap->a_options & XATTR_SHOWCOMPRESSION) ||
1881 !compressed ||
1882 !decmpfs_hides_rsrc(ap->a_context, VTOCMP(vp))
1883 )
1884 #endif /* HFS_COMPRESSION */
1885 {
1886 if (uio == NULL) {
1887 *ap->a_size += sizeof(XATTR_RESOURCEFORK_NAME);
1888 } else if ((user_size_t)uio_resid(uio) < sizeof(XATTR_RESOURCEFORK_NAME)) {
1889 result = ERANGE;
1890 goto exit;
1891 } else {
1892 result = uiomove(XATTR_RESOURCEFORK_NAME,
1893 sizeof(XATTR_RESOURCEFORK_NAME), uio);
1894 if (result)
1895 goto exit;
1896 }
1897 }
1898 }
1899 #if CONFIG_HFS_STD
1900 /*
1901 * Standard HFS only supports native FinderInfo and Resource Forks.
1902 * Return at this point.
1903 */
1904 if (hfsmp->hfs_flags & HFS_STANDARD) {
1905 result = 0;
1906 goto exit;
1907 }
1908 #endif
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) {
1912 result = 0;
1913 goto exit;
1914 }
1915 btfile = VTOF(hfsmp->hfs_attribute_vp);
1916
1917 iterator = hfs_mallocz(sizeof(*iterator));
1918
1919 result = hfs_buildattrkey(cp->c_fileid, NULL, (HFSPlusAttrKey *)&iterator->key);
1920 if (result)
1921 goto exit;
1922
1923 /*
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.
1926 */
1927 if (uio && uio_isuserspace(uio)) {
1928 user_start = uio_curriovbase(uio);
1929 user_len = uio_curriovlen(uio);
1930
1931 if ((result = vslock(user_start, user_len)) != 0) {
1932 user_start = 0;
1933 goto exit;
1934 }
1935 }
1936 lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK);
1937
1938 result = BTSearchRecord(btfile, iterator, NULL, NULL, NULL);
1939 if (result && result != btNotFound) {
1940 hfs_systemfile_unlock(hfsmp, lockflags);
1941 goto exit;
1942 }
1943
1944 state.fileID = cp->c_fileid;
1945 state.result = 0;
1946 state.uio = uio;
1947 state.size = 0;
1948 #if HFS_COMPRESSION
1949 state.showcompressed = !compressed || ap->a_options & XATTR_SHOWCOMPRESSION;
1950 state.ctx = ap->a_context;
1951 state.vp = vp;
1952 #endif /* HFS_COMPRESSION */
1953
1954 /*
1955 * Process entries starting just after iterator->key.
1956 */
1957 result = BTIterateRecords(btfile, kBTreeNextRecord, iterator,
1958 (IterateCallBackProcPtr)listattr_callback, &state);
1959 hfs_systemfile_unlock(hfsmp, lockflags);
1960 if (uio == NULL) {
1961 *ap->a_size += state.size;
1962 }
1963
1964 if (state.result || result == btNotFound)
1965 result = state.result;
1966
1967 exit:
1968 if (user_start) {
1969 vsunlock(user_start, user_len, TRUE);
1970 }
1971 hfs_free(iterator, sizeof(*iterator));
1972 hfs_unlock(cp);
1973 hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT);
1974
1975 return MacToVFSError(result);
1976 }
1977
1978
1979 /*
1980 * Callback - called for each attribute record
1981 */
1982 static int
1983 listattr_callback(const HFSPlusAttrKey *key, __unused const HFSPlusAttrData *data, struct listattr_callback_state *state)
1984 {
1985 char attrname[XATTR_MAXNAMELEN + 1];
1986 ssize_t bytecount;
1987 int result;
1988
1989 if (state->fileID != key->fileID) {
1990 state->result = 0;
1991 return (0); /* stop */
1992 }
1993 /*
1994 * Skip over non-primary keys
1995 */
1996 if (key->startBlock != 0) {
1997 return (1); /* continue */
1998 }
1999
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);
2003 if (result) {
2004 state->result = result;
2005 return (0); /* stop */
2006 }
2007 bytecount++; /* account for null termination char */
2008
2009 if (xattr_protected(attrname))
2010 return (1); /* continue */
2011
2012 #if HFS_COMPRESSION
2013 if (!state->showcompressed && decmpfs_hides_xattr(state->ctx, VTOCMP(state->vp), attrname) )
2014 return 1; /* continue */
2015 #endif /* HFS_COMPRESSION */
2016
2017 if (state->uio == NULL) {
2018 state->size += bytecount;
2019 } else {
2020 if (bytecount > uio_resid(state->uio)) {
2021 state->result = ERANGE;
2022 return (0); /* stop */
2023 }
2024 result = uiomove((caddr_t) attrname, bytecount, state->uio);
2025 if (result) {
2026 state->result = result;
2027 return (0); /* stop */
2028 }
2029 }
2030 return (1); /* continue */
2031 }
2032
2033 /*
2034 * Remove all the attributes from a cnode.
2035 *
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).
2039 *
2040 * This function takes the necessary locks on the attribute
2041 * b-tree file and the allocation (bitmap) file.
2042 *
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.
2049 */
2050 int
2051 hfs_removeallattr(struct hfsmount *hfsmp, u_int32_t fileid,
2052 bool *open_transaction)
2053 {
2054 BTreeIterator *iterator = NULL;
2055 HFSPlusAttrKey *key;
2056 struct filefork *btfile;
2057 int result, lockflags = 0;
2058
2059 *open_transaction = false;
2060
2061 if (hfsmp->hfs_attribute_vp == NULL)
2062 return 0;
2063
2064 btfile = VTOF(hfsmp->hfs_attribute_vp);
2065
2066 iterator = hfs_mallocz(sizeof(BTreeIterator));
2067
2068 key = (HFSPlusAttrKey *)&iterator->key;
2069
2070 /* Loop until there are no more attributes for this file id */
2071 do {
2072 if (!*open_transaction)
2073 lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK);
2074
2075 (void) hfs_buildattrkey(fileid, NULL, key);
2076 result = BTIterateRecord(btfile, kBTreeNextRecord, iterator, NULL, NULL);
2077 if (result || key->fileID != fileid)
2078 goto exit;
2079
2080 hfs_systemfile_unlock(hfsmp, lockflags);
2081 lockflags = 0;
2082
2083 if (*open_transaction) {
2084 hfs_end_transaction(hfsmp);
2085 *open_transaction = false;
2086 }
2087
2088 if (hfs_start_transaction(hfsmp) != 0) {
2089 result = EINVAL;
2090 goto exit;
2091 }
2092
2093 *open_transaction = true;
2094
2095 lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE | SFL_BITMAP, HFS_EXCLUSIVE_LOCK);
2096
2097 result = remove_attribute_records(hfsmp, iterator);
2098
2099 #if HFS_XATTR_VERBOSE
2100 if (result) {
2101 printf("hfs_removeallattr: unexpected err %d\n", result);
2102 }
2103 #endif
2104 } while (!result);
2105
2106 exit:
2107 hfs_free(iterator, sizeof(*iterator));
2108
2109 if (lockflags)
2110 hfs_systemfile_unlock(hfsmp, lockflags);
2111
2112 result = result == btNotFound ? 0 : MacToVFSError(result);
2113
2114 if (result && *open_transaction) {
2115 hfs_end_transaction(hfsmp);
2116 *open_transaction = false;
2117 }
2118
2119 return result;
2120 }
2121
2122 void
2123 hfs_xattr_init(struct hfsmount * hfsmp)
2124 {
2125 #if CONFIG_HFS_STD
2126 if (ISSET(hfsmp->hfs_flags, HFS_STANDARD))
2127 return;
2128 #endif
2129
2130 /*
2131 * If there isn't an attributes b-tree then create one.
2132 */
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));
2137 }
2138 if (hfsmp->hfs_attribute_vp)
2139 hfsmp->hfs_max_inline_attrsize = getmaxinlineattrsize(hfsmp->hfs_attribute_vp);
2140 }
2141
2142 /*
2143 * Enable/Disable volume attributes stored as EA for root file system.
2144 * Supported attributes are -
2145 * 1. Extent-based Extended Attributes
2146 */
2147 int
2148 hfs_set_volxattr(struct hfsmount *hfsmp, unsigned int xattrtype, int state)
2149 {
2150 struct BTreeIterator * iterator = NULL;
2151 struct filefork *btfile;
2152 int lockflags;
2153 int result;
2154
2155 #if CONFIG_HFS_STD
2156 if (hfsmp->hfs_flags & HFS_STANDARD) {
2157 return (ENOTSUP);
2158 }
2159 #endif
2160 if (xattrtype != HFSIOC_SET_XATTREXTENTS_STATE) {
2161 return EINVAL;
2162 }
2163
2164 /*
2165 * If there isn't an attributes b-tree then create one.
2166 */
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));
2170 if (result) {
2171 return (result);
2172 }
2173 }
2174
2175 iterator = hfs_mallocz(sizeof(*iterator));
2176
2177 /*
2178 * Build a b-tree key.
2179 * We use the root's parent id (1) to hold this volume attribute.
2180 */
2181 (void) hfs_buildattrkey(kHFSRootParentID, XATTR_XATTREXTENTS_NAME,
2182 (HFSPlusAttrKey *)&iterator->key);
2183
2184 /* Start a transaction for our changes. */
2185 if (hfs_start_transaction(hfsmp) != 0) {
2186 result = EINVAL;
2187 goto exit;
2188 }
2189 btfile = VTOF(hfsmp->hfs_attribute_vp);
2190
2191 lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_EXCLUSIVE_LOCK);
2192
2193 if (state == 0) {
2194 /* Remove the attribute. */
2195 result = BTDeleteRecord(btfile, iterator);
2196 if (result == btNotFound)
2197 result = 0;
2198 } else {
2199 FSBufferDescriptor btdata;
2200 HFSPlusAttrData attrdata;
2201 u_int16_t datasize;
2202
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;
2213
2214 /* Insert the attribute. */
2215 result = BTInsertRecord(btfile, iterator, &btdata, datasize);
2216 if (result == btExists)
2217 result = 0;
2218 }
2219 (void) BTFlushPath(btfile);
2220
2221 hfs_systemfile_unlock(hfsmp, lockflags);
2222
2223 /* Finish the transaction of our changes. */
2224 hfs_end_transaction(hfsmp);
2225
2226 /* Update the state in the mount point */
2227 hfs_lock_mount (hfsmp);
2228 if (state == 0) {
2229 hfsmp->hfs_flags &= ~HFS_XATTR_EXTENTS;
2230 } else {
2231 hfsmp->hfs_flags |= HFS_XATTR_EXTENTS;
2232 }
2233 hfs_unlock_mount (hfsmp);
2234
2235 exit:
2236 hfs_free(iterator, sizeof(*iterator));
2237 return MacToVFSError(result);
2238 }
2239
2240
2241 /*
2242 * hfs_attrkeycompare - compare two attribute b-tree keys.
2243 *
2244 * The name portion of the key is compared using a 16-bit binary comparison.
2245 * This is called from the b-tree code.
2246 */
2247 int
2248 hfs_attrkeycompare(HFSPlusAttrKey *searchKey, HFSPlusAttrKey *trialKey)
2249 {
2250 u_int32_t searchFileID, trialFileID;
2251 int result;
2252
2253 searchFileID = searchKey->fileID;
2254 trialFileID = trialKey->fileID;
2255 result = 0;
2256
2257 if (searchFileID > trialFileID) {
2258 ++result;
2259 } else if (searchFileID < trialFileID) {
2260 --result;
2261 } else {
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;
2266 u_int16_t c1, c2;
2267 int length;
2268
2269 if (length1 < length2) {
2270 length = length1;
2271 --result;
2272 } else if (length1 > length2) {
2273 length = length2;
2274 ++result;
2275 } else {
2276 length = length1;
2277 }
2278
2279 while (length--) {
2280 c1 = *(str1++);
2281 c2 = *(str2++);
2282
2283 if (c1 > c2) {
2284 result = 1;
2285 break;
2286 }
2287 if (c1 < c2) {
2288 result = -1;
2289 break;
2290 }
2291 }
2292 if (result)
2293 return (result);
2294 /*
2295 * Names are equal; compare startBlock
2296 */
2297 if (searchKey->startBlock == trialKey->startBlock) {
2298 return (0);
2299 } else {
2300 return (searchKey->startBlock < trialKey->startBlock ? -1 : 1);
2301 }
2302 }
2303
2304 return result;
2305 }
2306
2307
2308 /*
2309 * hfs_buildattrkey - build an Attribute b-tree key
2310 */
2311 int
2312 hfs_buildattrkey(u_int32_t fileID, const char *attrname, HFSPlusAttrKey *key)
2313 {
2314 int result = 0;
2315 size_t unicodeBytes = 0;
2316
2317 if (attrname != NULL) {
2318 /*
2319 * Convert filename from UTF-8 into Unicode
2320 */
2321 result = utf8_decodestr((const u_int8_t *)attrname, strlen(attrname), key->attrName,
2322 &unicodeBytes, sizeof(key->attrName), 0, 0);
2323 if (result) {
2324 if (result != ENAMETOOLONG)
2325 result = EINVAL; /* name has invalid characters */
2326 return (result);
2327 }
2328 key->attrNameLen = unicodeBytes / sizeof(UniChar);
2329 key->keyLength = kHFSPlusAttrKeyMinimumLength + unicodeBytes;
2330 } else {
2331 key->attrNameLen = 0;
2332 key->keyLength = kHFSPlusAttrKeyMinimumLength;
2333 }
2334 key->pad = 0;
2335 key->fileID = fileID;
2336 key->startBlock = 0;
2337
2338 return (0);
2339 }
2340
2341 /*
2342 * getnodecount - calculate starting node count for attributes b-tree.
2343 */
2344 static int
2345 getnodecount(struct hfsmount *hfsmp, size_t nodesize)
2346 {
2347 u_int64_t freebytes;
2348 u_int64_t calcbytes;
2349
2350 /*
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.
2353 *
2354 * Use no more than 10 % of the remaining free space.
2355 */
2356 freebytes = (u_int64_t)hfs_freeblks(hfsmp, 0) * (u_int64_t)hfsmp->blockSize;
2357
2358 calcbytes = MIN(hfsmp->hfs_catalog_cp->c_datafork->ff_size / 5, 20 * 1024 * 1024);
2359
2360 calcbytes = MAX(calcbytes, hfsmp->hfs_catalog_cp->c_datafork->ff_clumpsize);
2361
2362 calcbytes = MIN(calcbytes, freebytes / 10);
2363
2364 return (MAX(2, (int)(calcbytes / nodesize)));
2365 }
2366
2367
2368 /*
2369 * getmaxinlineattrsize - calculate maximum inline attribute size.
2370 *
2371 * This yields 3,802 bytes for an 8K node size.
2372 */
2373 static size_t
2374 getmaxinlineattrsize(struct vnode * attrvp)
2375 {
2376 struct BTreeInfoRec btinfo;
2377 size_t nodesize = ATTRIBUTE_FILE_NODE_SIZE;
2378 size_t maxsize;
2379
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));
2385 }
2386 maxsize = nodesize;
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 */
2393
2394 return (maxsize);
2395 }
2396
2397 /*
2398 * Initialize vnode for attribute data I/O.
2399 *
2400 * On success,
2401 * - returns zero
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
2405 * during unmount
2406 * - the attrdata cnode is not locked
2407 *
2408 * On failure,
2409 * - returns non-zero value
2410 * - the caller does not have to worry about any locks or references
2411 */
2412 int init_attrdata_vnode(struct hfsmount *hfsmp)
2413 {
2414 vnode_t vp;
2415 int result = 0;
2416 struct cat_desc cat_desc;
2417 struct cat_attr cat_attr;
2418 struct cat_fork cat_fork;
2419 int newvnode_flags = 0;
2420
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;
2428
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;
2434
2435 /*
2436 * The attribute data file is a virtual file that spans the
2437 * entire file system space.
2438 *
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.
2443 *
2444 * This approach allows the attribute data to be cached without
2445 * incurring the high cost of using a separate vnode per attribute.
2446 *
2447 * Since we need to acquire the attribute b-tree file lock anyways,
2448 * the virtual file doesn't introduce any additional serialization.
2449 */
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;
2455
2456 result = hfs_getnewvnode(hfsmp, NULL, NULL, &cat_desc, 0, &cat_attr,
2457 &cat_fork, &vp, &newvnode_flags);
2458 if (result == 0) {
2459 hfsmp->hfs_attrdata_vp = vp;
2460 #if NEW_XATTR
2461 vnode_setnoreadahead(hfsmp->hfs_attrdata_vp);
2462 #endif
2463 hfs_unlock(VTOC(vp));
2464 }
2465 return (result);
2466 }
2467
2468 /* The following code (inside NEW_XATTR) was ported from apfs. */
2469 #if NEW_XATTR
2470 /*
2471 * This is the same as fext2cluster_check(), but overflow is asserted against.
2472 *
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
2475 * the case.
2476 */
2477 static uint64_t
2478 fext2cluster(const HFSPlusExtentDescriptor *fext, uint32_t fs_bsize)
2479 {
2480 uint64_t off;
2481 const bool ok = fext2cluster_check(fext, fs_bsize, &off);
2482 xattr_assert(ok);
2483 return off;
2484 }
2485
2486 /*
2487 * Translate `fext' to a cluster layer virtual offset, set via `out', that is
2488 * suitable for IO to the single xattr vnode.
2489 *
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().
2497 *
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
2501 *
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.
2509 */
2510 static bool
2511 fext2cluster_check(const HFSPlusExtentDescriptor *fext, uint32_t fs_bsize,
2512 uint64_t *out)
2513 {
2514 const uint64_t pbn = fext->startBlock;
2515
2516 // xattrs has invalid start block
2517 if (!pbn) {
2518 return false;
2519 }
2520
2521 // scale pbn
2522 uint64_t off;
2523 if (__builtin_mul_overflow(pbn, xattr_cluster_scale(fs_bsize), &off)) {
2524 return false;
2525 }
2526
2527 *out = off;
2528
2529 // the whole fext should be in range
2530 if (__builtin_add_overflow(off, fext->blockCount * fs_bsize, &off)) {
2531 return false;
2532 }
2533
2534 // don't exceed signed 64bit precision
2535 if (off > INT64_MAX) {
2536 return false;
2537 }
2538
2539 return true;
2540 }
2541
2542 /*
2543 * Return the scale factor for translating xattr physical block numbers into
2544 * a logical offset into the single xattr vnode's ubc.
2545 *
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.
2552 *
2553 * A table of all possible configurations:
2554 * pagesize, fs blocksize, scale
2555 * 4k, 4k, 4k
2556 * 4k, 8k, 8k
2557 * ...
2558 * 4k, 64k, 64k
2559 * 16k, 4k, 16k
2560 * 16k, 8k, 16k
2561 * 16k, 16k, 16k
2562 * 16k, 32k, 32k
2563 * 16k, 64k, 64k
2564 *
2565 * This may be expressed as
2566 * scale = max(pagesize, fs blocksize)
2567 */
2568 static uint32_t
2569 xattr_cluster_scale(uint32_t fs_bsize)
2570 {
2571 return MAX(PAGE_SIZE, fs_bsize);
2572 }
2573
2574 /*
2575 * See off_in_range().
2576 */
2577 static bool
2578 off_in_range2(uint64_t off, uint64_t start, uint64_t len)
2579 {
2580 return (start <= off) && (off < (start + len));
2581 }
2582
2583 /*
2584 * Return true if `off' returned from fext2cluster() was produced from
2585 * `fext'.
2586 */
2587 static bool
2588 cluster_off_in_fext(uint64_t off, const HFSPlusExtentDescriptor *fext,
2589 uint32_t fs_bsize)
2590 {
2591 return off_in_range2(off, fext2cluster(fext, fs_bsize), fext->blockCount * fs_bsize);
2592 }
2593
2594 /*
2595 * Allocate space to save a file extent reference for xattr IO.
2596 *
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()).
2600 *
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.
2605 *
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().
2609 *
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().
2612 *
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'.
2618 */
2619 static const HFSPlusExtentDescriptor **
2620 xattr_fext_alloc(xattr_io_info_t *info)
2621 {
2622 const HFSPlusExtentDescriptor **ret;
2623 size_t i;
2624
2625 // search for the first free bit
2626 lck_spin_lock(&info->lock);
2627 if (!xbm_find_free(info, &i)) {
2628 // no free fexts
2629 ret = NULL;
2630 goto fail;
2631 }
2632
2633 // mark that position as allocated
2634 xbm_set_used(info, i);
2635 ret = &info->xattr_fexts[i];
2636 xattr_assert(!*ret);
2637
2638 fail:
2639 lck_spin_unlock(&info->lock);
2640 return ret;
2641 }
2642
2643 /*
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.
2650 */
2651 static void
2652 xattr_fext_free(xattr_io_info_t *info, const HFSPlusExtentDescriptor **xfext)
2653 {
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);
2659 }
2660
2661 /*
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().
2665 *
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().
2671 *
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).
2675 */
2676 static void
2677 xattr_fext_set(xattr_io_info_t *info, const HFSPlusExtentDescriptor **xfext,
2678 const HFSPlusExtentDescriptor *fext)
2679 {
2680 xattr_assert(xbm_valid_index(info, xattr_fext_index(info, xfext)));
2681 lck_spin_lock(&info->lock);
2682 *xfext = fext;
2683 lck_spin_unlock(&info->lock);
2684 }
2685
2686 /*
2687 * Given a cluster layer virtual offset, attempt to look up a file extent set
2688 * via a previous call to xattr_fext_set().
2689 *
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.
2693 *
2694 * off_out will contain the "unvirtualized" offset
2695 */
2696 bool
2697 hfs_xattr_fext_find(xattr_io_info_t *info, uint32_t fs_bsize, uint64_t off,
2698 HFSPlusExtentDescriptor *out, uint64_t *off_out)
2699 {
2700 bool found = false;
2701 lck_spin_lock(&info->lock);
2702
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)) {
2708 continue;
2709 }
2710 // `off' intersects; return `fext'
2711 *out = *fext;
2712 found = true;
2713 break;
2714 }
2715
2716 lck_spin_unlock(&info->lock);
2717
2718 if (found) {
2719 *off_out = ((uint64_t)out->startBlock * fs_bsize) + off - fext2cluster(out, fs_bsize);
2720 }
2721
2722 return found;
2723 }
2724
2725 /*
2726 * Given an allocated xattr fext, clear its reference to any `fext' passed to a
2727 * previous call to xattr_fext_set().
2728 *
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.
2731 *
2732 * Unlike xattr_fext_free(), `xfext' will remain allocated and it may passed to
2733 * xattr_fext_set() again.
2734 */
2735 static void
2736 xattr_fext_clear(xattr_io_info_t *info, const HFSPlusExtentDescriptor **xfext)
2737 {
2738 xattr_assert(xbm_valid_index(info, xattr_fext_index(info, xfext)));
2739 lck_spin_lock(&info->lock);
2740 *xfext = NULL;
2741 lck_spin_unlock(&info->lock);
2742 }
2743
2744 /*
2745 * For an xattr file extent, `xfext', returned from a previous call to
2746 * xattr_fext_alloc(), return its index within `info->xattr_fexts'.
2747 */
2748 static size_t
2749 xattr_fext_index(const xattr_io_info_t *info, const HFSPlusExtentDescriptor **xfext)
2750 {
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);
2754 }
2755
2756 static void
2757 bitmap_set_range(uint64_t *bm, int64_t index, int64_t count)
2758 {
2759 int dstshift0, dstshift1;
2760 uint64_t dstmask0, dstmask1;
2761 int64_t bmi;
2762
2763 dstshift0 = index % 64;
2764 dstshift1 = 64 - (index % 64);
2765 dstmask0 = ~0ULL << (index % 64);
2766 dstmask1 = (dstshift1 == 64) ? 0ULL : (~0ULL >> (64 - (index % 64)));
2767
2768 bmi = index / 64;
2769 while (count >= 64) {
2770 bm[bmi] = (bm[bmi] & ~dstmask0) | ((~0ULL << dstshift0) & dstmask0);
2771 if (dstmask1)
2772 bm[bmi + 1] = (bm[bmi + 1] & ~dstmask1) | ((~0ULL >> dstshift1) & dstmask1);
2773 bmi++;
2774 count -= 64;
2775 }
2776 if (count) {
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);
2783 }
2784 }
2785
2786 static void
2787 bitmap_clear_range(uint64_t *bm, int64_t index, int64_t count)
2788 {
2789 int dstshift0, dstshift1;
2790 uint64_t dstmask0, dstmask1;
2791 int64_t bmi;
2792
2793 dstshift0 = index % 64;
2794 dstshift1 = 64 - (index % 64);
2795 dstmask0 = ~0ULL << (index % 64);
2796 dstmask1 = (dstshift1 == 64) ? 0ULL : (~0ULL >> (64 - (index % 64)));
2797
2798 bmi = index / 64;
2799 while (count >= 64) {
2800 bm[bmi] = (bm[bmi] & ~dstmask0) | ((0ULL << dstshift0) & dstmask0);
2801 if (dstmask1)
2802 bm[bmi + 1] = (bm[bmi + 1] & ~dstmask1) | ((0ULL >> dstshift1) & dstmask1);
2803 bmi++;
2804 count -= 64;
2805 }
2806 if (count) {
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);
2813 }
2814 }
2815
2816 static int
2817 ctzll(uint64_t val)
2818 {
2819 return (val == 0) ? 64 : __builtin_ctzll(val);
2820 }
2821
2822 // search forwards through range and return index of first "bit" (0 or 1) encountered
2823 static int
2824 bitmap_range_find_first(int bit, const uint64_t *bm, int64_t index, int64_t count, int64_t *clear_index)
2825 {
2826 int64_t bmi, check_count;
2827 uint64_t val;
2828 int pos;
2829
2830 bmi = index / 64;
2831 while (count > 0) {
2832 check_count = 64 - (index % 64);
2833 if (check_count > count)
2834 check_count = count;
2835 val = bm[bmi] >> (index % 64);
2836 if (!bit)
2837 val = ~val;
2838 pos = ctzll(val);
2839 if (pos < check_count) {
2840 *clear_index = index + pos;
2841 return 1;
2842 }
2843 index += check_count;
2844 count -= check_count;
2845 bmi++;
2846 }
2847 return 0;
2848 }
2849
2850 /*
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.
2854 */
2855 static bool
2856 xbm_find_free(const xattr_io_info_t *info, size_t *out)
2857 {
2858 return bm_find(info->free_bm, 0, xbm_size(info), false, out);
2859 }
2860
2861 /*
2862 * Set the bit at index `i' within `info's underlying free xattr fext bitmap.
2863 *
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().
2867 */
2868 static void
2869 xbm_set_used(xattr_io_info_t *info, size_t i)
2870 {
2871 xattr_assert(xbm_valid_index(info, i));
2872 bitmap_set_range(info->free_bm, i, 1);
2873 }
2874
2875 /*
2876 * Clear the bit at index `i' within `info's underlying free xattr fext bitmap.
2877 * This is the opposite of xbm_set_used().
2878 */
2879 static void
2880 xbm_clear_used(xattr_io_info_t *info, size_t i)
2881 {
2882 xattr_assert(xbm_valid_index(info, i));
2883 bitmap_clear_range(info->free_bm, i, 1);
2884 }
2885
2886 /*
2887 * Return whether the given bitmap index is a valid index into `info's free
2888 * bitmap.
2889 */
2890 static bool
2891 xbm_valid_index(const xattr_io_info_t *info, int64_t i)
2892 {
2893 return bm_valid_index(i, xbm_size(info));
2894 }
2895
2896 #ifndef ARRAY_SIZE
2897 #define ARRAY_SIZE(A) (sizeof(A) / sizeof(A[0]))
2898 #endif
2899
2900 /*
2901 * Return the total number of *bits* in `info's free xattr fext bitmap.
2902 */
2903 static size_t
2904 xbm_size(const xattr_io_info_t *info)
2905 {
2906 // one bit per xattr fext
2907 return ARRAY_SIZE(info->xattr_fexts);
2908 }
2909
2910 /*
2911 * Factory for an iterator over all the set (true value) bits in `bitmap'.
2912 * `len' is the length in *bits* of `bitmap'.
2913 *
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
2919 * set bit.
2920 *
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);
2927 * }
2928 *
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
2933 * xbm_iter_next().
2934 */
2935 static xbm_iter_t
2936 xbm_make_iter(const uint64_t *bitmap, size_t len)
2937 {
2938 xattr_assert(len);
2939 return (xbm_iter_t) {
2940 .bitmap = bitmap,
2941 .i = SIZE_MAX, // This will overflow-to-zero on the first call to xbm_iter_next()
2942 .len = len
2943 };
2944 }
2945
2946 /*
2947 * Advance `iter' to internally reference the next set bit within the bitmap.
2948 * If there are no more set bits, this returns false.
2949 *
2950 * On success, the index of the next set bit can be retrieved by
2951 * xbm_iter_peek().
2952 *
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.
2956 */
2957 static bool
2958 xbm_iter_next(xbm_iter_t *iter)
2959 {
2960 size_t i;
2961 // find the next set bit > `i'
2962 const bool found = bm_find(iter->bitmap, iter->i + 1, iter->len, true, &i);
2963
2964 // if no bit is found, invalidate the iterator by setting i=len
2965 iter->i = found ? i : iter->len;
2966 return found;
2967 }
2968
2969 /*
2970 * Return the index a the set bit after a successful call to xbm_iter_next().
2971 */
2972 static size_t
2973 xbm_iter_peek(const xbm_iter_t *iter)
2974 {
2975 xattr_assert(iter->i < iter->len);
2976 return iter->i;
2977 }
2978
2979 /*
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'.
2983 *
2984 * This is just a fancy wrapper around bitmap_range_find_first().
2985 */
2986 static bool
2987 bm_find(const uint64_t *bm, size_t from, size_t len, bool value, size_t *out)
2988 {
2989 xattr_assert(bm_valid_index(from, len));
2990
2991 // search for `value' in [from, len)
2992 int64_t i;
2993 if (!bitmap_range_find_first(value,
2994 const_cast(uint64_t *, bm), from, len, &i)) {
2995 return false;
2996 }
2997
2998 // some bit found; check the returned index is valid
2999 xattr_assert(bm_valid_index(i, len));
3000 *out = (size_t)i;
3001 return true;
3002 }
3003
3004 /*
3005 * Return true if `i' is a valid index into a bit of `len' bits.
3006 *
3007 * The underlying bitmap_ routines operate on `int64_t' indices. This is
3008 * mainly to safely convert to `size_t'.
3009 */
3010 static bool
3011 bm_valid_index(int64_t i, size_t len)
3012 {
3013 return (i >= 0) && ((uint64_t)i < len);
3014 }
3015
3016 /*
3017 * Virtualize `uio' offset to target xattr `fext' before a call to
3018 * cluster_xattr().
3019 *
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:
3041 * - 16k vm pagesize
3042 * - 4k fs blocksize
3043 *
3044 * On disk, we have two xattrs that reside on adjacent blocks:
3045 * xattr A xattr B
3046 * [aaaa|aaaa|bbbb|bbbb]
3047 * pbn 4 5 6 7 8
3048 *
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
3051 * would result in
3052 *
3053 * xattr A xattr B
3054 * in memory:
3055 * [aaaa|aaaa|0000|0000]
3056 *
3057 * on disk:
3058 * [aaaa|aaaa|bbbb|bbbb]
3059 * pbn 4 5 6 7 8
3060 *
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
3065 *
3066 * xattr A xattr B
3067 * in memory:
3068 * [aaaa|aaaa|O!#W|JF%R]
3069 *
3070 * on disk:
3071 * [asdf|ghjk|ZXCV|BNM,] encrypted
3072 * [aaaa|aaaa|bbbb|bbbb] unencrypted
3073 * pbn 4 5 6 7 8
3074 *
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.
3078 *
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
3083 *
3084 * xattr A xattr B
3085 * in memory:
3086 * [aaaa|aaaa|0000|0000] ... [bbbb|bbbb|0000|0000]
3087 * 64k 96k
3088 *
3089 * on disk:
3090 * [asdf|ghjk|ZXCV|BNM,] encrypted
3091 * [aaaa|aaaa|bbbb|bbbb] unencrypted
3092 * pbn 4 5 6 7 8
3093 *
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
3101 * in memory:
3102 * [aaaa|0000|0000|0000][aaaa|0000|0000|0000]
3103 * 64k 80k
3104 * on disk:
3105 * [aaaa|aaaa] unencrypted
3106 * pbn 4 5
3107 *
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.
3111 * --
3112 *
3113 * If `fext' is not representable as a virtual offset (e.g. its phys_block_num
3114 * is corrupt), this function returns false.
3115 */
3116 static bool
3117 uio_set_fext(uio_t uio, const HFSPlusExtentDescriptor *fext, uint32_t fs_bsize)
3118 {
3119 uint64_t off;
3120 if (!fext2cluster_check(fext, fs_bsize, &off)) {
3121 // `fext' is out of range
3122 return false;
3123 }
3124
3125 uio_setoffset(uio, off);
3126 return true;
3127 }
3128 #endif // NEW_XATTR
3129 /*
3130 * Read an extent based attribute.
3131 */
3132 static int
3133 read_attr_data(struct hfsmount *hfsmp, uio_t uio, size_t datasize, HFSPlusExtentDescriptor *extents)
3134 {
3135 vnode_t evp = hfsmp->hfs_attrdata_vp;
3136 off_t filesize;
3137 int bufsize;
3138 int64_t iosize;
3139 int attrsize;
3140 int blksize;
3141 int i;
3142 int result = 0;
3143
3144 hfs_lock_truncate(VTOC(evp), HFS_SHARED_LOCK, HFS_LOCK_DEFAULT);
3145
3146 bufsize = (int)uio_resid(uio);
3147 attrsize = (int)datasize;
3148 blksize = (int)hfsmp->blockSize;
3149 filesize = VTOF(evp)->ff_size;
3150
3151 #if NEW_XATTR
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))) {
3155 result = ENOMEM;
3156 goto exit;
3157 }
3158 #endif
3159 /*
3160 * Read the attribute data one extent at a time.
3161 * For the typical case there is only one extent.
3162 */
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);
3168 #if NEW_XATTR
3169 // virtualize the IO offset to target this fext
3170 if (!uio_set_fext(uio, &extents[i], blksize)) {
3171 // `fext' is corrupted
3172 result = EILSEQ;
3173 break;
3174 }
3175
3176 // stage the next xattr fext for IO
3177 xattr_fext_set(&hfsmp->hfs_xattr_io, xattr_fext, &extents[i]);
3178
3179 // Set filesize to end of data read to prevent cluster read-ahead
3180 filesize = uio_offset(uio) + iosize;
3181 #else
3182 uio_setoffset(uio, (u_int64_t)extents[i].startBlock * (u_int64_t)blksize);
3183 #endif
3184 result = cluster_read(evp, uio, filesize, IO_SYNC | IO_UNIT);
3185
3186 #if NEW_XATTR
3187 // post IO, unstage this xattr fext
3188 xattr_fext_clear(&hfsmp->hfs_xattr_io, xattr_fext);
3189 #endif
3190
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);
3194 #endif
3195 if (result)
3196 break;
3197 attrsize -= iosize;
3198 bufsize -= iosize;
3199 }
3200 uio_setresid(uio, bufsize);
3201 uio_setoffset(uio, datasize);
3202
3203 #if NEW_XATTR
3204 xattr_fext_free(&hfsmp->hfs_xattr_io, xattr_fext);
3205
3206 exit:
3207 #endif
3208 hfs_unlock_truncate(VTOC(evp), HFS_LOCK_DEFAULT);
3209 return (result);
3210 }
3211
3212 /*
3213 * Write an extent based attribute.
3214 */
3215 static int
3216 write_attr_data(struct hfsmount *hfsmp, uio_t uio, size_t datasize, HFSPlusExtentDescriptor *extents)
3217 {
3218 vnode_t evp = hfsmp->hfs_attrdata_vp;
3219 off_t filesize;
3220 int bufsize;
3221 int attrsize;
3222 int64_t iosize;
3223 int blksize;
3224 int i;
3225 int result = 0;
3226
3227 hfs_lock_truncate(VTOC(evp), HFS_SHARED_LOCK, HFS_LOCK_DEFAULT);
3228
3229 bufsize = uio_resid(uio);
3230 attrsize = (int) datasize;
3231 blksize = (int) hfsmp->blockSize;
3232 filesize = VTOF(evp)->ff_size;
3233
3234 #if NEW_XATTR
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))) {
3238 result = ENOMEM;
3239 goto exit;
3240 }
3241 #endif
3242
3243 /*
3244 * Write the attribute data one extent at a time.
3245 */
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);
3251 #if NEW_XATTR
3252 // virtualize the IO offset to target this fext
3253 if (!uio_set_fext(uio, &extents[i], blksize)) {
3254 // `fext' is corrupted
3255 result = EILSEQ;
3256 break;
3257 }
3258
3259 // stage the next xattr fext for IO
3260 xattr_fext_set(&hfsmp->hfs_xattr_io, xattr_fext, &extents[i]);
3261
3262 filesize = uio_offset(uio) + iosize;
3263 #else
3264 uio_setoffset(uio, (u_int64_t)extents[i].startBlock * (u_int64_t)blksize);
3265 #endif
3266 result = cluster_write(evp, uio, filesize, filesize, filesize,
3267 (off_t) 0, IO_SYNC | IO_UNIT);
3268
3269 #if NEW_XATTR
3270 // post IO, unstage this xattr fext
3271 xattr_fext_clear(&hfsmp->hfs_xattr_io, xattr_fext);
3272 #endif
3273
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);
3277 #endif
3278 if (result)
3279 break;
3280 attrsize -= iosize;
3281 bufsize -= iosize;
3282 }
3283 uio_setresid(uio, bufsize);
3284 uio_setoffset(uio, datasize);
3285
3286 #if NEW_XATTR
3287 xattr_fext_free(&hfsmp->hfs_xattr_io, xattr_fext);
3288
3289 exit:
3290 #endif
3291 hfs_unlock_truncate(VTOC(evp), HFS_LOCK_DEFAULT);
3292 return (result);
3293 }
3294
3295 /*
3296 * Allocate blocks for an extent based attribute.
3297 */
3298 static int
3299 alloc_attr_blks(struct hfsmount *hfsmp, size_t attrsize, size_t extentbufsize, HFSPlusExtentDescriptor *extents, int *blocks)
3300 {
3301 int blkcnt;
3302 int startblk;
3303 int lockflags;
3304 int i;
3305 int maxextents;
3306 int result = 0;
3307
3308 startblk = hfsmp->hfs_metazone_end;
3309 blkcnt = howmany(attrsize, hfsmp->blockSize);
3310 if (blkcnt > (int)hfs_freeblks(hfsmp, 0)) {
3311 return (ENOSPC);
3312 }
3313 *blocks = blkcnt;
3314 maxextents = extentbufsize / sizeof(HFSPlusExtentDescriptor);
3315
3316 lockflags = hfs_systemfile_lock(hfsmp, SFL_BITMAP, HFS_EXCLUSIVE_LOCK);
3317
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);
3322 /*
3323 * If we couldn't find anything, then re-try the allocation but allow
3324 * journal flushes.
3325 */
3326 if (result == dskFulErr) {
3327 result = BlockAllocate(hfsmp, startblk, blkcnt, blkcnt, HFS_ALLOC_FLUSHTXN,
3328 &extents[i].startBlock, &extents[i].blockCount);
3329 }
3330
3331
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);
3335 #endif
3336 if (result) {
3337 extents[i].startBlock = 0;
3338 extents[i].blockCount = 0;
3339 break;
3340 }
3341 blkcnt -= extents[i].blockCount;
3342 startblk = extents[i].startBlock + extents[i].blockCount;
3343 }
3344 /*
3345 * If it didn't fit in the extents buffer then bail.
3346 */
3347 if (blkcnt) {
3348 result = ENOSPC;
3349
3350 #if HFS_XATTR_VERBOSE
3351 printf("hfs: alloc_attr_blks: unexpected failure, %d blocks unallocated\n", blkcnt);
3352 #endif
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;
3358 }
3359 }
3360 }
3361
3362 hfs_systemfile_unlock(hfsmp, lockflags);
3363 return MacToVFSError(result);
3364 }
3365
3366 /*
3367 * Release blocks from an extent based attribute.
3368 */
3369 static void
3370 free_attr_blks(struct hfsmount *hfsmp, int blkcnt, HFSPlusExtentDescriptor *extents)
3371 {
3372 vnode_t evp = hfsmp->hfs_attrdata_vp;
3373 int remblks = blkcnt;
3374 int lockflags;
3375 int i;
3376
3377 lockflags = hfs_systemfile_lock(hfsmp, SFL_BITMAP, HFS_EXCLUSIVE_LOCK);
3378
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);
3384 #endif
3385 extents[i].blockCount = 0;
3386 continue;
3387 }
3388 if (extents[i].startBlock == 0) {
3389 break;
3390 }
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;
3395
3396 #if HFS_XATTR_VERBOSE
3397 printf("hfs: free_attr_blks: BlockDeallocate [%d, %d]\n",
3398 extents[i].startBlock, extents[i].blockCount);
3399 #endif
3400 /* Discard any resident pages for this block range. */
3401 if (evp) {
3402 off_t start, end;
3403
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);
3407 }
3408 }
3409
3410 hfs_systemfile_unlock(hfsmp, lockflags);
3411 }
3412
3413 static int
3414 has_overflow_extents(HFSPlusForkData *forkdata)
3415 {
3416 u_int32_t blocks;
3417
3418 if (forkdata->extents[7].blockCount == 0)
3419 return (0);
3420
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;
3429
3430 return (forkdata->totalBlocks > blocks);
3431 }
3432
3433 static int
3434 count_extent_blocks(int maxblks, HFSPlusExtentRecord extents)
3435 {
3436 int blocks;
3437 int i;
3438
3439 for (i = 0, blocks = 0; i < kHFSPlusExtentDensity; ++i) {
3440 /* Ignore obvious bogus extents. */
3441 if (extents[i].blockCount > (u_int32_t)maxblks)
3442 continue;
3443 if (extents[i].startBlock == 0 || extents[i].blockCount == 0)
3444 break;
3445 blocks += extents[i].blockCount;
3446 }
3447 return (blocks);
3448 }
3449