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. */
58struct 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
81static u_int32_t emptyfinfo[8] = {0};
82
83static int hfs_zero_dateadded (struct cnode *cp, u_int8_t *finderinfo);
84
85const char hfs_attrdatafilename[] = "Attribute Data";
86
87static int  listattr_callback(const HFSPlusAttrKey *key, const HFSPlusAttrData *data,
88                       struct listattr_callback_state *state);
89
90static int  remove_attribute_records(struct hfsmount *hfsmp, BTreeIterator * iterator);
91
92static int  getnodecount(struct hfsmount *hfsmp, size_t nodesize);
93
94static size_t  getmaxinlineattrsize(struct vnode * attrvp);
95
96static int  read_attr_data(struct hfsmount *hfsmp, uio_t uio, size_t datasize, HFSPlusExtentDescriptor *extents);
97
98static int  write_attr_data(struct hfsmount *hfsmp, uio_t uio, size_t datasize, HFSPlusExtentDescriptor *extents);
99
100static int  alloc_attr_blks(struct hfsmount *hfsmp, size_t attrsize, size_t extentbufsize, HFSPlusExtentDescriptor *extents, int *blocks);
101
102static void  free_attr_blks(struct hfsmount *hfsmp, int blkcnt, HFSPlusExtentDescriptor *extents);
103
104static int  has_overflow_extents(HFSPlusForkData *forkdata);
105
106static int  count_extent_blocks(int maxblks, HFSPlusExtentRecord extents);
107
108#if NAMEDSTREAMS
109/*
110 * Obtain the vnode for a stream.
111 */
112int
113hfs_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 */
155int
156hfs_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 */
198int
199hfs_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);
229out:
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 */
237static 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 */
262int
263hfs_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
440int 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
631exit:
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 */
647int
648hfs_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 */
939int 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
1200exit:
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 */
1247int
1248hfs_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);
1473exit:
1474	hfs_unlock(cp);
1475exit_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 */
1492int
1493file_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
1535out:
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 */
1552int
1553remove_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);
1622exit:
1623	return (result == btNotFound ? ENOATTR :  MacToVFSError(result));
1624}
1625
1626
1627/*
1628 * Retrieve the list of extended attribute names.
1629 */
1630int
1631hfs_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
1795exit:
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 */
1811static int
1812listattr_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 */
1872int
1873hfs_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	}
1924exit:
1925	FREE(iterator, M_TEMP);
1926	return (result == btNotFound ? 0: MacToVFSError(result));
1927}
1928
1929__private_extern__
1930void
1931hfs_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 */
1951int
1952hfs_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
2041exit:
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__
2056int
2057hfs_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__
2120int
2121hfs_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 */
2153static int
2154getnodecount(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 */
2182static size_t
2183getmaxinlineattrsize(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 */
2221int 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 */
2277static int
2278read_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 */
2326static int
2327write_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 */
2376static int
2377alloc_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 */
2436static void
2437free_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
2480static int
2481has_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
2500static int
2501count_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}
2516