]> git.saurik.com Git - apple/xnu.git/blob - bsd/hfs/hfs_xattr.c
xnu-2050.48.11.tar.gz
[apple/xnu.git] / bsd / hfs / hfs_xattr.c
1 /*
2 * Copyright (c) 2004-2009 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/vnode_internal.h>
40 #include <sys/kauth.h>
41
42 #include "hfs.h"
43 #include "hfs_cnode.h"
44 #include "hfs_mount.h"
45 #include "hfs_format.h"
46 #include "hfs_endian.h"
47 #include "hfs_btreeio.h"
48 #include "hfs_fsctl.h"
49
50 #include "hfscommon/headers/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 #define HFS_MAXATTRBLKS (32 * 1024)
71
72
73 /* HFS Internal Names */
74 #define XATTR_EXTENDEDSECURITY_NAME "system.extendedsecurity"
75 #define XATTR_XATTREXTENTS_NAME "system.xattrextents"
76
77 /* Faster version if we already know this is the data fork. */
78 #define RSRC_FORK_EXISTS(CP) \
79 (((CP)->c_attr.ca_blocks - (CP)->c_datafork->ff_data.cf_blocks) > 0)
80
81 static u_int32_t emptyfinfo[8] = {0};
82
83 static int hfs_zero_dateadded (struct cnode *cp, u_int8_t *finderinfo);
84
85 const char hfs_attrdatafilename[] = "Attribute Data";
86
87 static int listattr_callback(const HFSPlusAttrKey *key, const HFSPlusAttrData *data,
88 struct listattr_callback_state *state);
89
90 static int remove_attribute_records(struct hfsmount *hfsmp, BTreeIterator * iterator);
91
92 static int getnodecount(struct hfsmount *hfsmp, size_t nodesize);
93
94 static size_t getmaxinlineattrsize(struct vnode * attrvp);
95
96 static int read_attr_data(struct hfsmount *hfsmp, uio_t uio, size_t datasize, HFSPlusExtentDescriptor *extents);
97
98 static int write_attr_data(struct hfsmount *hfsmp, uio_t uio, size_t datasize, HFSPlusExtentDescriptor *extents);
99
100 static int alloc_attr_blks(struct hfsmount *hfsmp, size_t attrsize, size_t extentbufsize, HFSPlusExtentDescriptor *extents, int *blocks);
101
102 static void free_attr_blks(struct hfsmount *hfsmp, int blkcnt, HFSPlusExtentDescriptor *extents);
103
104 static int has_overflow_extents(HFSPlusForkData *forkdata);
105
106 static int count_extent_blocks(int maxblks, HFSPlusExtentRecord extents);
107
108 #if NAMEDSTREAMS
109 /*
110 * Obtain the vnode for a stream.
111 */
112 int
113 hfs_vnop_getnamedstream(struct vnop_getnamedstream_args* ap)
114 {
115 vnode_t vp = ap->a_vp;
116 vnode_t *svpp = ap->a_svpp;
117 struct cnode *cp;
118 int error = 0;
119
120 *svpp = NULL;
121
122 /*
123 * We only support the "com.apple.ResourceFork" stream.
124 */
125 if (bcmp(ap->a_name, XATTR_RESOURCEFORK_NAME, sizeof(XATTR_RESOURCEFORK_NAME)) != 0) {
126 return (ENOATTR);
127 }
128 cp = VTOC(vp);
129 if ( !S_ISREG(cp->c_mode) ) {
130 return (EPERM);
131 }
132 #if HFS_COMPRESSION
133 int hide_rsrc = hfs_hides_rsrc(ap->a_context, VTOC(vp), 1);
134 #endif /* HFS_COMPRESSION */
135 if ((error = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK))) {
136 return (error);
137 }
138 if ((!RSRC_FORK_EXISTS(cp)
139 #if HFS_COMPRESSION
140 || hide_rsrc
141 #endif /* HFS_COMPRESSION */
142 ) && (ap->a_operation != NS_OPEN)) {
143 hfs_unlock(cp);
144 return (ENOATTR);
145 }
146 error = hfs_vgetrsrc(VTOHFS(vp), vp, svpp, TRUE, FALSE);
147 hfs_unlock(cp);
148
149 return (error);
150 }
151
152 /*
153 * Create a stream.
154 */
155 int
156 hfs_vnop_makenamedstream(struct vnop_makenamedstream_args* ap)
157 {
158 vnode_t vp = ap->a_vp;
159 vnode_t *svpp = ap->a_svpp;
160 struct cnode *cp;
161 int error = 0;
162
163 *svpp = NULL;
164
165 /*
166 * We only support the "com.apple.ResourceFork" stream.
167 */
168 if (bcmp(ap->a_name, XATTR_RESOURCEFORK_NAME, sizeof(XATTR_RESOURCEFORK_NAME)) != 0) {
169 return (ENOATTR);
170 }
171 cp = VTOC(vp);
172 if ( !S_ISREG(cp->c_mode) ) {
173 return (EPERM);
174 }
175 #if HFS_COMPRESSION
176 if (hfs_hides_rsrc(ap->a_context, VTOC(vp), 1)) {
177 if (VNODE_IS_RSRC(vp)) {
178 return EINVAL;
179 } else {
180 error = decmpfs_decompress_file(vp, VTOCMP(vp), -1, 1, 0);
181 if (error != 0)
182 return error;
183 }
184 }
185 #endif /* HFS_COMPRESSION */
186 if ((error = hfs_lock(cp, HFS_EXCLUSIVE_LOCK))) {
187 return (error);
188 }
189 error = hfs_vgetrsrc(VTOHFS(vp), vp, svpp, TRUE, FALSE);
190 hfs_unlock(cp);
191
192 return (error);
193 }
194
195 /*
196 * Remove a stream.
197 */
198 int
199 hfs_vnop_removenamedstream(struct vnop_removenamedstream_args* ap)
200 {
201 vnode_t svp = ap->a_svp;
202 struct cnode *scp;
203 int error = 0;
204
205 /*
206 * We only support the "com.apple.ResourceFork" stream.
207 */
208 if (bcmp(ap->a_name, XATTR_RESOURCEFORK_NAME, sizeof(XATTR_RESOURCEFORK_NAME)) != 0) {
209 return (ENOATTR);
210 }
211 #if HFS_COMPRESSION
212 if (hfs_hides_rsrc(ap->a_context, VTOC(svp), 1)) {
213 /* do nothing */
214 return 0;
215 }
216 #endif /* HFS_COMPRESSION */
217
218 scp = VTOC(svp);
219
220 /* Take truncate lock before taking cnode lock. */
221 hfs_lock_truncate(scp, HFS_EXCLUSIVE_LOCK);
222 if ((error = hfs_lock(scp, HFS_EXCLUSIVE_LOCK))) {
223 goto out;
224 }
225 if (VTOF(svp)->ff_size != 0) {
226 error = hfs_truncate(svp, 0, IO_NDELAY, 0, 0, ap->a_context);
227 }
228 hfs_unlock(scp);
229 out:
230 hfs_unlock_truncate(scp, 0);
231 return (error);
232 }
233 #endif
234
235
236 /* Zero out the date added field for the specified cnode */
237 static int hfs_zero_dateadded (struct cnode *cp, u_int8_t *finderinfo) {
238 u_int8_t *finfo = finderinfo;
239
240 /* Advance finfo by 16 bytes to the 2nd half of the finderinfo */
241 finfo = finfo + 16;
242
243 if (S_ISREG(cp->c_attr.ca_mode)) {
244 struct FndrExtendedFileInfo *extinfo = (struct FndrExtendedFileInfo *)finfo;
245 extinfo->date_added = 0;
246 }
247 else if (S_ISDIR(cp->c_attr.ca_mode)) {
248 struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)finfo;
249 extinfo->date_added = 0;
250 }
251 else {
252 /* Return an error */
253 return -1;
254 }
255 return 0;
256
257 }
258
259 /*
260 * Retrieve the data of an extended attribute.
261 */
262 int
263 hfs_vnop_getxattr(struct vnop_getxattr_args *ap)
264 /*
265 struct vnop_getxattr_args {
266 struct vnodeop_desc *a_desc;
267 vnode_t a_vp;
268 char * a_name;
269 uio_t a_uio;
270 size_t *a_size;
271 int a_options;
272 vfs_context_t a_context;
273 };
274 */
275 {
276 struct vnode *vp = ap->a_vp;
277 struct cnode *cp;
278 struct hfsmount *hfsmp;
279 uio_t uio = ap->a_uio;
280 size_t bufsize;
281 int result;
282
283 cp = VTOC(vp);
284 if (vp == cp->c_vp) {
285 #if HFS_COMPRESSION
286 int decmpfs_hide = hfs_hides_xattr(ap->a_context, VTOC(vp), ap->a_name, 1); /* 1 == don't take the cnode lock */
287 if (decmpfs_hide && !(ap->a_options & XATTR_SHOWCOMPRESSION))
288 return ENOATTR;
289 #endif /* HFS_COMPRESSION */
290
291 /* Get the Finder Info. */
292 if (bcmp(ap->a_name, XATTR_FINDERINFO_NAME, sizeof(XATTR_FINDERINFO_NAME)) == 0) {
293 u_int8_t finderinfo[32];
294 bufsize = 32;
295
296 if ((result = hfs_lock(cp, HFS_SHARED_LOCK))) {
297 return (result);
298 }
299 /* Make a copy since we may not export all of it. */
300 bcopy(cp->c_finderinfo, finderinfo, sizeof(finderinfo));
301 hfs_unlock(cp);
302
303 /* Zero out the date added field in the local copy */
304 hfs_zero_dateadded (cp, finderinfo);
305
306 /* Don't expose a symlink's private type/creator. */
307 if (vnode_islnk(vp)) {
308 struct FndrFileInfo *fip;
309
310 fip = (struct FndrFileInfo *)&finderinfo;
311 fip->fdType = 0;
312 fip->fdCreator = 0;
313 }
314 /* If Finder Info is empty then it doesn't exist. */
315 if (bcmp(finderinfo, emptyfinfo, sizeof(emptyfinfo)) == 0) {
316 return (ENOATTR);
317 }
318 if (uio == NULL) {
319 *ap->a_size = bufsize;
320 return (0);
321 }
322 if ((user_size_t)uio_resid(uio) < bufsize)
323 return (ERANGE);
324
325 result = uiomove((caddr_t)&finderinfo , bufsize, uio);
326
327 return (result);
328 }
329 /* Read the Resource Fork. */
330 if (bcmp(ap->a_name, XATTR_RESOURCEFORK_NAME, sizeof(XATTR_RESOURCEFORK_NAME)) == 0) {
331 struct vnode *rvp = NULL;
332 int openunlinked = 0;
333 int namelen = 0;
334
335 if ( !S_ISREG(cp->c_mode) ) {
336 return (EPERM);
337 }
338 if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK))) {
339 return (result);
340 }
341 namelen = cp->c_desc.cd_namelen;
342
343 if ( !RSRC_FORK_EXISTS(cp)) {
344 hfs_unlock(cp);
345 return (ENOATTR);
346 }
347 hfsmp = VTOHFS(vp);
348 if ((cp->c_flag & C_DELETED) && (namelen == 0)) {
349 openunlinked = 1;
350 }
351
352 result = hfs_vgetrsrc(hfsmp, vp, &rvp, TRUE, FALSE);
353 hfs_unlock(cp);
354 if (result) {
355 return (result);
356 }
357 if (uio == NULL) {
358 *ap->a_size = (size_t)VTOF(rvp)->ff_size;
359 } else {
360 #if HFS_COMPRESSION
361 user_ssize_t uio_size = 0;
362 if (decmpfs_hide)
363 uio_size = uio_resid(uio);
364 #endif /* HFS_COMPRESSION */
365 result = VNOP_READ(rvp, uio, 0, ap->a_context);
366 #if HFS_COMPRESSION
367 if (decmpfs_hide &&
368 (result == 0) &&
369 (uio_resid(uio) == uio_size)) {
370 /*
371 * We intentionally make the above call to VNOP_READ so that
372 * it can return an authorization/permission/etc. Error
373 * based on ap->a_context and thus deny this operation;
374 * in that case, result != 0 and we won't proceed.
375 *
376 * However, if result == 0, it will have returned no data
377 * because hfs_vnop_read hid the resource fork
378 * (hence uio_resid(uio) == uio_size, i.e. the uio is untouched)
379 *
380 * In that case, we try again with the decmpfs_ctx context
381 * to get the actual data
382 */
383 result = VNOP_READ(rvp, uio, 0, decmpfs_ctx);
384 }
385 #endif /* HFS_COMPRESSION */
386 }
387 /* force the rsrc fork vnode to recycle right away */
388 if (openunlinked) {
389 int vref;
390 vref = vnode_ref (rvp);
391 if (vref == 0) {
392 vnode_rele (rvp);
393 }
394 vnode_recycle(rvp);
395 }
396 vnode_put(rvp);
397 return (result);
398 }
399 }
400 hfsmp = VTOHFS(vp);
401 /*
402 * Standard HFS only supports native FinderInfo and Resource Forks.
403 */
404 if (hfsmp->hfs_flags & HFS_STANDARD) {
405 return (EPERM);
406 }
407
408 if ((result = hfs_lock(cp, HFS_SHARED_LOCK))) {
409 return (result);
410 }
411
412 /* Check for non-rsrc, non-finderinfo EAs */
413 result = hfs_getxattr_internal (cp, ap, VTOHFS(cp->c_vp), 0);
414
415 hfs_unlock(cp);
416
417 return MacToVFSError(result);
418 }
419
420
421
422 /*
423 * getxattr_internal
424 *
425 * We break out this internal function which searches the attributes B-Tree and the
426 * overflow extents file to find non-resource, non-finderinfo EAs. There may be cases
427 * where we need to get EAs in contexts where we are already holding the cnode lock,
428 * and to re-enter hfs_vnop_getxattr would cause us to double-lock the cnode. Instead,
429 * we can just directly call this function.
430 *
431 * We pass the hfsmp argument directly here because we may not necessarily have a cnode to
432 * operate on. Under normal conditions, we have a file or directory to query, but if we
433 * are operating on the root directory (id 1), then we may not have a cnode. In this case, if hte
434 * 'cp' argument is NULL, then we need to use the 'fileid' argument as the entry to manipulate
435 *
436 * NOTE: This function assumes the cnode lock for 'cp' is held exclusive or shared.
437 */
438
439
440 int hfs_getxattr_internal (struct cnode *cp, struct vnop_getxattr_args *ap,
441 struct hfsmount *hfsmp, u_int32_t fileid) {
442
443 struct filefork *btfile;
444 struct BTreeIterator * iterator = NULL;
445 size_t bufsize = 0;
446 HFSPlusAttrRecord *recp = NULL;
447 FSBufferDescriptor btdata;
448 int lockflags = 0;
449 int result = 0;
450 u_int16_t datasize = 0;
451 uio_t uio = ap->a_uio;
452 u_int32_t target_id = 0;
453
454 if (cp) {
455 target_id = cp->c_fileid;
456 }
457 else {
458 target_id = fileid;
459 }
460
461
462 /* Bail if we don't have an EA B-Tree. */
463 if ((hfsmp->hfs_attribute_vp == NULL) ||
464 ((cp) && (cp->c_attr.ca_recflags & kHFSHasAttributesMask) == 0)) {
465 result = ENOATTR;
466 goto exit;
467 }
468
469 /* Initialize the B-Tree iterator for searching for the proper EA */
470 btfile = VTOF(hfsmp->hfs_attribute_vp);
471
472 MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
473 if (iterator == NULL) {
474 result = ENOMEM;
475 goto exit;
476 }
477 bzero(iterator, sizeof(*iterator));
478
479 bufsize = sizeof(HFSPlusAttrData) - 2;
480 if (uio) {
481 bufsize += uio_resid(uio);
482 }
483 bufsize = MAX(bufsize, sizeof(HFSPlusAttrRecord));
484 MALLOC(recp, HFSPlusAttrRecord *, bufsize, M_TEMP, M_WAITOK);
485 if (recp == NULL) {
486 result = ENOMEM;
487 goto exit;
488 }
489 btdata.bufferAddress = recp;
490 btdata.itemSize = bufsize;
491 btdata.itemCount = 1;
492
493 result = hfs_buildattrkey(target_id, ap->a_name, (HFSPlusAttrKey *)&iterator->key);
494 if (result) {
495 goto exit;
496 }
497
498 /* Lookup the attribute in the Attribute B-Tree */
499 lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK);
500 result = BTSearchRecord(btfile, iterator, &btdata, &datasize, NULL);
501 hfs_systemfile_unlock(hfsmp, lockflags);
502
503 if (result) {
504 if (result == btNotFound) {
505 result = ENOATTR;
506 }
507 goto exit;
508 }
509
510 /*
511 * Operate differently if we have inline EAs that can fit in the attribute B-Tree or if
512 * we have extent based EAs.
513 */
514 switch (recp->recordType) {
515 /* Attribute fits in the Attribute B-Tree */
516 case kHFSPlusAttrInlineData:
517 /*
518 * Sanity check record size. It's not required to have any
519 * user data, so the minimum size is 2 bytes less that the
520 * size of HFSPlusAttrData (since HFSPlusAttrData struct
521 * has 2 bytes set aside for attribute data).
522 */
523 if (datasize < (sizeof(HFSPlusAttrData) - 2)) {
524 printf("hfs_getxattr: %d,%s invalid record size %d (expecting %lu)\n",
525 target_id, ap->a_name, datasize, sizeof(HFSPlusAttrData));
526 result = ENOATTR;
527 break;
528 }
529 *ap->a_size = recp->attrData.attrSize;
530 if (uio && recp->attrData.attrSize != 0) {
531 if (*ap->a_size > (user_size_t)uio_resid(uio)) {
532 result = ERANGE;
533 }
534 else {
535 result = uiomove((caddr_t) &recp->attrData.attrData , recp->attrData.attrSize, uio);
536 }
537 }
538 break;
539 /* Extent-Based EAs */
540 case kHFSPlusAttrForkData: {
541 if (datasize < sizeof(HFSPlusAttrForkData)) {
542 printf("hfs_getxattr: %d,%s invalid record size %d (expecting %lu)\n",
543 target_id, ap->a_name, datasize, sizeof(HFSPlusAttrForkData));
544 result = ENOATTR;
545 break;
546 }
547 *ap->a_size = recp->forkData.theFork.logicalSize;
548 if (uio == NULL) {
549 break;
550 }
551 if (*ap->a_size > (user_size_t)uio_resid(uio)) {
552 result = ERANGE;
553 break;
554 }
555 /* Process overflow extents if necessary. */
556 if (has_overflow_extents(&recp->forkData.theFork)) {
557 HFSPlusExtentDescriptor *extentbuf;
558 HFSPlusExtentDescriptor *extentptr;
559 size_t extentbufsize;
560 u_int32_t totalblocks;
561 u_int32_t blkcnt;
562 u_int32_t attrlen;
563
564 totalblocks = recp->forkData.theFork.totalBlocks;
565 /* Ignore bogus block counts. */
566 if (totalblocks > HFS_MAXATTRBLKS) {
567 result = ERANGE;
568 break;
569 }
570 attrlen = recp->forkData.theFork.logicalSize;
571
572 /* Get a buffer to hold the worst case amount of extents. */
573 extentbufsize = totalblocks * sizeof(HFSPlusExtentDescriptor);
574 extentbufsize = roundup(extentbufsize, sizeof(HFSPlusExtentRecord));
575 MALLOC(extentbuf, HFSPlusExtentDescriptor *, extentbufsize, M_TEMP, M_WAITOK);
576 if (extentbuf == NULL) {
577 result = ENOMEM;
578 break;
579 }
580 bzero(extentbuf, extentbufsize);
581 extentptr = extentbuf;
582
583 /* Grab the first 8 extents. */
584 bcopy(&recp->forkData.theFork.extents[0], extentptr, sizeof(HFSPlusExtentRecord));
585 extentptr += kHFSPlusExtentDensity;
586 blkcnt = count_extent_blocks(totalblocks, recp->forkData.theFork.extents);
587
588 /* Now lookup the overflow extents. */
589 lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK);
590 while (blkcnt < totalblocks) {
591 ((HFSPlusAttrKey *)&iterator->key)->startBlock = blkcnt;
592 result = BTSearchRecord(btfile, iterator, &btdata, &datasize, NULL);
593 if (result ||
594 (recp->recordType != kHFSPlusAttrExtents) ||
595 (datasize < sizeof(HFSPlusAttrExtents))) {
596 printf("hfs_getxattr: %s missing extents, only %d blks of %d found\n",
597 ap->a_name, blkcnt, totalblocks);
598 result = ENOATTR;
599 break; /* break from while */
600 }
601 /* Grab the next 8 extents. */
602 bcopy(&recp->overflowExtents.extents[0], extentptr, sizeof(HFSPlusExtentRecord));
603 extentptr += kHFSPlusExtentDensity;
604 blkcnt += count_extent_blocks(totalblocks, recp->overflowExtents.extents);
605 }
606
607 /* Release Attr B-Tree lock */
608 hfs_systemfile_unlock(hfsmp, lockflags);
609
610 if (blkcnt < totalblocks) {
611 result = ENOATTR;
612 }
613 else {
614 result = read_attr_data(hfsmp, uio, attrlen, extentbuf);
615 }
616 FREE(extentbuf, M_TEMP);
617
618 }
619 else /* No overflow extents. */ {
620 result = read_attr_data(hfsmp, uio, recp->forkData.theFork.logicalSize, recp->forkData.theFork.extents);
621 }
622 break;
623 }
624
625 default:
626 /* We only support Extent or inline EAs. Default to ENOATTR for anything else */
627 result = ENOATTR;
628 break;
629 }
630
631 exit:
632 if (iterator) {
633 FREE(iterator, M_TEMP);
634 }
635 if (recp) {
636 FREE(recp, M_TEMP);
637 }
638
639 return result;
640
641 }
642
643
644 /*
645 * Set the data of an extended attribute.
646 */
647 int
648 hfs_vnop_setxattr(struct vnop_setxattr_args *ap)
649 /*
650 struct vnop_setxattr_args {
651 struct vnodeop_desc *a_desc;
652 vnode_t a_vp;
653 char * a_name;
654 uio_t a_uio;
655 int a_options;
656 vfs_context_t a_context;
657 };
658 */
659 {
660 struct vnode *vp = ap->a_vp;
661 struct cnode *cp = NULL;
662 struct hfsmount *hfsmp;
663 uio_t uio = ap->a_uio;
664 size_t attrsize;
665 void * user_data_ptr = NULL;
666 int result;
667 time_t orig_ctime=VTOC(vp)->c_ctime;
668
669 if (ap->a_name == NULL || ap->a_name[0] == '\0') {
670 return (EINVAL); /* invalid name */
671 }
672 hfsmp = VTOHFS(vp);
673 if (VNODE_IS_RSRC(vp)) {
674 return (EPERM);
675 }
676
677 #if HFS_COMPRESSION
678 if (hfs_hides_xattr(ap->a_context, VTOC(vp), ap->a_name, 1) ) { /* 1 == don't take the cnode lock */
679 result = decmpfs_decompress_file(vp, VTOCMP(vp), -1, 1, 0);
680 if (result != 0)
681 return result;
682 }
683
684 check_for_tracked_file(vp, orig_ctime, NAMESPACE_HANDLER_METADATA_WRITE_OP, NULL);
685 #endif /* HFS_COMPRESSION */
686
687 /* Set the Finder Info. */
688 if (bcmp(ap->a_name, XATTR_FINDERINFO_NAME, sizeof(XATTR_FINDERINFO_NAME)) == 0) {
689 u_int8_t finderinfo[32];
690 struct FndrFileInfo *fip;
691 void * finderinfo_start;
692 u_int8_t *finfo = NULL;
693 u_int16_t fdFlags;
694 u_int32_t dateadded = 0;
695
696 attrsize = sizeof(VTOC(vp)->c_finderinfo);
697
698 if ((user_size_t)uio_resid(uio) != attrsize) {
699 return (ERANGE);
700 }
701 /* Grab the new Finder Info data. */
702 if ((result = uiomove((caddr_t)&finderinfo , attrsize, uio))) {
703 return (result);
704 }
705 fip = (struct FndrFileInfo *)&finderinfo;
706
707 if ((result = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK))) {
708 return (result);
709 }
710 cp = VTOC(vp);
711
712 /* Symlink's don't have an external type/creator. */
713 if (vnode_islnk(vp)) {
714 /* Skip over type/creator fields. */
715 finderinfo_start = &cp->c_finderinfo[8];
716 attrsize -= 8;
717 } else {
718 finderinfo_start = &cp->c_finderinfo[0];
719 /*
720 * Don't allow the external setting of
721 * file type to kHardLinkFileType.
722 */
723 if (fip->fdType == SWAP_BE32(kHardLinkFileType)) {
724 hfs_unlock(cp);
725 return (EPERM);
726 }
727 }
728
729 /* Grab the current date added from the cnode */
730 dateadded = hfs_get_dateadded (cp);
731
732 /* Zero out the date added field to ignore user's attempts to set it */
733 hfs_zero_dateadded(cp, finderinfo);
734
735 if (bcmp(finderinfo_start, emptyfinfo, attrsize)) {
736 /* attr exists and "create" was specified. */
737 if (ap->a_options & XATTR_CREATE) {
738 hfs_unlock(cp);
739 return (EEXIST);
740 }
741 } else /* empty */ {
742 /* attr doesn't exists and "replace" was specified. */
743 if (ap->a_options & XATTR_REPLACE) {
744 hfs_unlock(cp);
745 return (ENOATTR);
746 }
747 }
748
749 /*
750 * Now restore the date added to the finderinfo to be written out.
751 * Advance to the 2nd half of the finderinfo to write out the date added
752 * into the buffer.
753 *
754 * Make sure to endian swap the date added back into big endian. When we used
755 * hfs_get_dateadded above to retrieve it, it swapped into local endianness
756 * for us. But now that we're writing it out, put it back into big endian.
757 */
758 finfo = &finderinfo[16];
759
760 if (S_ISREG(cp->c_attr.ca_mode)) {
761 struct FndrExtendedFileInfo *extinfo = (struct FndrExtendedFileInfo *)finfo;
762 extinfo->date_added = OSSwapHostToBigInt32(dateadded);
763 }
764 else if (S_ISDIR(cp->c_attr.ca_mode)) {
765 struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)finfo;
766 extinfo->date_added = OSSwapHostToBigInt32(dateadded);
767 }
768
769 /* Set the cnode's Finder Info. */
770 if (attrsize == sizeof(cp->c_finderinfo))
771 bcopy(&finderinfo[0], finderinfo_start, attrsize);
772 else
773 bcopy(&finderinfo[8], finderinfo_start, attrsize);
774
775 /* Updating finderInfo updates change time and modified time */
776 cp->c_touch_chgtime = TRUE;
777 cp->c_flag |= C_MODIFIED;
778
779 /*
780 * Mirror the invisible bit to the UF_HIDDEN flag.
781 *
782 * The fdFlags for files and frFlags for folders are both 8 bytes
783 * into the userInfo (the first 16 bytes of the Finder Info). They
784 * are both 16-bit fields.
785 */
786 fdFlags = *((u_int16_t *) &cp->c_finderinfo[8]);
787 if (fdFlags & OSSwapHostToBigConstInt16(kFinderInvisibleMask))
788 cp->c_bsdflags |= UF_HIDDEN;
789 else
790 cp->c_bsdflags &= ~UF_HIDDEN;
791
792 result = hfs_update(vp, FALSE);
793
794 hfs_unlock(cp);
795 return (result);
796 }
797 /* Write the Resource Fork. */
798 if (bcmp(ap->a_name, XATTR_RESOURCEFORK_NAME, sizeof(XATTR_RESOURCEFORK_NAME)) == 0) {
799 struct vnode *rvp = NULL;
800 int namelen = 0;
801 int openunlinked = 0;
802
803 if (!vnode_isreg(vp)) {
804 return (EPERM);
805 }
806 if ((result = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK))) {
807 return (result);
808 }
809 cp = VTOC(vp);
810 namelen = cp->c_desc.cd_namelen;
811
812 if (RSRC_FORK_EXISTS(cp)) {
813 /* attr exists and "create" was specified. */
814 if (ap->a_options & XATTR_CREATE) {
815 hfs_unlock(cp);
816 return (EEXIST);
817 }
818 } else {
819 /* attr doesn't exists and "replace" was specified. */
820 if (ap->a_options & XATTR_REPLACE) {
821 hfs_unlock(cp);
822 return (ENOATTR);
823 }
824 }
825
826 /*
827 * Note that we could be called on to grab the rsrc fork vnode
828 * for a file that has become open-unlinked.
829 */
830 if ((cp->c_flag & C_DELETED) && (namelen == 0)) {
831 openunlinked = 1;
832 }
833
834 result = hfs_vgetrsrc(hfsmp, vp, &rvp, TRUE, FALSE);
835 hfs_unlock(cp);
836 if (result) {
837 return (result);
838 }
839 /* VNOP_WRITE marks cnode as needing a modtime update */
840 result = VNOP_WRITE(rvp, uio, 0, ap->a_context);
841
842 /* if open unlinked, force it inactive */
843 if (openunlinked) {
844 int vref;
845 vref = vnode_ref (rvp);
846 if (vref == 0) {
847 vnode_rele(rvp);
848 }
849 vnode_recycle (rvp);
850 }
851 else {
852 /* cnode is not open-unlinked, so re-lock cnode to sync */
853 if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK))) {
854 vnode_recycle (rvp);
855 vnode_put(rvp);
856 return result;
857 }
858
859 /* hfs fsync rsrc fork to force to disk and update modtime */
860 result = hfs_fsync (rvp, MNT_NOWAIT, 0, vfs_context_proc (ap->a_context));
861 hfs_unlock (cp);
862 }
863
864 vnode_put(rvp);
865 return (result);
866 }
867 /*
868 * Standard HFS only supports native FinderInfo and Resource Forks.
869 */
870 if (hfsmp->hfs_flags & HFS_STANDARD) {
871 return (EPERM);
872 }
873 attrsize = uio_resid(uio);
874
875 /* Enforce an upper limit. */
876 if (attrsize > HFS_XATTR_MAXSIZE) {
877 result = E2BIG;
878 goto exit;
879 }
880
881 /*
882 * Attempt to copy the users attr data before taking any locks.
883 */
884 if (attrsize > 0 &&
885 hfsmp->hfs_max_inline_attrsize != 0 &&
886 attrsize < hfsmp->hfs_max_inline_attrsize) {
887 MALLOC(user_data_ptr, void *, attrsize, M_TEMP, M_WAITOK);
888 if (user_data_ptr == NULL) {
889 result = ENOMEM;
890 goto exit;
891 }
892
893 result = uiomove((caddr_t)user_data_ptr, attrsize, uio);
894 if (result) {
895 goto exit;
896 }
897 }
898
899 result = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK);
900 if (result) {
901 goto exit;
902 }
903 cp = VTOC(vp);
904
905 /*
906 * If we're trying to set a non-finderinfo, non-resourcefork EA, then
907 * call the breakout function.
908 */
909 result = hfs_setxattr_internal (cp, user_data_ptr, attrsize, ap, VTOHFS(vp), 0);
910
911 exit:
912 if (cp) {
913 hfs_unlock(cp);
914 }
915 if (user_data_ptr) {
916 FREE(user_data_ptr, M_TEMP);
917 }
918
919 return (result == btNotFound ? ENOATTR : MacToVFSError(result));
920 }
921
922
923 /*
924 * hfs_setxattr_internal
925 *
926 * Internal function to set non-rsrc, non-finderinfo EAs to either the attribute B-Tree or
927 * extent-based EAs.
928 *
929 * See comments from hfs_getxattr_internal on why we need to pass 'hfsmp' and fileid here.
930 * The gist is that we could end up writing to the root folder which may not have a cnode.
931 *
932 * Assumptions:
933 * 1. cnode 'cp' is locked EXCLUSIVE before calling this function.
934 * 2. data_ptr contains data to be written. If gathering data from userland, this must be
935 * done before calling this function.
936 * 3. If data originates entirely in-kernel, use a null UIO, and ensure the size is less than
937 * hfsmp->hfs_max_inline_attrsize bytes long.
938 */
939 int hfs_setxattr_internal (struct cnode *cp, caddr_t data_ptr, size_t attrsize,
940 struct vnop_setxattr_args *ap, struct hfsmount *hfsmp,
941 u_int32_t fileid) {
942 uio_t uio = ap->a_uio;
943 struct vnode *vp = ap->a_vp;
944 int started_transaction = 0;
945 struct BTreeIterator * iterator = NULL;
946 struct filefork *btfile = NULL;
947 FSBufferDescriptor btdata;
948 HFSPlusAttrRecord attrdata; /* 90 bytes */
949 HFSPlusAttrRecord *recp = NULL;
950 HFSPlusExtentDescriptor *extentptr = NULL;
951 int result = 0;
952 int lockflags = 0;
953 int exists = 0;
954 int allocatedblks = 0;
955 u_int32_t target_id;
956 int takelock = 1;
957
958 if (cp) {
959 target_id = cp->c_fileid;
960 }
961 else {
962 target_id = fileid;
963 if (target_id != 1) {
964 /*
965 * If we are manipulating something other than
966 * the root folder (id 1), and do not have a cnode-in-hand,
967 * then we must already hold the requisite b-tree locks from
968 * earlier up the call stack. (See hfs_makenode)
969 */
970 takelock = 0;
971 }
972 }
973
974 /* Start a transaction for our changes. */
975 if (hfs_start_transaction(hfsmp) != 0) {
976 result = EINVAL;
977 goto exit;
978 }
979 started_transaction = 1;
980
981 /*
982 * Once we started the transaction, nobody can compete
983 * with us, so make sure this file is still there.
984 */
985 if ((cp) && (cp->c_flag & C_NOEXISTS)) {
986 result = ENOENT;
987 goto exit;
988 }
989
990 /*
991 * If there isn't an attributes b-tree then create one.
992 */
993 if (hfsmp->hfs_attribute_vp == NULL) {
994 result = hfs_create_attr_btree(hfsmp, ATTRIBUTE_FILE_NODE_SIZE,
995 getnodecount(hfsmp, ATTRIBUTE_FILE_NODE_SIZE));
996 if (result) {
997 goto exit;
998 }
999 }
1000 if (hfsmp->hfs_max_inline_attrsize == 0) {
1001 hfsmp->hfs_max_inline_attrsize = getmaxinlineattrsize(hfsmp->hfs_attribute_vp);
1002 }
1003
1004 if (takelock) {
1005 /* Take exclusive access to the attributes b-tree. */
1006 lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_EXCLUSIVE_LOCK);
1007 }
1008
1009 /* Build the b-tree key. */
1010 MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
1011 if (iterator == NULL) {
1012 result = ENOMEM;
1013 goto exit;
1014 }
1015 bzero(iterator, sizeof(*iterator));
1016 result = hfs_buildattrkey(target_id, ap->a_name, (HFSPlusAttrKey *)&iterator->key);
1017 if (result) {
1018 goto exit;
1019 }
1020
1021 /* Preflight for replace/create semantics. */
1022 btfile = VTOF(hfsmp->hfs_attribute_vp);
1023 btdata.bufferAddress = &attrdata;
1024 btdata.itemSize = sizeof(attrdata);
1025 btdata.itemCount = 1;
1026 exists = BTSearchRecord(btfile, iterator, &btdata, NULL, NULL) == 0;
1027
1028 /* Replace requires that the attribute already exists. */
1029 if ((ap->a_options & XATTR_REPLACE) && !exists) {
1030 result = ENOATTR;
1031 goto exit;
1032 }
1033 /* Create requires that the attribute doesn't exist. */
1034 if ((ap->a_options & XATTR_CREATE) && exists) {
1035 result = EEXIST;
1036 goto exit;
1037 }
1038
1039 /* If it won't fit inline then use extent-based attributes. */
1040 if (attrsize > hfsmp->hfs_max_inline_attrsize) {
1041 size_t extentbufsize;
1042 int blkcnt;
1043 int extentblks;
1044 u_int32_t *keystartblk;
1045 int i;
1046
1047 if (uio == NULL) {
1048 /*
1049 * setxattrs originating from in-kernel are not supported if they are bigger
1050 * than the inline max size. Just return ENOATTR and force them to do it with a
1051 * smaller EA.
1052 */
1053 result = EPERM;
1054 goto exit;
1055 }
1056
1057 /* Get some blocks. */
1058 blkcnt = howmany(attrsize, hfsmp->blockSize);
1059 extentbufsize = blkcnt * sizeof(HFSPlusExtentDescriptor);
1060 extentbufsize = roundup(extentbufsize, sizeof(HFSPlusExtentRecord));
1061 MALLOC(extentptr, HFSPlusExtentDescriptor *, extentbufsize, M_TEMP, M_WAITOK);
1062 if (extentptr == NULL) {
1063 result = ENOMEM;
1064 goto exit;
1065 }
1066 bzero(extentptr, extentbufsize);
1067 result = alloc_attr_blks(hfsmp, attrsize, extentbufsize, extentptr, &allocatedblks);
1068 if (result) {
1069 allocatedblks = 0;
1070 goto exit; /* no more space */
1071 }
1072 /* Copy data into the blocks. */
1073 result = write_attr_data(hfsmp, uio, attrsize, extentptr);
1074 if (result) {
1075 if (vp) {
1076 const char *name = vnode_getname(vp);
1077 printf("hfs_setxattr: write_attr_data err (%d) %s:%s\n",
1078 result, name ? name : "", ap->a_name);
1079 if (name)
1080 vnode_putname(name);
1081 }
1082 goto exit;
1083 }
1084
1085 /* Now remove any previous attribute. */
1086 if (exists) {
1087 result = remove_attribute_records(hfsmp, iterator);
1088 if (result) {
1089 if (vp) {
1090 const char *name = vnode_getname(vp);
1091 printf("hfs_setxattr: remove_attribute_records err (%d) %s:%s\n",
1092 result, name ? name : "", ap->a_name);
1093 if (name)
1094 vnode_putname(name);
1095 }
1096 goto exit;
1097 }
1098 }
1099 /* Create attribute fork data record. */
1100 MALLOC(recp, HFSPlusAttrRecord *, sizeof(HFSPlusAttrRecord), M_TEMP, M_WAITOK);
1101 if (recp == NULL) {
1102 result = ENOMEM;
1103 goto exit;
1104 }
1105 btdata.bufferAddress = recp;
1106 btdata.itemCount = 1;
1107 btdata.itemSize = sizeof(HFSPlusAttrForkData);
1108
1109 recp->recordType = kHFSPlusAttrForkData;
1110 recp->forkData.reserved = 0;
1111 recp->forkData.theFork.logicalSize = attrsize;
1112 recp->forkData.theFork.clumpSize = 0;
1113 recp->forkData.theFork.totalBlocks = blkcnt;
1114 bcopy(extentptr, recp->forkData.theFork.extents, sizeof(HFSPlusExtentRecord));
1115
1116 (void) hfs_buildattrkey(target_id, ap->a_name, (HFSPlusAttrKey *)&iterator->key);
1117
1118 result = BTInsertRecord(btfile, iterator, &btdata, btdata.itemSize);
1119 if (result) {
1120 printf ("hfs_setxattr: BTInsertRecord() - %d,%s err=%d\n",
1121 target_id, ap->a_name, result);
1122 goto exit;
1123 }
1124 extentblks = count_extent_blocks(blkcnt, recp->forkData.theFork.extents);
1125 blkcnt -= extentblks;
1126 keystartblk = &((HFSPlusAttrKey *)&iterator->key)->startBlock;
1127 i = 0;
1128
1129 /* Create overflow extents as needed. */
1130 while (blkcnt > 0) {
1131 /* Initialize the key and record. */
1132 *keystartblk += (u_int32_t)extentblks;
1133 btdata.itemSize = sizeof(HFSPlusAttrExtents);
1134 recp->recordType = kHFSPlusAttrExtents;
1135 recp->overflowExtents.reserved = 0;
1136
1137 /* Copy the next set of extents. */
1138 i += kHFSPlusExtentDensity;
1139 bcopy(&extentptr[i], recp->overflowExtents.extents, sizeof(HFSPlusExtentRecord));
1140
1141 result = BTInsertRecord(btfile, iterator, &btdata, btdata.itemSize);
1142 if (result) {
1143 printf ("hfs_setxattr: BTInsertRecord() overflow - %d,%s err=%d\n",
1144 target_id, ap->a_name, result);
1145 goto exit;
1146 }
1147 extentblks = count_extent_blocks(blkcnt, recp->overflowExtents.extents);
1148 blkcnt -= extentblks;
1149 }
1150 }
1151 else { /* Inline data */
1152 if (exists) {
1153 result = remove_attribute_records(hfsmp, iterator);
1154 if (result) {
1155 goto exit;
1156 }
1157 }
1158
1159 /* Calculate size of record rounded up to multiple of 2 bytes. */
1160 btdata.itemSize = sizeof(HFSPlusAttrData) - 2 + attrsize + ((attrsize & 1) ? 1 : 0);
1161 MALLOC(recp, HFSPlusAttrRecord *, btdata.itemSize, M_TEMP, M_WAITOK);
1162 if (recp == NULL) {
1163 result = ENOMEM;
1164 goto exit;
1165 }
1166 recp->recordType = kHFSPlusAttrInlineData;
1167 recp->attrData.reserved[0] = 0;
1168 recp->attrData.reserved[1] = 0;
1169 recp->attrData.attrSize = attrsize;
1170
1171 /* Copy in the attribute data (if any). */
1172 if (attrsize > 0) {
1173 if (data_ptr) {
1174 bcopy(data_ptr, &recp->attrData.attrData, attrsize);
1175 }
1176 else {
1177 /*
1178 * A null UIO meant it originated in-kernel. If they didn't supply data_ptr
1179 * then deny the copy operation.
1180 */
1181 if (uio == NULL) {
1182 result = EPERM;
1183 goto exit;
1184 }
1185 result = uiomove((caddr_t)&recp->attrData.attrData, attrsize, uio);
1186 }
1187
1188 if (result) {
1189 goto exit;
1190 }
1191 }
1192
1193 (void) hfs_buildattrkey(target_id, ap->a_name, (HFSPlusAttrKey *)&iterator->key);
1194
1195 btdata.bufferAddress = recp;
1196 btdata.itemCount = 1;
1197 result = BTInsertRecord(btfile, iterator, &btdata, btdata.itemSize);
1198 }
1199
1200 exit:
1201 if (btfile && started_transaction) {
1202 (void) BTFlushPath(btfile);
1203 }
1204 if (lockflags) {
1205 hfs_systemfile_unlock(hfsmp, lockflags);
1206 }
1207 if (result == 0) {
1208 if (vp) {
1209 cp = VTOC(vp);
1210 /* Setting an attribute only updates change time and not
1211 * modified time of the file.
1212 */
1213 cp->c_touch_chgtime = TRUE;
1214 cp->c_attr.ca_recflags |= kHFSHasAttributesMask;
1215 if ((bcmp(ap->a_name, KAUTH_FILESEC_XATTR, sizeof(KAUTH_FILESEC_XATTR)) == 0)) {
1216 cp->c_attr.ca_recflags |= kHFSHasSecurityMask;
1217 }
1218 (void) hfs_update(vp, 0);
1219 }
1220 }
1221 if (started_transaction) {
1222 if (result && allocatedblks) {
1223 free_attr_blks(hfsmp, allocatedblks, extentptr);
1224 }
1225 hfs_end_transaction(hfsmp);
1226 }
1227
1228 if (recp) {
1229 FREE(recp, M_TEMP);
1230 }
1231 if (extentptr) {
1232 FREE(extentptr, M_TEMP);
1233 }
1234 if (iterator) {
1235 FREE(iterator, M_TEMP);
1236 }
1237
1238 return result;
1239 }
1240
1241
1242
1243
1244 /*
1245 * Remove an extended attribute.
1246 */
1247 int
1248 hfs_vnop_removexattr(struct vnop_removexattr_args *ap)
1249 /*
1250 struct vnop_removexattr_args {
1251 struct vnodeop_desc *a_desc;
1252 vnode_t a_vp;
1253 char * a_name;
1254 int a_options;
1255 vfs_context_t a_context;
1256 };
1257 */
1258 {
1259 struct vnode *vp = ap->a_vp;
1260 struct cnode *cp = VTOC(vp);
1261 struct hfsmount *hfsmp;
1262 struct BTreeIterator * iterator = NULL;
1263 int lockflags;
1264 int result;
1265 time_t orig_ctime=VTOC(vp)->c_ctime;
1266
1267 if (ap->a_name == NULL || ap->a_name[0] == '\0') {
1268 return (EINVAL); /* invalid name */
1269 }
1270 hfsmp = VTOHFS(vp);
1271 if (VNODE_IS_RSRC(vp)) {
1272 return (EPERM);
1273 }
1274
1275 #if HFS_COMPRESSION
1276 if (hfs_hides_xattr(ap->a_context, VTOC(vp), ap->a_name, 1) && !(ap->a_options & XATTR_SHOWCOMPRESSION)) {
1277 return ENOATTR;
1278 }
1279
1280 check_for_tracked_file(vp, orig_ctime, NAMESPACE_HANDLER_METADATA_DELETE_OP, NULL);
1281 #endif /* HFS_COMPRESSION */
1282
1283 /* If Resource Fork is non-empty then truncate it. */
1284 if (bcmp(ap->a_name, XATTR_RESOURCEFORK_NAME, sizeof(XATTR_RESOURCEFORK_NAME)) == 0) {
1285 struct vnode *rvp = NULL;
1286
1287 if ( !vnode_isreg(vp) ) {
1288 return (EPERM);
1289 }
1290 if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK))) {
1291 return (result);
1292 }
1293 if ( !RSRC_FORK_EXISTS(cp)) {
1294 hfs_unlock(cp);
1295 return (ENOATTR);
1296 }
1297 result = hfs_vgetrsrc(hfsmp, vp, &rvp, TRUE, FALSE);
1298 hfs_unlock(cp);
1299 if (result) {
1300 return (result);
1301 }
1302
1303 hfs_lock_truncate(VTOC(rvp), HFS_EXCLUSIVE_LOCK);
1304 if ((result = hfs_lock(VTOC(rvp), HFS_EXCLUSIVE_LOCK))) {
1305 hfs_unlock_truncate(cp, 0);
1306 vnode_put(rvp);
1307 return (result);
1308 }
1309
1310 /* Start a transaction for encapsulating changes in
1311 * hfs_truncate() and hfs_update()
1312 */
1313 if ((result = hfs_start_transaction(hfsmp))) {
1314 hfs_unlock_truncate(cp, 0);
1315 hfs_unlock(cp);
1316 vnode_put(rvp);
1317 return (result);
1318 }
1319
1320 result = hfs_truncate(rvp, (off_t)0, IO_NDELAY, 0, 0, ap->a_context);
1321 if (result == 0) {
1322 cp->c_touch_chgtime = TRUE;
1323 cp->c_flag |= C_MODIFIED;
1324 result = hfs_update(vp, FALSE);
1325 }
1326
1327 hfs_end_transaction(hfsmp);
1328 hfs_unlock_truncate(VTOC(rvp), 0);
1329 hfs_unlock(VTOC(rvp));
1330
1331 vnode_put(rvp);
1332 return (result);
1333 }
1334 /* Clear out the Finder Info. */
1335 if (bcmp(ap->a_name, XATTR_FINDERINFO_NAME, sizeof(XATTR_FINDERINFO_NAME)) == 0) {
1336 void * finderinfo_start;
1337 int finderinfo_size;
1338 u_int8_t finderinfo[32];
1339 u_int32_t date_added;
1340 u_int8_t *finfo = NULL;
1341
1342 if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK))) {
1343 return (result);
1344 }
1345
1346 /* Use the local copy to store our temporary changes. */
1347 bcopy(cp->c_finderinfo, finderinfo, sizeof(finderinfo));
1348
1349
1350 /* Zero out the date added field in the local copy */
1351 hfs_zero_dateadded (cp, finderinfo);
1352
1353 /* Don't expose a symlink's private type/creator. */
1354 if (vnode_islnk(vp)) {
1355 struct FndrFileInfo *fip;
1356
1357 fip = (struct FndrFileInfo *)&finderinfo;
1358 fip->fdType = 0;
1359 fip->fdCreator = 0;
1360 }
1361
1362 /* Do the byte compare against the local copy */
1363 if (bcmp(finderinfo, emptyfinfo, sizeof(emptyfinfo)) == 0) {
1364 hfs_unlock(cp);
1365 return (ENOATTR);
1366 }
1367
1368 /*
1369 * If there was other content, zero out everything except
1370 * type/creator and date added. First, save the date added.
1371 */
1372 finfo = cp->c_finderinfo;
1373 finfo = finfo + 16;
1374 if (S_ISREG(cp->c_attr.ca_mode)) {
1375 struct FndrExtendedFileInfo *extinfo = (struct FndrExtendedFileInfo *)finfo;
1376 date_added = extinfo->date_added;
1377 }
1378 else if (S_ISDIR(cp->c_attr.ca_mode)) {
1379 struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)finfo;
1380 date_added = extinfo->date_added;
1381 }
1382
1383 if (vnode_islnk(vp)) {
1384 /* Ignore type/creator */
1385 finderinfo_start = &cp->c_finderinfo[8];
1386 finderinfo_size = sizeof(cp->c_finderinfo) - 8;
1387 }
1388 else {
1389 finderinfo_start = &cp->c_finderinfo[0];
1390 finderinfo_size = sizeof(cp->c_finderinfo);
1391 }
1392 bzero(finderinfo_start, finderinfo_size);
1393
1394
1395 /* Now restore the date added */
1396 if (S_ISREG(cp->c_attr.ca_mode)) {
1397 struct FndrExtendedFileInfo *extinfo = (struct FndrExtendedFileInfo *)finfo;
1398 extinfo->date_added = date_added;
1399 }
1400 else if (S_ISDIR(cp->c_attr.ca_mode)) {
1401 struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)finfo;
1402 extinfo->date_added = date_added;
1403 }
1404
1405 /* Updating finderInfo updates change time and modified time */
1406 cp->c_touch_chgtime = TRUE;
1407 cp->c_flag |= C_MODIFIED;
1408 hfs_update(vp, FALSE);
1409
1410 hfs_unlock(cp);
1411
1412 return (0);
1413 }
1414 /*
1415 * Standard HFS only supports native FinderInfo and Resource Forks.
1416 */
1417 if (hfsmp->hfs_flags & HFS_STANDARD) {
1418 return (EPERM);
1419 }
1420 if (hfsmp->hfs_attribute_vp == NULL) {
1421 return (ENOATTR);
1422 }
1423
1424 MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
1425 if (iterator == NULL) {
1426 return (ENOMEM);
1427 }
1428 bzero(iterator, sizeof(*iterator));
1429
1430 if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK))) {
1431 goto exit_nolock;
1432 }
1433
1434 result = hfs_buildattrkey(cp->c_fileid, ap->a_name, (HFSPlusAttrKey *)&iterator->key);
1435 if (result) {
1436 goto exit;
1437 }
1438
1439 if (hfs_start_transaction(hfsmp) != 0) {
1440 result = EINVAL;
1441 goto exit;
1442 }
1443 lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE | SFL_BITMAP, HFS_EXCLUSIVE_LOCK);
1444
1445 result = remove_attribute_records(hfsmp, iterator);
1446
1447 hfs_systemfile_unlock(hfsmp, lockflags);
1448
1449 if (result == 0) {
1450 cp->c_touch_chgtime = TRUE;
1451
1452 lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK);
1453
1454 /* If no more attributes exist, clear attribute bit */
1455 result = file_attribute_exist(hfsmp, cp->c_fileid);
1456 if (result == 0) {
1457 cp->c_attr.ca_recflags &= ~kHFSHasAttributesMask;
1458 }
1459 if (result == EEXIST) {
1460 result = 0;
1461 }
1462
1463 hfs_systemfile_unlock(hfsmp, lockflags);
1464
1465 /* If ACL was removed, clear security bit */
1466 if ((bcmp(ap->a_name, KAUTH_FILESEC_XATTR, sizeof(KAUTH_FILESEC_XATTR)) == 0)) {
1467 cp->c_attr.ca_recflags &= ~kHFSHasSecurityMask;
1468 }
1469 (void) hfs_update(vp, 0);
1470 }
1471
1472 hfs_end_transaction(hfsmp);
1473 exit:
1474 hfs_unlock(cp);
1475 exit_nolock:
1476 FREE(iterator, M_TEMP);
1477 return MacToVFSError(result);
1478 }
1479
1480 /* Check if any attribute record exist for given fileID. This function
1481 * is called by hfs_vnop_removexattr to determine if it should clear the
1482 * attribute bit in the catalog record or not.
1483 *
1484 * Note - you must acquire a shared lock on the attribute btree before
1485 * calling this function.
1486 *
1487 * Output:
1488 * EEXIST - If attribute record was found
1489 * 0 - Attribute was not found
1490 * (other) - Other error (such as EIO)
1491 */
1492 int
1493 file_attribute_exist(struct hfsmount *hfsmp, uint32_t fileID)
1494 {
1495 HFSPlusAttrKey *key;
1496 struct BTreeIterator * iterator = NULL;
1497 struct filefork *btfile;
1498 int result = 0;
1499
1500 // if there's no attribute b-tree we sure as heck
1501 // can't have any attributes!
1502 if (hfsmp->hfs_attribute_vp == NULL) {
1503 return false;
1504 }
1505
1506 MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
1507 if (iterator == NULL) {
1508 result = ENOMEM;
1509 goto out;
1510 }
1511 bzero(iterator, sizeof(*iterator));
1512 key = (HFSPlusAttrKey *)&iterator->key;
1513
1514 result = hfs_buildattrkey(fileID, NULL, key);
1515 if (result) {
1516 goto out;
1517 }
1518
1519 btfile = VTOF(hfsmp->hfs_attribute_vp);
1520 result = BTSearchRecord(btfile, iterator, NULL, NULL, NULL);
1521 if (result && (result != btNotFound)) {
1522 goto out;
1523 }
1524
1525 result = BTIterateRecord(btfile, kBTreeNextRecord, iterator, NULL, NULL);
1526 /* If no next record was found or fileID for next record did not match,
1527 * no more attributes exist for this fileID
1528 */
1529 if ((result && (result == btNotFound)) || (key->fileID != fileID)) {
1530 result = 0;
1531 } else {
1532 result = EEXIST;
1533 }
1534
1535 out:
1536 if (iterator) {
1537 FREE(iterator, M_TEMP);
1538 }
1539 return result;
1540 }
1541
1542
1543 /*
1544 * Remove all the records for a given attribute.
1545 *
1546 * - Used by hfs_vnop_removexattr, hfs_vnop_setxattr and hfs_removeallattr.
1547 * - A transaction must have been started.
1548 * - The Attribute b-tree file must be locked exclusive.
1549 * - The Allocation Bitmap file must be locked exclusive.
1550 * - The iterator key must be initialized.
1551 */
1552 int
1553 remove_attribute_records(struct hfsmount *hfsmp, BTreeIterator * iterator)
1554 {
1555 struct filefork *btfile;
1556 FSBufferDescriptor btdata;
1557 HFSPlusAttrRecord attrdata; /* 90 bytes */
1558 u_int16_t datasize;
1559 int result;
1560
1561 btfile = VTOF(hfsmp->hfs_attribute_vp);
1562
1563 btdata.bufferAddress = &attrdata;
1564 btdata.itemSize = sizeof(attrdata);
1565 btdata.itemCount = 1;
1566 result = BTSearchRecord(btfile, iterator, &btdata, &datasize, NULL);
1567 if (result) {
1568 goto exit; /* no records. */
1569 }
1570 /*
1571 * Free the blocks from extent based attributes.
1572 *
1573 * Note that the block references (btree records) are removed
1574 * before releasing the blocks in the allocation bitmap.
1575 */
1576 if (attrdata.recordType == kHFSPlusAttrForkData) {
1577 int totalblks;
1578 int extentblks;
1579 u_int32_t *keystartblk;
1580
1581 if (datasize < sizeof(HFSPlusAttrForkData)) {
1582 printf("hfs: remove_attribute_records: bad record size %d (expecting %lu)\n", datasize, sizeof(HFSPlusAttrForkData));
1583 }
1584 totalblks = attrdata.forkData.theFork.totalBlocks;
1585
1586 /* Process the first 8 extents. */
1587 extentblks = count_extent_blocks(totalblks, attrdata.forkData.theFork.extents);
1588 if (extentblks > totalblks)
1589 panic("hfs: remove_attribute_records: corruption...");
1590 if (BTDeleteRecord(btfile, iterator) == 0) {
1591 free_attr_blks(hfsmp, extentblks, attrdata.forkData.theFork.extents);
1592 }
1593 totalblks -= extentblks;
1594 keystartblk = &((HFSPlusAttrKey *)&iterator->key)->startBlock;
1595
1596 /* Process any overflow extents. */
1597 while (totalblks) {
1598 *keystartblk += (u_int32_t)extentblks;
1599
1600 result = BTSearchRecord(btfile, iterator, &btdata, &datasize, NULL);
1601 if (result ||
1602 (attrdata.recordType != kHFSPlusAttrExtents) ||
1603 (datasize < sizeof(HFSPlusAttrExtents))) {
1604 printf("hfs: remove_attribute_records: BTSearchRecord %d (%d), totalblks %d\n",
1605 MacToVFSError(result), attrdata.recordType != kHFSPlusAttrExtents, totalblks);
1606 result = ENOATTR;
1607 break; /* break from while */
1608 }
1609 /* Process the next 8 extents. */
1610 extentblks = count_extent_blocks(totalblks, attrdata.overflowExtents.extents);
1611 if (extentblks > totalblks)
1612 panic("hfs: remove_attribute_records: corruption...");
1613 if (BTDeleteRecord(btfile, iterator) == 0) {
1614 free_attr_blks(hfsmp, extentblks, attrdata.overflowExtents.extents);
1615 }
1616 totalblks -= extentblks;
1617 }
1618 } else {
1619 result = BTDeleteRecord(btfile, iterator);
1620 }
1621 (void) BTFlushPath(btfile);
1622 exit:
1623 return (result == btNotFound ? ENOATTR : MacToVFSError(result));
1624 }
1625
1626
1627 /*
1628 * Retrieve the list of extended attribute names.
1629 */
1630 int
1631 hfs_vnop_listxattr(struct vnop_listxattr_args *ap)
1632 /*
1633 struct vnop_listxattr_args {
1634 struct vnodeop_desc *a_desc;
1635 vnode_t a_vp;
1636 uio_t a_uio;
1637 size_t *a_size;
1638 int a_options;
1639 vfs_context_t a_context;
1640 */
1641 {
1642 struct vnode *vp = ap->a_vp;
1643 struct cnode *cp = VTOC(vp);
1644 struct hfsmount *hfsmp;
1645 uio_t uio = ap->a_uio;
1646 struct BTreeIterator * iterator = NULL;
1647 struct filefork *btfile;
1648 struct listattr_callback_state state;
1649 user_addr_t user_start = 0;
1650 user_size_t user_len = 0;
1651 int lockflags;
1652 int result;
1653 u_int8_t finderinfo[32];
1654
1655
1656 if (VNODE_IS_RSRC(vp)) {
1657 return (EPERM);
1658 }
1659
1660 #if HFS_COMPRESSION
1661 int compressed = hfs_file_is_compressed(cp, 1); /* 1 == don't take the cnode lock */
1662 #endif /* HFS_COMPRESSION */
1663
1664 hfsmp = VTOHFS(vp);
1665 *ap->a_size = 0;
1666
1667 if ((result = hfs_lock(cp, HFS_SHARED_LOCK))) {
1668 return (result);
1669 }
1670
1671 /*
1672 * Make a copy of the cnode's finderinfo to a local so we can
1673 * zero out the date added field. Also zero out the private type/creator
1674 * for symlinks.
1675 */
1676 bcopy(cp->c_finderinfo, finderinfo, sizeof(finderinfo));
1677 hfs_zero_dateadded (cp, finderinfo);
1678
1679 /* Don't expose a symlink's private type/creator. */
1680 if (vnode_islnk(vp)) {
1681 struct FndrFileInfo *fip;
1682
1683 fip = (struct FndrFileInfo *)&finderinfo;
1684 fip->fdType = 0;
1685 fip->fdCreator = 0;
1686 }
1687
1688
1689 /* If Finder Info is non-empty then export it's name. */
1690 if (bcmp(finderinfo, emptyfinfo, sizeof(emptyfinfo)) != 0) {
1691 if (uio == NULL) {
1692 *ap->a_size += sizeof(XATTR_FINDERINFO_NAME);
1693 } else if ((user_size_t)uio_resid(uio) < sizeof(XATTR_FINDERINFO_NAME)) {
1694 result = ERANGE;
1695 goto exit;
1696 } else {
1697 result = uiomove(XATTR_FINDERINFO_NAME,
1698 sizeof(XATTR_FINDERINFO_NAME), uio);
1699 if (result)
1700 goto exit;
1701 }
1702 }
1703 /* If Resource Fork is non-empty then export it's name. */
1704 if (S_ISREG(cp->c_mode) && RSRC_FORK_EXISTS(cp)) {
1705 #if HFS_COMPRESSION
1706 if ((ap->a_options & XATTR_SHOWCOMPRESSION) ||
1707 !compressed ||
1708 !hfs_hides_rsrc(ap->a_context, VTOC(vp), 1) /* 1 == don't take the cnode lock */
1709 )
1710 #endif /* HFS_COMPRESSION */
1711 {
1712 if (uio == NULL) {
1713 *ap->a_size += sizeof(XATTR_RESOURCEFORK_NAME);
1714 } else if ((user_size_t)uio_resid(uio) < sizeof(XATTR_RESOURCEFORK_NAME)) {
1715 result = ERANGE;
1716 goto exit;
1717 } else {
1718 result = uiomove(XATTR_RESOURCEFORK_NAME,
1719 sizeof(XATTR_RESOURCEFORK_NAME), uio);
1720 if (result)
1721 goto exit;
1722 }
1723 }
1724 }
1725 /*
1726 * Standard HFS only supports native FinderInfo and Resource Forks.
1727 * Return at this point.
1728 */
1729 if (hfsmp->hfs_flags & HFS_STANDARD) {
1730 result = 0;
1731 goto exit;
1732 }
1733 /* Bail if we don't have any extended attributes. */
1734 if ((hfsmp->hfs_attribute_vp == NULL) ||
1735 (cp->c_attr.ca_recflags & kHFSHasAttributesMask) == 0) {
1736 result = 0;
1737 goto exit;
1738 }
1739 btfile = VTOF(hfsmp->hfs_attribute_vp);
1740
1741 MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
1742 if (iterator == NULL) {
1743 result = ENOMEM;
1744 goto exit;
1745 }
1746 bzero(iterator, sizeof(*iterator));
1747 result = hfs_buildattrkey(cp->c_fileid, NULL, (HFSPlusAttrKey *)&iterator->key);
1748 if (result)
1749 goto exit;
1750
1751 /*
1752 * Lock the user's buffer here so that we won't fault on
1753 * it in uiomove while holding the attributes b-tree lock.
1754 */
1755 if (uio && uio_isuserspace(uio)) {
1756 user_start = uio_curriovbase(uio);
1757 user_len = uio_curriovlen(uio);
1758
1759 if ((result = vslock(user_start, user_len)) != 0) {
1760 user_start = 0;
1761 goto exit;
1762 }
1763 }
1764 lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK);
1765
1766 result = BTSearchRecord(btfile, iterator, NULL, NULL, NULL);
1767 if (result && result != btNotFound) {
1768 hfs_systemfile_unlock(hfsmp, lockflags);
1769 goto exit;
1770 }
1771
1772 state.fileID = cp->c_fileid;
1773 state.result = 0;
1774 state.uio = uio;
1775 state.size = 0;
1776 #if HFS_COMPRESSION
1777 state.showcompressed = !compressed || ap->a_options & XATTR_SHOWCOMPRESSION;
1778 state.ctx = ap->a_context;
1779 state.vp = vp;
1780 #endif /* HFS_COMPRESSION */
1781
1782 /*
1783 * Process entries starting just after iterator->key.
1784 */
1785 result = BTIterateRecords(btfile, kBTreeNextRecord, iterator,
1786 (IterateCallBackProcPtr)listattr_callback, &state);
1787 hfs_systemfile_unlock(hfsmp, lockflags);
1788 if (uio == NULL) {
1789 *ap->a_size += state.size;
1790 }
1791
1792 if (state.result || result == btNotFound)
1793 result = state.result;
1794
1795 exit:
1796 if (user_start) {
1797 vsunlock(user_start, user_len, TRUE);
1798 }
1799 if (iterator) {
1800 FREE(iterator, M_TEMP);
1801 }
1802 hfs_unlock(cp);
1803
1804 return MacToVFSError(result);
1805 }
1806
1807
1808 /*
1809 * Callback - called for each attribute record
1810 */
1811 static int
1812 listattr_callback(const HFSPlusAttrKey *key, __unused const HFSPlusAttrData *data, struct listattr_callback_state *state)
1813 {
1814 char attrname[XATTR_MAXNAMELEN + 1];
1815 ssize_t bytecount;
1816 int result;
1817
1818 if (state->fileID != key->fileID) {
1819 state->result = 0;
1820 return (0); /* stop */
1821 }
1822 /*
1823 * Skip over non-primary keys
1824 */
1825 if (key->startBlock != 0) {
1826 return (1); /* continue */
1827 }
1828
1829 /* Convert the attribute name into UTF-8. */
1830 result = utf8_encodestr(key->attrName, key->attrNameLen * sizeof(UniChar),
1831 (u_int8_t *)attrname, (size_t *)&bytecount, sizeof(attrname), '/', 0);
1832 if (result) {
1833 state->result = result;
1834 return (0); /* stop */
1835 }
1836 bytecount++; /* account for null termination char */
1837
1838 if (xattr_protected(attrname))
1839 return (1); /* continue */
1840
1841 #if HFS_COMPRESSION
1842 if (!state->showcompressed && hfs_hides_xattr(state->ctx, VTOC(state->vp), attrname, 1) ) /* 1 == don't take the cnode lock */
1843 return 1; /* continue */
1844 #endif /* HFS_COMPRESSION */
1845
1846 if (state->uio == NULL) {
1847 state->size += bytecount;
1848 } else {
1849 if (bytecount > uio_resid(state->uio)) {
1850 state->result = ERANGE;
1851 return (0); /* stop */
1852 }
1853 result = uiomove((caddr_t) attrname, bytecount, state->uio);
1854 if (result) {
1855 state->result = result;
1856 return (0); /* stop */
1857 }
1858 }
1859 return (1); /* continue */
1860 }
1861
1862 /*
1863 * Remove all the attributes from a cnode.
1864 *
1865 * This function creates/ends its own transaction so that each
1866 * attribute is deleted in its own transaction (to avoid having
1867 * a transaction grow too large).
1868 *
1869 * This function takes the necessary locks on the attribute
1870 * b-tree file and the allocation (bitmap) file.
1871 */
1872 int
1873 hfs_removeallattr(struct hfsmount *hfsmp, u_int32_t fileid)
1874 {
1875 BTreeIterator *iterator = NULL;
1876 HFSPlusAttrKey *key;
1877 struct filefork *btfile;
1878 int result, lockflags;
1879
1880 if (hfsmp->hfs_attribute_vp == NULL) {
1881 return (0);
1882 }
1883 btfile = VTOF(hfsmp->hfs_attribute_vp);
1884
1885 MALLOC(iterator, BTreeIterator *, sizeof(BTreeIterator), M_TEMP, M_WAITOK);
1886 if (iterator == NULL) {
1887 return (ENOMEM);
1888 }
1889 bzero(iterator, sizeof(BTreeIterator));
1890 key = (HFSPlusAttrKey *)&iterator->key;
1891
1892 /* Loop until there are no more attributes for this file id */
1893 for(;;) {
1894 if (hfs_start_transaction(hfsmp) != 0) {
1895 result = EINVAL;
1896 goto exit;
1897 }
1898
1899 /* Lock the attribute b-tree and the allocation (bitmap) files */
1900 lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE | SFL_BITMAP, HFS_EXCLUSIVE_LOCK);
1901
1902 /*
1903 * Go to first possible attribute key/record pair
1904 */
1905 (void) hfs_buildattrkey(fileid, NULL, key);
1906 result = BTIterateRecord(btfile, kBTreeNextRecord, iterator, NULL, NULL);
1907 if (result || key->fileID != fileid) {
1908 hfs_systemfile_unlock(hfsmp, lockflags);
1909 hfs_end_transaction(hfsmp);
1910 goto exit;
1911 }
1912 result = remove_attribute_records(hfsmp, iterator);
1913
1914 #if HFS_XATTR_VERBOSE
1915 if (result) {
1916 printf("hfs_removeallattr: unexpected err %d\n", result);
1917 }
1918 #endif
1919 hfs_systemfile_unlock(hfsmp, lockflags);
1920 hfs_end_transaction(hfsmp);
1921 if (result)
1922 break;
1923 }
1924 exit:
1925 FREE(iterator, M_TEMP);
1926 return (result == btNotFound ? 0: MacToVFSError(result));
1927 }
1928
1929 __private_extern__
1930 void
1931 hfs_xattr_init(struct hfsmount * hfsmp)
1932 {
1933 /*
1934 * If there isn't an attributes b-tree then create one.
1935 */
1936 if (!(hfsmp->hfs_flags & HFS_STANDARD) &&
1937 (hfsmp->hfs_attribute_vp == NULL) &&
1938 !(hfsmp->hfs_flags & HFS_READ_ONLY)) {
1939 (void) hfs_create_attr_btree(hfsmp, ATTRIBUTE_FILE_NODE_SIZE,
1940 getnodecount(hfsmp, ATTRIBUTE_FILE_NODE_SIZE));
1941 }
1942 if (hfsmp->hfs_attribute_vp)
1943 hfsmp->hfs_max_inline_attrsize = getmaxinlineattrsize(hfsmp->hfs_attribute_vp);
1944 }
1945
1946 /*
1947 * Enable/Disable volume attributes stored as EA for root file system.
1948 * Supported attributes are -
1949 * 1. Extent-based Extended Attributes
1950 */
1951 int
1952 hfs_set_volxattr(struct hfsmount *hfsmp, unsigned int xattrtype, int state)
1953 {
1954 struct BTreeIterator * iterator = NULL;
1955 struct filefork *btfile;
1956 int lockflags;
1957 int result;
1958
1959 if (hfsmp->hfs_flags & HFS_STANDARD) {
1960 return (ENOTSUP);
1961 }
1962 if (xattrtype != HFS_SET_XATTREXTENTS_STATE) {
1963 return EINVAL;
1964 }
1965
1966 /*
1967 * If there isn't an attributes b-tree then create one.
1968 */
1969 if (hfsmp->hfs_attribute_vp == NULL) {
1970 result = hfs_create_attr_btree(hfsmp, ATTRIBUTE_FILE_NODE_SIZE,
1971 getnodecount(hfsmp, ATTRIBUTE_FILE_NODE_SIZE));
1972 if (result) {
1973 return (result);
1974 }
1975 }
1976
1977 MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
1978 if (iterator == NULL) {
1979 return (ENOMEM);
1980 }
1981 bzero(iterator, sizeof(*iterator));
1982
1983 /*
1984 * Build a b-tree key.
1985 * We use the root's parent id (1) to hold this volume attribute.
1986 */
1987 (void) hfs_buildattrkey(kHFSRootParentID, XATTR_XATTREXTENTS_NAME,
1988 (HFSPlusAttrKey *)&iterator->key);
1989
1990 /* Start a transaction for our changes. */
1991 if (hfs_start_transaction(hfsmp) != 0) {
1992 result = EINVAL;
1993 goto exit;
1994 }
1995 btfile = VTOF(hfsmp->hfs_attribute_vp);
1996
1997 lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_EXCLUSIVE_LOCK);
1998
1999 if (state == 0) {
2000 /* Remove the attribute. */
2001 result = BTDeleteRecord(btfile, iterator);
2002 if (result == btNotFound)
2003 result = 0;
2004 } else {
2005 FSBufferDescriptor btdata;
2006 HFSPlusAttrData attrdata;
2007 u_int16_t datasize;
2008
2009 datasize = sizeof(attrdata);
2010 btdata.bufferAddress = &attrdata;
2011 btdata.itemSize = datasize;
2012 btdata.itemCount = 1;
2013 attrdata.recordType = kHFSPlusAttrInlineData;
2014 attrdata.reserved[0] = 0;
2015 attrdata.reserved[1] = 0;
2016 attrdata.attrSize = 2;
2017 attrdata.attrData[0] = 0;
2018 attrdata.attrData[1] = 0;
2019
2020 /* Insert the attribute. */
2021 result = BTInsertRecord(btfile, iterator, &btdata, datasize);
2022 if (result == btExists)
2023 result = 0;
2024 }
2025 (void) BTFlushPath(btfile);
2026
2027 hfs_systemfile_unlock(hfsmp, lockflags);
2028
2029 /* Finish the transaction of our changes. */
2030 hfs_end_transaction(hfsmp);
2031
2032 /* Update the state in the mount point */
2033 HFS_MOUNT_LOCK(hfsmp, TRUE);
2034 if (state == 0) {
2035 hfsmp->hfs_flags &= ~HFS_XATTR_EXTENTS;
2036 } else {
2037 hfsmp->hfs_flags |= HFS_XATTR_EXTENTS;
2038 }
2039 HFS_MOUNT_UNLOCK(hfsmp, TRUE);
2040
2041 exit:
2042 if (iterator) {
2043 FREE(iterator, M_TEMP);
2044 }
2045 return MacToVFSError(result);
2046 }
2047
2048
2049 /*
2050 * hfs_attrkeycompare - compare two attribute b-tree keys.
2051 *
2052 * The name portion of the key is compared using a 16-bit binary comparison.
2053 * This is called from the b-tree code.
2054 */
2055 __private_extern__
2056 int
2057 hfs_attrkeycompare(HFSPlusAttrKey *searchKey, HFSPlusAttrKey *trialKey)
2058 {
2059 u_int32_t searchFileID, trialFileID;
2060 int result;
2061
2062 searchFileID = searchKey->fileID;
2063 trialFileID = trialKey->fileID;
2064 result = 0;
2065
2066 if (searchFileID > trialFileID) {
2067 ++result;
2068 } else if (searchFileID < trialFileID) {
2069 --result;
2070 } else {
2071 u_int16_t * str1 = &searchKey->attrName[0];
2072 u_int16_t * str2 = &trialKey->attrName[0];
2073 int length1 = searchKey->attrNameLen;
2074 int length2 = trialKey->attrNameLen;
2075 u_int16_t c1, c2;
2076 int length;
2077
2078 if (length1 < length2) {
2079 length = length1;
2080 --result;
2081 } else if (length1 > length2) {
2082 length = length2;
2083 ++result;
2084 } else {
2085 length = length1;
2086 }
2087
2088 while (length--) {
2089 c1 = *(str1++);
2090 c2 = *(str2++);
2091
2092 if (c1 > c2) {
2093 result = 1;
2094 break;
2095 }
2096 if (c1 < c2) {
2097 result = -1;
2098 break;
2099 }
2100 }
2101 if (result)
2102 return (result);
2103 /*
2104 * Names are equal; compare startBlock
2105 */
2106 if (searchKey->startBlock == trialKey->startBlock)
2107 return (0);
2108 else
2109 return (searchKey->startBlock < trialKey->startBlock ? -1 : 1);
2110 }
2111
2112 return result;
2113 }
2114
2115
2116 /*
2117 * hfs_buildattrkey - build an Attribute b-tree key
2118 */
2119 __private_extern__
2120 int
2121 hfs_buildattrkey(u_int32_t fileID, const char *attrname, HFSPlusAttrKey *key)
2122 {
2123 int result = 0;
2124 size_t unicodeBytes = 0;
2125
2126 if (attrname != NULL) {
2127 /*
2128 * Convert filename from UTF-8 into Unicode
2129 */
2130 result = utf8_decodestr((const u_int8_t *)attrname, strlen(attrname), key->attrName,
2131 &unicodeBytes, sizeof(key->attrName), 0, 0);
2132 if (result) {
2133 if (result != ENAMETOOLONG)
2134 result = EINVAL; /* name has invalid characters */
2135 return (result);
2136 }
2137 key->attrNameLen = unicodeBytes / sizeof(UniChar);
2138 key->keyLength = kHFSPlusAttrKeyMinimumLength + unicodeBytes;
2139 } else {
2140 key->attrNameLen = 0;
2141 key->keyLength = kHFSPlusAttrKeyMinimumLength;
2142 }
2143 key->pad = 0;
2144 key->fileID = fileID;
2145 key->startBlock = 0;
2146
2147 return (0);
2148 }
2149
2150 /*
2151 * getnodecount - calculate starting node count for attributes b-tree.
2152 */
2153 static int
2154 getnodecount(struct hfsmount *hfsmp, size_t nodesize)
2155 {
2156 u_int64_t freebytes;
2157 u_int64_t calcbytes;
2158
2159 /*
2160 * 10.4: Scale base on current catalog file size (20 %) up to 20 MB.
2161 * 10.5: Attempt to be as big as the catalog clump size.
2162 *
2163 * Use no more than 10 % of the remaining free space.
2164 */
2165 freebytes = (u_int64_t)hfs_freeblks(hfsmp, 0) * (u_int64_t)hfsmp->blockSize;
2166
2167 calcbytes = MIN(hfsmp->hfs_catalog_cp->c_datafork->ff_size / 5, 20 * 1024 * 1024);
2168
2169 calcbytes = MAX(calcbytes, hfsmp->hfs_catalog_cp->c_datafork->ff_clumpsize);
2170
2171 calcbytes = MIN(calcbytes, freebytes / 10);
2172
2173 return (MAX(2, (int)(calcbytes / nodesize)));
2174 }
2175
2176
2177 /*
2178 * getmaxinlineattrsize - calculate maximum inline attribute size.
2179 *
2180 * This yields 3,802 bytes for an 8K node size.
2181 */
2182 static size_t
2183 getmaxinlineattrsize(struct vnode * attrvp)
2184 {
2185 struct BTreeInfoRec btinfo;
2186 size_t nodesize = ATTRIBUTE_FILE_NODE_SIZE;
2187 size_t maxsize;
2188
2189 if (attrvp != NULL) {
2190 (void) hfs_lock(VTOC(attrvp), HFS_SHARED_LOCK);
2191 if (BTGetInformation(VTOF(attrvp), 0, &btinfo) == 0)
2192 nodesize = btinfo.nodeSize;
2193 hfs_unlock(VTOC(attrvp));
2194 }
2195 maxsize = nodesize;
2196 maxsize -= sizeof(BTNodeDescriptor); /* minus node descriptor */
2197 maxsize -= 3 * sizeof(u_int16_t); /* minus 3 index slots */
2198 maxsize /= 2; /* 2 key/rec pairs minumum */
2199 maxsize -= sizeof(HFSPlusAttrKey); /* minus maximum key size */
2200 maxsize -= sizeof(HFSPlusAttrData) - 2; /* minus data header */
2201 maxsize &= 0xFFFFFFFE; /* multiple of 2 bytes */
2202
2203 return (maxsize);
2204 }
2205
2206 /*
2207 * Initialize vnode for attribute data I/O.
2208 *
2209 * On success,
2210 * - returns zero
2211 * - the attrdata vnode is initialized as hfsmp->hfs_attrdata_vp
2212 * - an iocount is taken on the attrdata vnode which exists
2213 * for the entire duration of the mount. It is only dropped
2214 * during unmount
2215 * - the attrdata cnode is not locked
2216 *
2217 * On failure,
2218 * - returns non-zero value
2219 * - the caller does not have to worry about any locks or references
2220 */
2221 int init_attrdata_vnode(struct hfsmount *hfsmp)
2222 {
2223 vnode_t vp;
2224 int result = 0;
2225 struct cat_desc cat_desc;
2226 struct cat_attr cat_attr;
2227 struct cat_fork cat_fork;
2228 int newvnode_flags = 0;
2229
2230 bzero(&cat_desc, sizeof(cat_desc));
2231 cat_desc.cd_parentcnid = kHFSRootParentID;
2232 cat_desc.cd_nameptr = (const u_int8_t *)hfs_attrdatafilename;
2233 cat_desc.cd_namelen = strlen(hfs_attrdatafilename);
2234 cat_desc.cd_cnid = kHFSAttributeDataFileID;
2235 /* Tag vnode as system file, note that we can still use cluster I/O */
2236 cat_desc.cd_flags |= CD_ISMETA;
2237
2238 bzero(&cat_attr, sizeof(cat_attr));
2239 cat_attr.ca_linkcount = 1;
2240 cat_attr.ca_mode = S_IFREG;
2241 cat_attr.ca_fileid = cat_desc.cd_cnid;
2242 cat_attr.ca_blocks = hfsmp->totalBlocks;
2243
2244 /*
2245 * The attribute data file is a virtual file that spans the
2246 * entire file system space.
2247 *
2248 * Each extent-based attribute occupies a unique portion of
2249 * in this virtual file. The cluster I/O is done using actual
2250 * allocation block offsets so no additional mapping is needed
2251 * for the VNOP_BLOCKMAP call.
2252 *
2253 * This approach allows the attribute data to be cached without
2254 * incurring the high cost of using a separate vnode per attribute.
2255 *
2256 * Since we need to acquire the attribute b-tree file lock anyways,
2257 * the virtual file doesn't introduce any additional serialization.
2258 */
2259 bzero(&cat_fork, sizeof(cat_fork));
2260 cat_fork.cf_size = (u_int64_t)hfsmp->totalBlocks * (u_int64_t)hfsmp->blockSize;
2261 cat_fork.cf_blocks = hfsmp->totalBlocks;
2262 cat_fork.cf_extents[0].startBlock = 0;
2263 cat_fork.cf_extents[0].blockCount = cat_fork.cf_blocks;
2264
2265 result = hfs_getnewvnode(hfsmp, NULL, NULL, &cat_desc, 0, &cat_attr,
2266 &cat_fork, &vp, &newvnode_flags);
2267 if (result == 0) {
2268 hfsmp->hfs_attrdata_vp = vp;
2269 hfs_unlock(VTOC(vp));
2270 }
2271 return (result);
2272 }
2273
2274 /*
2275 * Read an extent based attribute.
2276 */
2277 static int
2278 read_attr_data(struct hfsmount *hfsmp, uio_t uio, size_t datasize, HFSPlusExtentDescriptor *extents)
2279 {
2280 vnode_t evp = hfsmp->hfs_attrdata_vp;
2281 int bufsize;
2282 int iosize;
2283 int attrsize;
2284 int blksize;
2285 int i;
2286 int result = 0;
2287
2288 hfs_lock_truncate(VTOC(evp), HFS_SHARED_LOCK);
2289
2290 bufsize = (int)uio_resid(uio);
2291 attrsize = (int)datasize;
2292 blksize = (int)hfsmp->blockSize;
2293
2294 /*
2295 * Read the attribute data one extent at a time.
2296 * For the typical case there is only one extent.
2297 */
2298 for (i = 0; (attrsize > 0) && (bufsize > 0) && (extents[i].startBlock != 0); ++i) {
2299 iosize = (int)extents[i].blockCount * blksize;
2300 iosize = MIN(iosize, attrsize);
2301 iosize = MIN(iosize, bufsize);
2302 uio_setresid(uio, iosize);
2303 uio_setoffset(uio, (u_int64_t)extents[i].startBlock * (u_int64_t)blksize);
2304
2305 result = cluster_read(evp, uio, VTOF(evp)->ff_size, IO_SYNC | IO_UNIT);
2306
2307 #if HFS_XATTR_VERBOSE
2308 printf("hfs: read_attr_data: cr iosize %d [%d, %d] (%d)\n",
2309 iosize, extents[i].startBlock, extents[i].blockCount, result);
2310 #endif
2311 if (result)
2312 break;
2313 attrsize -= iosize;
2314 bufsize -= iosize;
2315 }
2316 uio_setresid(uio, bufsize);
2317 uio_setoffset(uio, datasize);
2318
2319 hfs_unlock_truncate(VTOC(evp), 0);
2320 return (result);
2321 }
2322
2323 /*
2324 * Write an extent based attribute.
2325 */
2326 static int
2327 write_attr_data(struct hfsmount *hfsmp, uio_t uio, size_t datasize, HFSPlusExtentDescriptor *extents)
2328 {
2329 vnode_t evp = hfsmp->hfs_attrdata_vp;
2330 off_t filesize;
2331 int bufsize;
2332 int attrsize;
2333 int iosize;
2334 int blksize;
2335 int i;
2336 int result = 0;
2337
2338 hfs_lock_truncate(VTOC(evp), HFS_SHARED_LOCK);
2339
2340 bufsize = uio_resid(uio);
2341 attrsize = (int) datasize;
2342 blksize = (int) hfsmp->blockSize;
2343 filesize = VTOF(evp)->ff_size;
2344
2345 /*
2346 * Write the attribute data one extent at a time.
2347 */
2348 for (i = 0; (attrsize > 0) && (bufsize > 0) && (extents[i].startBlock != 0); ++i) {
2349 iosize = (int)extents[i].blockCount * blksize;
2350 iosize = MIN(iosize, attrsize);
2351 iosize = MIN(iosize, bufsize);
2352 uio_setresid(uio, iosize);
2353 uio_setoffset(uio, (u_int64_t)extents[i].startBlock * (u_int64_t)blksize);
2354
2355 result = cluster_write(evp, uio, filesize, filesize, filesize,
2356 (off_t) 0, IO_SYNC | IO_UNIT);
2357 #if HFS_XATTR_VERBOSE
2358 printf("hfs: write_attr_data: cw iosize %d [%d, %d] (%d)\n",
2359 iosize, extents[i].startBlock, extents[i].blockCount, result);
2360 #endif
2361 if (result)
2362 break;
2363 attrsize -= iosize;
2364 bufsize -= iosize;
2365 }
2366 uio_setresid(uio, bufsize);
2367 uio_setoffset(uio, datasize);
2368
2369 hfs_unlock_truncate(VTOC(evp), 0);
2370 return (result);
2371 }
2372
2373 /*
2374 * Allocate blocks for an extent based attribute.
2375 */
2376 static int
2377 alloc_attr_blks(struct hfsmount *hfsmp, size_t attrsize, size_t extentbufsize, HFSPlusExtentDescriptor *extents, int *blocks)
2378 {
2379 int blkcnt;
2380 int startblk;
2381 int lockflags;
2382 int i;
2383 int maxextents;
2384 int result = 0;
2385
2386 startblk = hfsmp->hfs_metazone_end;
2387 blkcnt = howmany(attrsize, hfsmp->blockSize);
2388 if (blkcnt > (int)hfs_freeblks(hfsmp, 0)) {
2389 return (ENOSPC);
2390 }
2391 *blocks = blkcnt;
2392 maxextents = extentbufsize / sizeof(HFSPlusExtentDescriptor);
2393
2394 lockflags = hfs_systemfile_lock(hfsmp, SFL_BITMAP, HFS_EXCLUSIVE_LOCK);
2395
2396 for (i = 0; (blkcnt > 0) && (i < maxextents); i++) {
2397 result = BlockAllocate(hfsmp, startblk, blkcnt, blkcnt, 0,
2398 &extents[i].startBlock, &extents[i].blockCount);
2399 #if HFS_XATTR_VERBOSE
2400 printf("hfs: alloc_attr_blks: BA blkcnt %d [%d, %d] (%d)\n",
2401 blkcnt, extents[i].startBlock, extents[i].blockCount, result);
2402 #endif
2403 if (result) {
2404 extents[i].startBlock = 0;
2405 extents[i].blockCount = 0;
2406 break;
2407 }
2408 blkcnt -= extents[i].blockCount;
2409 startblk = extents[i].startBlock + extents[i].blockCount;
2410 }
2411 /*
2412 * If it didn't fit in the extents buffer then bail.
2413 */
2414 if (blkcnt) {
2415 result = ENOSPC;
2416
2417 #if HFS_XATTR_VERBOSE
2418 printf("hfs: alloc_attr_blks: unexpected failure, %d blocks unallocated\n", blkcnt);
2419 #endif
2420 for (; i >= 0; i--) {
2421 if ((blkcnt = extents[i].blockCount) != 0) {
2422 (void) BlockDeallocate(hfsmp, extents[i].startBlock, blkcnt, 0);
2423 extents[i].startBlock = 0;
2424 extents[i].blockCount = 0;
2425 }
2426 }
2427 }
2428
2429 hfs_systemfile_unlock(hfsmp, lockflags);
2430 return MacToVFSError(result);
2431 }
2432
2433 /*
2434 * Release blocks from an extent based attribute.
2435 */
2436 static void
2437 free_attr_blks(struct hfsmount *hfsmp, int blkcnt, HFSPlusExtentDescriptor *extents)
2438 {
2439 vnode_t evp = hfsmp->hfs_attrdata_vp;
2440 int remblks = blkcnt;
2441 int lockflags;
2442 int i;
2443
2444 lockflags = hfs_systemfile_lock(hfsmp, SFL_BITMAP, HFS_EXCLUSIVE_LOCK);
2445
2446 for (i = 0; (remblks > 0) && (extents[i].blockCount != 0); i++) {
2447 if (extents[i].blockCount > (u_int32_t)blkcnt) {
2448 #if HFS_XATTR_VERBOSE
2449 printf("hfs: free_attr_blks: skipping bad extent [%d, %d]\n",
2450 extents[i].startBlock, extents[i].blockCount);
2451 #endif
2452 extents[i].blockCount = 0;
2453 continue;
2454 }
2455 if (extents[i].startBlock == 0) {
2456 break;
2457 }
2458 (void)BlockDeallocate(hfsmp, extents[i].startBlock, extents[i].blockCount, 0);
2459 remblks -= extents[i].blockCount;
2460 extents[i].startBlock = 0;
2461 extents[i].blockCount = 0;
2462
2463 #if HFS_XATTR_VERBOSE
2464 printf("hfs: free_attr_blks: BlockDeallocate [%d, %d]\n",
2465 extents[i].startBlock, extents[i].blockCount);
2466 #endif
2467 /* Discard any resident pages for this block range. */
2468 if (evp) {
2469 off_t start, end;
2470
2471 start = (u_int64_t)extents[i].startBlock * (u_int64_t)hfsmp->blockSize;
2472 end = start + (u_int64_t)extents[i].blockCount * (u_int64_t)hfsmp->blockSize;
2473 (void) ubc_msync(hfsmp->hfs_attrdata_vp, start, end, &start, UBC_INVALIDATE);
2474 }
2475 }
2476
2477 hfs_systemfile_unlock(hfsmp, lockflags);
2478 }
2479
2480 static int
2481 has_overflow_extents(HFSPlusForkData *forkdata)
2482 {
2483 u_int32_t blocks;
2484
2485 if (forkdata->extents[7].blockCount == 0)
2486 return (0);
2487
2488 blocks = forkdata->extents[0].blockCount +
2489 forkdata->extents[1].blockCount +
2490 forkdata->extents[2].blockCount +
2491 forkdata->extents[3].blockCount +
2492 forkdata->extents[4].blockCount +
2493 forkdata->extents[5].blockCount +
2494 forkdata->extents[6].blockCount +
2495 forkdata->extents[7].blockCount;
2496
2497 return (forkdata->totalBlocks > blocks);
2498 }
2499
2500 static int
2501 count_extent_blocks(int maxblks, HFSPlusExtentRecord extents)
2502 {
2503 int blocks;
2504 int i;
2505
2506 for (i = 0, blocks = 0; i < kHFSPlusExtentDensity; ++i) {
2507 /* Ignore obvious bogus extents. */
2508 if (extents[i].blockCount > (u_int32_t)maxblks)
2509 continue;
2510 if (extents[i].startBlock == 0 || extents[i].blockCount == 0)
2511 break;
2512 blocks += extents[i].blockCount;
2513 }
2514 return (blocks);
2515 }