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