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