X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/743b15655a24ee3fe9f458f383003e011db0558f..e2d2fc5c71f7d145cba7267989251af45e3bb5ba:/bsd/hfs/hfs_xattr.c diff --git a/bsd/hfs/hfs_xattr.c b/bsd/hfs/hfs_xattr.c index 1e2a11550..8091dfaa2 100644 --- a/bsd/hfs/hfs_xattr.c +++ b/bsd/hfs/hfs_xattr.c @@ -1,41 +1,55 @@ /* - * Copyright (c) 2004-2005 Apple Computer, Inc. All rights reserved. + * Copyright (c) 2004-2009 Apple Inc. All rights reserved. * - * @APPLE_LICENSE_HEADER_START@ + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * - * The contents of this file constitute Original Code as defined in and - * are subject to the Apple Public Source License Version 1.1 (the - * "License"). You may not use this file except in compliance with the - * License. Please obtain a copy of the License at - * http://www.apple.com/publicsource and read it before using this file. + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. * - * This Original Code and all software distributed under the License are - * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License. + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. * - * @APPLE_LICENSE_HEADER_END@ + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ #include #include #include #include +#include #include #include #include +#include +#include +#include +#include #include "hfs.h" #include "hfs_cnode.h" #include "hfs_mount.h" #include "hfs_format.h" #include "hfs_endian.h" +#include "hfs_btreeio.h" +#include "hfs_fsctl.h" #include "hfscommon/headers/BTreesInternal.h" +#define HFS_XATTR_VERBOSE 0 #define ATTRIBUTE_FILE_NODE_SIZE 8192 @@ -46,44 +60,205 @@ struct listattr_callback_state { int result; uio_t uio; size_t size; +#if HFS_COMPRESSION + int showcompressed; + vfs_context_t ctx; + vnode_t vp; +#endif /* HFS_COMPRESSION */ }; -#define HFS_MAXATTRIBUTESIZE (1024*1024) +#define HFS_MAXATTRBLKS (32 * 1024) + /* HFS Internal Names */ #define XATTR_EXTENDEDSECURITY_NAME "system.extendedsecurity" +#define XATTR_XATTREXTENTS_NAME "system.xattrextents" - -#define RESOURCE_FORK_EXISTS(VP) \ - ((VTOC((VP))->c_blocks - VTOF((VP))->ff_blocks) > 0) +/* Faster version if we already know this is the data fork. */ +#define RSRC_FORK_EXISTS(CP) \ + (((CP)->c_attr.ca_blocks - (CP)->c_datafork->ff_data.cf_blocks) > 0) static u_int32_t emptyfinfo[8] = {0}; +static int hfs_zero_dateadded (struct cnode *cp, u_int8_t *finderinfo); -extern int hfs_create_attr_btree(struct hfsmount *hfsmp, uint32_t nodesize, uint32_t nodecnt); - - -int hfs_vnop_getxattr(struct vnop_getxattr_args *ap); -int hfs_vnop_setxattr(struct vnop_setxattr_args *ap); -int hfs_vnop_removexattr(struct vnop_removexattr_args *ap); -int hfs_vnop_listxattr(struct vnop_listxattr_args *ap); -int hfs_attrkeycompare(HFSPlusAttrKey *searchKey, HFSPlusAttrKey *trialKey); - - +const char hfs_attrdatafilename[] = "Attribute Data"; static int listattr_callback(const HFSPlusAttrKey *key, const HFSPlusAttrData *data, struct listattr_callback_state *state); -static int buildkey(u_int32_t fileID, const char *attrname, HFSPlusAttrKey *key); +static int remove_attribute_records(struct hfsmount *hfsmp, BTreeIterator * iterator); static int getnodecount(struct hfsmount *hfsmp, size_t nodesize); static size_t getmaxinlineattrsize(struct vnode * attrvp); +static int read_attr_data(struct hfsmount *hfsmp, uio_t uio, size_t datasize, HFSPlusExtentDescriptor *extents); + +static int write_attr_data(struct hfsmount *hfsmp, uio_t uio, size_t datasize, HFSPlusExtentDescriptor *extents); + +static int alloc_attr_blks(struct hfsmount *hfsmp, size_t attrsize, size_t extentbufsize, HFSPlusExtentDescriptor *extents, int *blocks); + +static void free_attr_blks(struct hfsmount *hfsmp, int blkcnt, HFSPlusExtentDescriptor *extents); + +static int has_overflow_extents(HFSPlusForkData *forkdata); + +static int count_extent_blocks(int maxblks, HFSPlusExtentRecord extents); + +#if NAMEDSTREAMS +/* + * Obtain the vnode for a stream. + */ +int +hfs_vnop_getnamedstream(struct vnop_getnamedstream_args* ap) +{ + vnode_t vp = ap->a_vp; + vnode_t *svpp = ap->a_svpp; + struct cnode *cp; + int error = 0; + + *svpp = NULL; + + /* + * We only support the "com.apple.ResourceFork" stream. + */ + if (bcmp(ap->a_name, XATTR_RESOURCEFORK_NAME, sizeof(XATTR_RESOURCEFORK_NAME)) != 0) { + return (ENOATTR); + } + cp = VTOC(vp); + if ( !S_ISREG(cp->c_mode) ) { + return (EPERM); + } +#if HFS_COMPRESSION + int hide_rsrc = hfs_hides_rsrc(ap->a_context, VTOC(vp), 1); +#endif /* HFS_COMPRESSION */ + if ((error = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK))) { + return (error); + } + if ((!RSRC_FORK_EXISTS(cp) +#if HFS_COMPRESSION + || hide_rsrc +#endif /* HFS_COMPRESSION */ + ) && (ap->a_operation != NS_OPEN)) { + hfs_unlock(cp); + return (ENOATTR); + } + error = hfs_vgetrsrc(VTOHFS(vp), vp, svpp, TRUE, FALSE); + hfs_unlock(cp); + + return (error); +} + +/* + * Create a stream. + */ +int +hfs_vnop_makenamedstream(struct vnop_makenamedstream_args* ap) +{ + vnode_t vp = ap->a_vp; + vnode_t *svpp = ap->a_svpp; + struct cnode *cp; + int error = 0; + + *svpp = NULL; + + /* + * We only support the "com.apple.ResourceFork" stream. + */ + if (bcmp(ap->a_name, XATTR_RESOURCEFORK_NAME, sizeof(XATTR_RESOURCEFORK_NAME)) != 0) { + return (ENOATTR); + } + cp = VTOC(vp); + if ( !S_ISREG(cp->c_mode) ) { + return (EPERM); + } +#if HFS_COMPRESSION + if (hfs_hides_rsrc(ap->a_context, VTOC(vp), 1)) { + if (VNODE_IS_RSRC(vp)) { + return EINVAL; + } else { + error = decmpfs_decompress_file(vp, VTOCMP(vp), -1, 1, 0); + if (error != 0) + return error; + } + } +#endif /* HFS_COMPRESSION */ + if ((error = hfs_lock(cp, HFS_EXCLUSIVE_LOCK))) { + return (error); + } + error = hfs_vgetrsrc(VTOHFS(vp), vp, svpp, TRUE, FALSE); + hfs_unlock(cp); + + return (error); +} + +/* + * Remove a stream. + */ +int +hfs_vnop_removenamedstream(struct vnop_removenamedstream_args* ap) +{ + vnode_t svp = ap->a_svp; + struct cnode *scp; + int error = 0; + + /* + * We only support the "com.apple.ResourceFork" stream. + */ + if (bcmp(ap->a_name, XATTR_RESOURCEFORK_NAME, sizeof(XATTR_RESOURCEFORK_NAME)) != 0) { + return (ENOATTR); + } +#if HFS_COMPRESSION + if (hfs_hides_rsrc(ap->a_context, VTOC(svp), 1)) { + /* do nothing */ + return 0; + } +#endif /* HFS_COMPRESSION */ + + scp = VTOC(svp); + + /* Take truncate lock before taking cnode lock. */ + hfs_lock_truncate(scp, HFS_EXCLUSIVE_LOCK); + if ((error = hfs_lock(scp, HFS_EXCLUSIVE_LOCK))) { + goto out; + } + if (VTOF(svp)->ff_size != 0) { + error = hfs_truncate(svp, 0, IO_NDELAY, 0, 0, ap->a_context); + } + hfs_unlock(scp); +out: + hfs_unlock_truncate(scp, 0); + return (error); +} +#endif + +/* Zero out the date added field for the specified cnode */ +static int hfs_zero_dateadded (struct cnode *cp, u_int8_t *finderinfo) { + u_int8_t *finfo = finderinfo; + + /* Advance finfo by 16 bytes to the 2nd half of the finderinfo */ + finfo = finfo + 16; + + if (S_ISREG(cp->c_attr.ca_mode)) { + struct FndrExtendedFileInfo *extinfo = (struct FndrExtendedFileInfo *)finfo; + extinfo->date_added = 0; + } + else if (S_ISDIR(cp->c_attr.ca_mode)) { + struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)finfo; + extinfo->date_added = 0; + } + else { + /* Return an error */ + return -1; + } + return 0; + +} + + /* * Retrieve the data of an extended attribute. */ -__private_extern__ int hfs_vnop_getxattr(struct vnop_getxattr_args *ap) /* @@ -99,128 +274,376 @@ hfs_vnop_getxattr(struct vnop_getxattr_args *ap) */ { struct vnode *vp = ap->a_vp; + struct cnode *cp; struct hfsmount *hfsmp; uio_t uio = ap->a_uio; - struct BTreeIterator * iterator = NULL; - struct filefork *btfile; - FSBufferDescriptor btdata; - HFSPlusAttrData * datap = NULL; size_t bufsize; - UInt16 datasize; - int lockflags; int result; - if (ap->a_name == NULL || ap->a_name[0] == '\0') { - return (EINVAL); /* invalid name */ - } - hfsmp = VTOHFS(vp); - - if (!VNODE_IS_RSRC(vp)) { + cp = VTOC(vp); + if (vp == cp->c_vp) { +#if HFS_COMPRESSION + int decmpfs_hide = hfs_hides_xattr(ap->a_context, VTOC(vp), ap->a_name, 1); /* 1 == don't take the cnode lock */ + if (decmpfs_hide && !(ap->a_options & XATTR_SHOWCOMPRESSION)) + return ENOATTR; +#endif /* HFS_COMPRESSION */ + /* Get the Finder Info. */ if (bcmp(ap->a_name, XATTR_FINDERINFO_NAME, sizeof(XATTR_FINDERINFO_NAME)) == 0) { + u_int8_t finderinfo[32]; bufsize = 32; + if ((result = hfs_lock(cp, HFS_SHARED_LOCK))) { + return (result); + } + /* Make a copy since we may not export all of it. */ + bcopy(cp->c_finderinfo, finderinfo, sizeof(finderinfo)); + hfs_unlock(cp); + + /* Zero out the date added field in the local copy */ + hfs_zero_dateadded (cp, finderinfo); + + /* Don't expose a symlink's private type/creator. */ + if (vnode_islnk(vp)) { + struct FndrFileInfo *fip; + + fip = (struct FndrFileInfo *)&finderinfo; + fip->fdType = 0; + fip->fdCreator = 0; + } /* If Finder Info is empty then it doesn't exist. */ - if (bcmp(VTOC(vp)->c_finderinfo, emptyfinfo, sizeof(emptyfinfo)) == 0) { + if (bcmp(finderinfo, emptyfinfo, sizeof(emptyfinfo)) == 0) { return (ENOATTR); } if (uio == NULL) { *ap->a_size = bufsize; return (0); } - if (uio_resid(uio) < bufsize) + if ((user_size_t)uio_resid(uio) < bufsize) return (ERANGE); - result = uiomove((caddr_t) &VTOC(vp)->c_finderinfo , bufsize, uio); + result = uiomove((caddr_t)&finderinfo , bufsize, uio); return (result); } /* Read the Resource Fork. */ if (bcmp(ap->a_name, XATTR_RESOURCEFORK_NAME, sizeof(XATTR_RESOURCEFORK_NAME)) == 0) { struct vnode *rvp = NULL; + int openunlinked = 0; + int namelen = 0; - if ( !vnode_isreg(vp) ) { + if ( !S_ISREG(cp->c_mode) ) { return (EPERM); } - if ( !RESOURCE_FORK_EXISTS(vp)) { + if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK))) { + return (result); + } + namelen = cp->c_desc.cd_namelen; + + if ( !RSRC_FORK_EXISTS(cp)) { + hfs_unlock(cp); return (ENOATTR); } - if ((result = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK))) { - return (result); + hfsmp = VTOHFS(vp); + if ((cp->c_flag & C_DELETED) && (namelen == 0)) { + openunlinked = 1; } - result = hfs_vgetrsrc(hfsmp, vp, &rvp, vfs_context_proc(ap->a_context)); - hfs_unlock(VTOC(vp)); + + result = hfs_vgetrsrc(hfsmp, vp, &rvp, TRUE, FALSE); + hfs_unlock(cp); if (result) { return (result); } if (uio == NULL) { *ap->a_size = (size_t)VTOF(rvp)->ff_size; } else { +#if HFS_COMPRESSION + user_ssize_t uio_size = 0; + if (decmpfs_hide) + uio_size = uio_resid(uio); +#endif /* HFS_COMPRESSION */ result = VNOP_READ(rvp, uio, 0, ap->a_context); +#if HFS_COMPRESSION + if (decmpfs_hide && + (result == 0) && + (uio_resid(uio) == uio_size)) { + /* + * We intentionally make the above call to VNOP_READ so that + * it can return an authorization/permission/etc. Error + * based on ap->a_context and thus deny this operation; + * in that case, result != 0 and we won't proceed. + * + * However, if result == 0, it will have returned no data + * because hfs_vnop_read hid the resource fork + * (hence uio_resid(uio) == uio_size, i.e. the uio is untouched) + * + * In that case, we try again with the decmpfs_ctx context + * to get the actual data + */ + result = VNOP_READ(rvp, uio, 0, decmpfs_ctx); + } +#endif /* HFS_COMPRESSION */ + } + /* force the rsrc fork vnode to recycle right away */ + if (openunlinked) { + int vref; + vref = vnode_ref (rvp); + if (vref == 0) { + vnode_rele (rvp); + } + vnode_recycle(rvp); } vnode_put(rvp); return (result); } } + hfsmp = VTOHFS(vp); /* * Standard HFS only supports native FinderInfo and Resource Forks. */ if (hfsmp->hfs_flags & HFS_STANDARD) { return (EPERM); } - /* Bail if we don't have any extended attributes. */ + + if ((result = hfs_lock(cp, HFS_SHARED_LOCK))) { + return (result); + } + + /* Check for non-rsrc, non-finderinfo EAs */ + result = hfs_getxattr_internal (cp, ap, VTOHFS(cp->c_vp), 0); + + hfs_unlock(cp); + + return MacToVFSError(result); +} + + + +/* + * getxattr_internal + * + * We break out this internal function which searches the attributes B-Tree and the + * overflow extents file to find non-resource, non-finderinfo EAs. There may be cases + * where we need to get EAs in contexts where we are already holding the cnode lock, + * and to re-enter hfs_vnop_getxattr would cause us to double-lock the cnode. Instead, + * we can just directly call this function. + * + * We pass the hfsmp argument directly here because we may not necessarily have a cnode to + * operate on. Under normal conditions, we have a file or directory to query, but if we + * are operating on the root directory (id 1), then we may not have a cnode. In this case, if hte + * 'cp' argument is NULL, then we need to use the 'fileid' argument as the entry to manipulate + * + * NOTE: This function assumes the cnode lock for 'cp' is held exclusive or shared. + */ + + +int hfs_getxattr_internal (struct cnode *cp, struct vnop_getxattr_args *ap, + struct hfsmount *hfsmp, u_int32_t fileid) { + + struct filefork *btfile; + struct BTreeIterator * iterator = NULL; + size_t bufsize = 0; + HFSPlusAttrRecord *recp = NULL; + FSBufferDescriptor btdata; + int lockflags = 0; + int result = 0; + u_int16_t datasize = 0; + uio_t uio = ap->a_uio; + u_int32_t target_id = 0; + + if (cp) { + target_id = cp->c_fileid; + } + else { + target_id = fileid; + } + + + /* Bail if we don't have an EA B-Tree. */ if ((hfsmp->hfs_attribute_vp == NULL) || - (VTOC(vp)->c_attr.ca_recflags & kHFSHasAttributesMask) == 0) { - return (ENOATTR); + ((cp) && (cp->c_attr.ca_recflags & kHFSHasAttributesMask) == 0)) { + result = ENOATTR; + goto exit; } + + /* Initialize the B-Tree iterator for searching for the proper EA */ btfile = VTOF(hfsmp->hfs_attribute_vp); - + MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK); + if (iterator == NULL) { + result = ENOMEM; + goto exit; + } bzero(iterator, sizeof(*iterator)); - + bufsize = sizeof(HFSPlusAttrData) - 2; - if (uio) + if (uio) { bufsize += uio_resid(uio); - MALLOC(datap, HFSPlusAttrData *, bufsize, M_TEMP, M_WAITOK); - btdata.bufferAddress = datap; + } + bufsize = MAX(bufsize, sizeof(HFSPlusAttrRecord)); + MALLOC(recp, HFSPlusAttrRecord *, bufsize, M_TEMP, M_WAITOK); + if (recp == NULL) { + result = ENOMEM; + goto exit; + } + btdata.bufferAddress = recp; btdata.itemSize = bufsize; btdata.itemCount = 1; + + result = hfs_buildattrkey(target_id, ap->a_name, (HFSPlusAttrKey *)&iterator->key); + if (result) { + goto exit; + } - result = buildkey(VTOC(vp)->c_fileid, ap->a_name, (HFSPlusAttrKey *)&iterator->key); - if (result) - goto exit; - - /* Lookup the attribute. */ + /* Lookup the attribute in the Attribute B-Tree */ lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK); result = BTSearchRecord(btfile, iterator, &btdata, &datasize, NULL); hfs_systemfile_unlock(hfsmp, lockflags); - + if (result) { - if (result == btNotFound) + if (result == btNotFound) { result = ENOATTR; + } goto exit; } - - *ap->a_size = datap->attrSize; - - /* Copy out the attribute data. */ - if (uio) { - if (datap->attrSize > uio_resid(uio)) - result = ERANGE; - else - result = uiomove((caddr_t) &datap->attrData , datap->attrSize, uio); + + /* + * Operate differently if we have inline EAs that can fit in the attribute B-Tree or if + * we have extent based EAs. + */ + switch (recp->recordType) { + /* Attribute fits in the Attribute B-Tree */ + case kHFSPlusAttrInlineData: + /* + * Sanity check record size. It's not required to have any + * user data, so the minimum size is 2 bytes less that the + * size of HFSPlusAttrData (since HFSPlusAttrData struct + * has 2 bytes set aside for attribute data). + */ + if (datasize < (sizeof(HFSPlusAttrData) - 2)) { + printf("hfs_getxattr: %d,%s invalid record size %d (expecting %lu)\n", + target_id, ap->a_name, datasize, sizeof(HFSPlusAttrData)); + result = ENOATTR; + break; + } + *ap->a_size = recp->attrData.attrSize; + if (uio && recp->attrData.attrSize != 0) { + if (*ap->a_size > (user_size_t)uio_resid(uio)) { + result = ERANGE; + } + else { + result = uiomove((caddr_t) &recp->attrData.attrData , recp->attrData.attrSize, uio); + } + } + break; + /* Extent-Based EAs */ + case kHFSPlusAttrForkData: { + if (datasize < sizeof(HFSPlusAttrForkData)) { + printf("hfs_getxattr: %d,%s invalid record size %d (expecting %lu)\n", + target_id, ap->a_name, datasize, sizeof(HFSPlusAttrForkData)); + result = ENOATTR; + break; + } + *ap->a_size = recp->forkData.theFork.logicalSize; + if (uio == NULL) { + break; + } + if (*ap->a_size > (user_size_t)uio_resid(uio)) { + result = ERANGE; + break; + } + /* Process overflow extents if necessary. */ + if (has_overflow_extents(&recp->forkData.theFork)) { + HFSPlusExtentDescriptor *extentbuf; + HFSPlusExtentDescriptor *extentptr; + size_t extentbufsize; + u_int32_t totalblocks; + u_int32_t blkcnt; + u_int32_t attrlen; + + totalblocks = recp->forkData.theFork.totalBlocks; + /* Ignore bogus block counts. */ + if (totalblocks > HFS_MAXATTRBLKS) { + result = ERANGE; + break; + } + attrlen = recp->forkData.theFork.logicalSize; + + /* Get a buffer to hold the worst case amount of extents. */ + extentbufsize = totalblocks * sizeof(HFSPlusExtentDescriptor); + extentbufsize = roundup(extentbufsize, sizeof(HFSPlusExtentRecord)); + MALLOC(extentbuf, HFSPlusExtentDescriptor *, extentbufsize, M_TEMP, M_WAITOK); + if (extentbuf == NULL) { + result = ENOMEM; + break; + } + bzero(extentbuf, extentbufsize); + extentptr = extentbuf; + + /* Grab the first 8 extents. */ + bcopy(&recp->forkData.theFork.extents[0], extentptr, sizeof(HFSPlusExtentRecord)); + extentptr += kHFSPlusExtentDensity; + blkcnt = count_extent_blocks(totalblocks, recp->forkData.theFork.extents); + + /* Now lookup the overflow extents. */ + lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK); + while (blkcnt < totalblocks) { + ((HFSPlusAttrKey *)&iterator->key)->startBlock = blkcnt; + result = BTSearchRecord(btfile, iterator, &btdata, &datasize, NULL); + if (result || + (recp->recordType != kHFSPlusAttrExtents) || + (datasize < sizeof(HFSPlusAttrExtents))) { + printf("hfs_getxattr: %s missing extents, only %d blks of %d found\n", + ap->a_name, blkcnt, totalblocks); + result = ENOATTR; + break; /* break from while */ + } + /* Grab the next 8 extents. */ + bcopy(&recp->overflowExtents.extents[0], extentptr, sizeof(HFSPlusExtentRecord)); + extentptr += kHFSPlusExtentDensity; + blkcnt += count_extent_blocks(totalblocks, recp->overflowExtents.extents); + } + + /* Release Attr B-Tree lock */ + hfs_systemfile_unlock(hfsmp, lockflags); + + if (blkcnt < totalblocks) { + result = ENOATTR; + } + else { + result = read_attr_data(hfsmp, uio, attrlen, extentbuf); + } + FREE(extentbuf, M_TEMP); + + } + else /* No overflow extents. */ { + result = read_attr_data(hfsmp, uio, recp->forkData.theFork.logicalSize, recp->forkData.theFork.extents); + } + break; + } + + default: + /* We only support Extent or inline EAs. Default to ENOATTR for anything else */ + result = ENOATTR; + break; } -exit: - FREE(datap, M_TEMP); - FREE(iterator, M_TEMP); - - return MacToVFSError(result); + +exit: + if (iterator) { + FREE(iterator, M_TEMP); + } + if (recp) { + FREE(recp, M_TEMP); + } + + return result; + } + /* * Set the data of an extended attribute. */ -__private_extern__ int hfs_vnop_setxattr(struct vnop_setxattr_args *ap) /* @@ -235,16 +658,13 @@ hfs_vnop_setxattr(struct vnop_setxattr_args *ap) */ { struct vnode *vp = ap->a_vp; + struct cnode *cp = NULL; struct hfsmount *hfsmp; uio_t uio = ap->a_uio; - struct BTreeIterator * iterator = NULL; - struct filefork *btfile; size_t attrsize; - FSBufferDescriptor btdata; - HFSPlusAttrData * datap = NULL; - UInt16 datasize; - int lockflags; + void * user_data_ptr = NULL; int result; + time_t orig_ctime=VTOC(vp)->c_ctime; if (ap->a_name == NULL || ap->a_name[0] == '\0') { return (EINVAL); /* invalid name */ @@ -253,59 +673,194 @@ hfs_vnop_setxattr(struct vnop_setxattr_args *ap) if (VNODE_IS_RSRC(vp)) { return (EPERM); } + +#if HFS_COMPRESSION + if (hfs_hides_xattr(ap->a_context, VTOC(vp), ap->a_name, 1) ) { /* 1 == don't take the cnode lock */ + result = decmpfs_decompress_file(vp, VTOCMP(vp), -1, 1, 0); + if (result != 0) + return result; + } + + check_for_tracked_file(vp, orig_ctime, NAMESPACE_HANDLER_METADATA_WRITE_OP, NULL); +#endif /* HFS_COMPRESSION */ + /* Set the Finder Info. */ if (bcmp(ap->a_name, XATTR_FINDERINFO_NAME, sizeof(XATTR_FINDERINFO_NAME)) == 0) { - attrsize = 32; + u_int8_t finderinfo[32]; + struct FndrFileInfo *fip; + void * finderinfo_start; + u_int8_t *finfo = NULL; + u_int16_t fdFlags; + u_int32_t dateadded = 0; + + attrsize = sizeof(VTOC(vp)->c_finderinfo); + + if ((user_size_t)uio_resid(uio) != attrsize) { + return (ERANGE); + } + /* Grab the new Finder Info data. */ + if ((result = uiomove((caddr_t)&finderinfo , attrsize, uio))) { + return (result); + } + fip = (struct FndrFileInfo *)&finderinfo; + + if ((result = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK))) { + return (result); + } + cp = VTOC(vp); - if (bcmp(VTOC(vp)->c_finderinfo, emptyfinfo, sizeof(emptyfinfo))) { + /* Symlink's don't have an external type/creator. */ + if (vnode_islnk(vp)) { + /* Skip over type/creator fields. */ + finderinfo_start = &cp->c_finderinfo[8]; + attrsize -= 8; + } else { + finderinfo_start = &cp->c_finderinfo[0]; + /* + * Don't allow the external setting of + * file type to kHardLinkFileType. + */ + if (fip->fdType == SWAP_BE32(kHardLinkFileType)) { + hfs_unlock(cp); + return (EPERM); + } + } + + /* Grab the current date added from the cnode */ + dateadded = hfs_get_dateadded (cp); + + /* Zero out the date added field to ignore user's attempts to set it */ + hfs_zero_dateadded(cp, finderinfo); + + if (bcmp(finderinfo_start, emptyfinfo, attrsize)) { /* attr exists and "create" was specified. */ if (ap->a_options & XATTR_CREATE) { + hfs_unlock(cp); return (EEXIST); } - } else { + } else /* empty */ { /* attr doesn't exists and "replace" was specified. */ if (ap->a_options & XATTR_REPLACE) { + hfs_unlock(cp); return (ENOATTR); } } - if (uio_resid(uio) != attrsize) - return (ERANGE); - result = uiomove((caddr_t) &VTOC(vp)->c_finderinfo , attrsize, uio); - if (result == 0) { - VTOC(vp)->c_touch_chgtime = TRUE; - VTOC(vp)->c_flag |= C_MODIFIED; - result = hfs_update(vp, FALSE); + /* + * Now restore the date added to the finderinfo to be written out. + * Advance to the 2nd half of the finderinfo to write out the date added + * into the buffer. + * + * Make sure to endian swap the date added back into big endian. When we used + * hfs_get_dateadded above to retrieve it, it swapped into local endianness + * for us. But now that we're writing it out, put it back into big endian. + */ + finfo = &finderinfo[16]; + + if (S_ISREG(cp->c_attr.ca_mode)) { + struct FndrExtendedFileInfo *extinfo = (struct FndrExtendedFileInfo *)finfo; + extinfo->date_added = OSSwapHostToBigInt32(dateadded); } + else if (S_ISDIR(cp->c_attr.ca_mode)) { + struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)finfo; + extinfo->date_added = OSSwapHostToBigInt32(dateadded); + } + + /* Set the cnode's Finder Info. */ + if (attrsize == sizeof(cp->c_finderinfo)) + bcopy(&finderinfo[0], finderinfo_start, attrsize); + else + bcopy(&finderinfo[8], finderinfo_start, attrsize); + + /* Updating finderInfo updates change time and modified time */ + cp->c_touch_chgtime = TRUE; + cp->c_flag |= C_MODIFIED; + + /* + * Mirror the invisible bit to the UF_HIDDEN flag. + * + * The fdFlags for files and frFlags for folders are both 8 bytes + * into the userInfo (the first 16 bytes of the Finder Info). They + * are both 16-bit fields. + */ + fdFlags = *((u_int16_t *) &cp->c_finderinfo[8]); + if (fdFlags & OSSwapHostToBigConstInt16(kFinderInvisibleMask)) + cp->c_flags |= UF_HIDDEN; + else + cp->c_flags &= ~UF_HIDDEN; + + result = hfs_update(vp, FALSE); + + hfs_unlock(cp); return (result); } /* Write the Resource Fork. */ if (bcmp(ap->a_name, XATTR_RESOURCEFORK_NAME, sizeof(XATTR_RESOURCEFORK_NAME)) == 0) { struct vnode *rvp = NULL; + int namelen = 0; + int openunlinked = 0; if (!vnode_isreg(vp)) { return (EPERM); } - if (RESOURCE_FORK_EXISTS(vp)) { + if ((result = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK))) { + return (result); + } + cp = VTOC(vp); + namelen = cp->c_desc.cd_namelen; + + if (RSRC_FORK_EXISTS(cp)) { /* attr exists and "create" was specified. */ if (ap->a_options & XATTR_CREATE) { + hfs_unlock(cp); return (EEXIST); } } else { /* attr doesn't exists and "replace" was specified. */ if (ap->a_options & XATTR_REPLACE) { + hfs_unlock(cp); return (ENOATTR); } } - if ((result = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK))) { - return (result); + + /* + * Note that we could be called on to grab the rsrc fork vnode + * for a file that has become open-unlinked. + */ + if ((cp->c_flag & C_DELETED) && (namelen == 0)) { + openunlinked = 1; } - result = hfs_vgetrsrc(hfsmp, vp, &rvp, vfs_context_proc(ap->a_context)); - hfs_unlock(VTOC(vp)); + + result = hfs_vgetrsrc(hfsmp, vp, &rvp, TRUE, FALSE); + hfs_unlock(cp); if (result) { return (result); } + /* VNOP_WRITE marks cnode as needing a modtime update */ result = VNOP_WRITE(rvp, uio, 0, ap->a_context); + + /* if open unlinked, force it inactive */ + if (openunlinked) { + int vref; + vref = vnode_ref (rvp); + if (vref == 0) { + vnode_rele(rvp); + } + vnode_recycle (rvp); + } + else { + /* cnode is not open-unlinked, so re-lock cnode to sync */ + if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK))) { + vnode_recycle (rvp); + vnode_put(rvp); + return result; + } + + /* hfs fsync rsrc fork to force to disk and update modtime */ + result = hfs_fsync (rvp, MNT_NOWAIT, 0, vfs_context_proc (ap->a_context)); + hfs_unlock (cp); + } + vnode_put(rvp); return (result); } @@ -315,129 +870,368 @@ hfs_vnop_setxattr(struct vnop_setxattr_args *ap) if (hfsmp->hfs_flags & HFS_STANDARD) { return (EPERM); } - if (hfsmp->hfs_max_inline_attrsize == 0) { - hfsmp->hfs_max_inline_attrsize = getmaxinlineattrsize(hfsmp->hfs_attribute_vp); - } attrsize = uio_resid(uio); - if (attrsize > hfsmp->hfs_max_inline_attrsize) { - /* - * XXX Need to support extent-based attributes XXX - */ - return (E2BIG); + + /* Enforce an upper limit. */ + if (attrsize > HFS_XATTR_MAXSIZE) { + result = E2BIG; + goto exit; } - /* Calculate size of record rounded up to multiple of 2 bytes. */ - datasize = sizeof(HFSPlusAttrData) - 2 + attrsize + ((attrsize & 1) ? 1 : 0); - MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK); - bzero(iterator, sizeof(*iterator)); + /* + * Attempt to copy the users attr data before taking any locks. + */ + if (attrsize > 0 && + hfsmp->hfs_max_inline_attrsize != 0 && + attrsize < hfsmp->hfs_max_inline_attrsize) { + MALLOC(user_data_ptr, void *, attrsize, M_TEMP, M_WAITOK); + if (user_data_ptr == NULL) { + result = ENOMEM; + goto exit; + } - MALLOC(datap, HFSPlusAttrData *, datasize, M_TEMP, M_WAITOK); - btdata.bufferAddress = datap; - btdata.itemSize = datasize; - btdata.itemCount = 1; - datap->recordType = kHFSPlusAttrInlineData; - datap->reserved[0] = 0; - datap->reserved[1] = 0; - datap->attrSize = attrsize; + result = uiomove((caddr_t)user_data_ptr, attrsize, uio); + if (result) { + goto exit; + } + } - /* Copy in the attribute data. */ - result = uiomove((caddr_t) &datap->attrData , attrsize, uio); + result = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK); if (result) { - goto exit2; + goto exit; } - /* Build a b-tree key. */ - result = buildkey(VTOC(vp)->c_fileid, ap->a_name, (HFSPlusAttrKey *)&iterator->key); - if (result) { - goto exit2; + cp = VTOC(vp); + + /* + * If we're trying to set a non-finderinfo, non-resourcefork EA, then + * call the breakout function. + */ + result = hfs_setxattr_internal (cp, user_data_ptr, attrsize, ap, VTOHFS(vp), 0); + + exit: + if (cp) { + hfs_unlock(cp); + } + if (user_data_ptr) { + FREE(user_data_ptr, M_TEMP); + } + + return (result == btNotFound ? ENOATTR : MacToVFSError(result)); +} + + +/* + * hfs_setxattr_internal + * + * Internal function to set non-rsrc, non-finderinfo EAs to either the attribute B-Tree or + * extent-based EAs. + * + * See comments from hfs_getxattr_internal on why we need to pass 'hfsmp' and fileid here. + * The gist is that we could end up writing to the root folder which may not have a cnode. + * + * Assumptions: + * 1. cnode 'cp' is locked EXCLUSIVE before calling this function. + * 2. data_ptr contains data to be written. If gathering data from userland, this must be + * done before calling this function. + * 3. If data originates entirely in-kernel, use a null UIO, and ensure the size is less than + * hfsmp->hfs_max_inline_attrsize bytes long. + */ +int hfs_setxattr_internal (struct cnode *cp, caddr_t data_ptr, size_t attrsize, + struct vnop_setxattr_args *ap, struct hfsmount *hfsmp, + u_int32_t fileid) { + uio_t uio = ap->a_uio; + struct vnode *vp = ap->a_vp; + int started_transaction = 0; + struct BTreeIterator * iterator = NULL; + struct filefork *btfile = NULL; + FSBufferDescriptor btdata; + HFSPlusAttrRecord attrdata; /* 90 bytes */ + HFSPlusAttrRecord *recp = NULL; + HFSPlusExtentDescriptor *extentptr = NULL; + int result = 0; + int lockflags = 0; + int exists = 0; + int allocatedblks = 0; + u_int32_t target_id; + + if (cp) { + target_id = cp->c_fileid; } + else { + target_id = fileid; + } + /* Start a transaction for our changes. */ if (hfs_start_transaction(hfsmp) != 0) { result = EINVAL; - goto exit2; + goto exit; } - - /* once we started the transaction, nobody can compete with us, so make sure this file is still there */ - struct cnode *cp; - cp = VTOC(vp); - if (cp->c_flag & C_NOEXISTS) { /* this file has already been removed */ + started_transaction = 1; + + /* + * Once we started the transaction, nobody can compete + * with us, so make sure this file is still there. + */ + if ((cp) && (cp->c_flag & C_NOEXISTS)) { result = ENOENT; - goto exit1; + goto exit; } - + /* * If there isn't an attributes b-tree then create one. */ if (hfsmp->hfs_attribute_vp == NULL) { - lockflags = hfs_systemfile_lock(hfsmp, SFL_EXTENTS, HFS_EXCLUSIVE_LOCK); result = hfs_create_attr_btree(hfsmp, ATTRIBUTE_FILE_NODE_SIZE, getnodecount(hfsmp, ATTRIBUTE_FILE_NODE_SIZE)); - hfs_systemfile_unlock(hfsmp, lockflags); if (result) { - goto exit1; + goto exit; } } - btfile = VTOF(hfsmp->hfs_attribute_vp); - + if (hfsmp->hfs_max_inline_attrsize == 0) { + hfsmp->hfs_max_inline_attrsize = getmaxinlineattrsize(hfsmp->hfs_attribute_vp); + } + + /* Take exclusive access to the attributes b-tree. */ lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_EXCLUSIVE_LOCK); - - if (ap->a_options & XATTR_REPLACE) { - result = BTReplaceRecord(btfile, iterator, &btdata, datasize); - if (result) - goto exit0; - else - goto exit; + + /* Build the b-tree key. */ + MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK); + if (iterator == NULL) { + result = ENOMEM; + goto exit; } - - /* Insert the attribute. */ - result = BTInsertRecord(btfile, iterator, &btdata, datasize); + bzero(iterator, sizeof(*iterator)); + result = hfs_buildattrkey(target_id, ap->a_name, (HFSPlusAttrKey *)&iterator->key); if (result) { - if (result != btExists) { - goto exit0; + goto exit; + } + + /* Preflight for replace/create semantics. */ + btfile = VTOF(hfsmp->hfs_attribute_vp); + btdata.bufferAddress = &attrdata; + btdata.itemSize = sizeof(attrdata); + btdata.itemCount = 1; + exists = BTSearchRecord(btfile, iterator, &btdata, NULL, NULL) == 0; + + /* Replace requires that the attribute already exists. */ + if ((ap->a_options & XATTR_REPLACE) && !exists) { + result = ENOATTR; + goto exit; + } + /* Create requires that the attribute doesn't exist. */ + if ((ap->a_options & XATTR_CREATE) && exists) { + result = EEXIST; + goto exit; + } + + /* If it won't fit inline then use extent-based attributes. */ + if (attrsize > hfsmp->hfs_max_inline_attrsize) { + size_t extentbufsize; + int blkcnt; + int extentblks; + u_int32_t *keystartblk; + int i; + + if (uio == NULL) { + /* + * setxattrs originating from in-kernel are not supported if they are bigger + * than the inline max size. Just return ENOATTR and force them to do it with a + * smaller EA. + */ + result = EPERM; + goto exit; + } + + /* Get some blocks. */ + blkcnt = howmany(attrsize, hfsmp->blockSize); + extentbufsize = blkcnt * sizeof(HFSPlusExtentDescriptor); + extentbufsize = roundup(extentbufsize, sizeof(HFSPlusExtentRecord)); + MALLOC(extentptr, HFSPlusExtentDescriptor *, extentbufsize, M_TEMP, M_WAITOK); + if (extentptr == NULL) { + result = ENOMEM; + goto exit; + } + bzero(extentptr, extentbufsize); + result = alloc_attr_blks(hfsmp, attrsize, extentbufsize, extentptr, &allocatedblks); + if (result) { + allocatedblks = 0; + goto exit; /* no more space */ + } + /* Copy data into the blocks. */ + result = write_attr_data(hfsmp, uio, attrsize, extentptr); + if (result) { + if (vp) { + const char *name = vnode_getname(vp); + printf("hfs_setxattr: write_attr_data err (%d) %s:%s\n", + result, name ? name : "", ap->a_name); + if (name) + vnode_putname(name); + } + goto exit; } - // if it exists and XATTR_CREATE was specified, - // the spec says to return EEXIST - if (ap->a_options & XATTR_CREATE) { - result = EEXIST; - goto exit0; + /* Now remove any previous attribute. */ + if (exists) { + result = remove_attribute_records(hfsmp, iterator); + if (result) { + if (vp) { + const char *name = vnode_getname(vp); + printf("hfs_setxattr: remove_attribute_records err (%d) %s:%s\n", + result, name ? name : "", ap->a_name); + if (name) + vnode_putname(name); + } + goto exit; + } + } + /* Create attribute fork data record. */ + MALLOC(recp, HFSPlusAttrRecord *, sizeof(HFSPlusAttrRecord), M_TEMP, M_WAITOK); + if (recp == NULL) { + result = ENOMEM; + goto exit; + } + btdata.bufferAddress = recp; + btdata.itemCount = 1; + btdata.itemSize = sizeof(HFSPlusAttrForkData); + + recp->recordType = kHFSPlusAttrForkData; + recp->forkData.reserved = 0; + recp->forkData.theFork.logicalSize = attrsize; + recp->forkData.theFork.clumpSize = 0; + recp->forkData.theFork.totalBlocks = blkcnt; + bcopy(extentptr, recp->forkData.theFork.extents, sizeof(HFSPlusExtentRecord)); + + (void) hfs_buildattrkey(target_id, ap->a_name, (HFSPlusAttrKey *)&iterator->key); + + result = BTInsertRecord(btfile, iterator, &btdata, btdata.itemSize); + if (result) { + printf ("hfs_setxattr: BTInsertRecord() - %d,%s err=%d\n", + target_id, ap->a_name, result); + goto exit; + } + extentblks = count_extent_blocks(blkcnt, recp->forkData.theFork.extents); + blkcnt -= extentblks; + keystartblk = &((HFSPlusAttrKey *)&iterator->key)->startBlock; + i = 0; + + /* Create overflow extents as needed. */ + while (blkcnt > 0) { + /* Initialize the key and record. */ + *keystartblk += (u_int32_t)extentblks; + btdata.itemSize = sizeof(HFSPlusAttrExtents); + recp->recordType = kHFSPlusAttrExtents; + recp->overflowExtents.reserved = 0; + + /* Copy the next set of extents. */ + i += kHFSPlusExtentDensity; + bcopy(&extentptr[i], recp->overflowExtents.extents, sizeof(HFSPlusExtentRecord)); + + result = BTInsertRecord(btfile, iterator, &btdata, btdata.itemSize); + if (result) { + printf ("hfs_setxattr: BTInsertRecord() overflow - %d,%s err=%d\n", + target_id, ap->a_name, result); + goto exit; + } + extentblks = count_extent_blocks(blkcnt, recp->overflowExtents.extents); + blkcnt -= extentblks; + } + } + else { /* Inline data */ + if (exists) { + result = remove_attribute_records(hfsmp, iterator); + if (result) { + goto exit; + } + } + + /* Calculate size of record rounded up to multiple of 2 bytes. */ + btdata.itemSize = sizeof(HFSPlusAttrData) - 2 + attrsize + ((attrsize & 1) ? 1 : 0); + MALLOC(recp, HFSPlusAttrRecord *, btdata.itemSize, M_TEMP, M_WAITOK); + if (recp == NULL) { + result = ENOMEM; + goto exit; + } + recp->recordType = kHFSPlusAttrInlineData; + recp->attrData.reserved[0] = 0; + recp->attrData.reserved[1] = 0; + recp->attrData.attrSize = attrsize; + + /* Copy in the attribute data (if any). */ + if (attrsize > 0) { + if (data_ptr) { + bcopy(data_ptr, &recp->attrData.attrData, attrsize); + } + else { + /* + * A null UIO meant it originated in-kernel. If they didn't supply data_ptr + * then deny the copy operation. + */ + if (uio == NULL) { + result = EPERM; + goto exit; + } + result = uiomove((caddr_t)&recp->attrData.attrData, attrsize, uio); + } + + if (result) { + goto exit; + } } - /* XXX need to account for old size in c_attrblks */ - result = BTReplaceRecord(btfile, iterator, &btdata, datasize); + + (void) hfs_buildattrkey(target_id, ap->a_name, (HFSPlusAttrKey *)&iterator->key); + + btdata.bufferAddress = recp; + btdata.itemCount = 1; + result = BTInsertRecord(btfile, iterator, &btdata, btdata.itemSize); } + exit: - (void) BTFlushPath(btfile); -exit0: - hfs_systemfile_unlock(hfsmp, lockflags); + if (btfile && started_transaction) { + (void) BTFlushPath(btfile); + } + if (lockflags) { + hfs_systemfile_unlock(hfsmp, lockflags); + } if (result == 0) { - struct cnode * cp; - - cp = VTOC(vp); - cp->c_touch_chgtime = TRUE; - if ((cp->c_attr.ca_recflags & kHFSHasAttributesMask) == 0) { + if (vp) { + cp = VTOC(vp); + /* Setting an attribute only updates change time and not + * modified time of the file. + */ + cp->c_touch_chgtime = TRUE; cp->c_attr.ca_recflags |= kHFSHasAttributesMask; + if ((bcmp(ap->a_name, KAUTH_FILESEC_XATTR, sizeof(KAUTH_FILESEC_XATTR)) == 0)) { + cp->c_attr.ca_recflags |= kHFSHasSecurityMask; + } (void) hfs_update(vp, 0); } - HFS_KNOTE(vp, NOTE_ATTRIB); } -exit1: - /* Finish the transaction of our changes. */ - hfs_end_transaction(hfsmp); -exit2: - FREE(datap, M_TEMP); - FREE(iterator, M_TEMP); + if (started_transaction) { + if (result && allocatedblks) { + free_attr_blks(hfsmp, allocatedblks, extentptr); + } + hfs_end_transaction(hfsmp); + } + + if (recp) { + FREE(recp, M_TEMP); + } + if (extentptr) { + FREE(extentptr, M_TEMP); + } + if (iterator) { + FREE(iterator, M_TEMP); + } + + return result; +} + - if (result == btNotFound) - result = ENOATTR; - else - result = MacToVFSError(result); - return (result); -} /* * Remove an extended attribute. */ -__private_extern__ int hfs_vnop_removexattr(struct vnop_removexattr_args *ap) /* @@ -451,14 +1245,12 @@ hfs_vnop_removexattr(struct vnop_removexattr_args *ap) */ { struct vnode *vp = ap->a_vp; + struct cnode *cp = VTOC(vp); struct hfsmount *hfsmp; struct BTreeIterator * iterator = NULL; - struct filefork *btfile; - struct proc *p = vfs_context_proc(ap->a_context); - FSBufferDescriptor btdata; - HFSPlusAttrData attrdata; int lockflags; int result; + time_t orig_ctime=VTOC(vp)->c_ctime; if (ap->a_name == NULL || ap->a_name[0] == '\0') { return (EINVAL); /* invalid name */ @@ -468,6 +1260,14 @@ hfs_vnop_removexattr(struct vnop_removexattr_args *ap) return (EPERM); } +#if HFS_COMPRESSION + if (hfs_hides_xattr(ap->a_context, VTOC(vp), ap->a_name, 1) && !(ap->a_options & XATTR_SHOWCOMPRESSION)) { + return ENOATTR; + } + + check_for_tracked_file(vp, orig_ctime, NAMESPACE_HANDLER_METADATA_DELETE_OP, NULL); +#endif /* HFS_COMPRESSION */ + /* If Resource Fork is non-empty then truncate it. */ if (bcmp(ap->a_name, XATTR_RESOURCEFORK_NAME, sizeof(XATTR_RESOURCEFORK_NAME)) == 0) { struct vnode *rvp = NULL; @@ -475,26 +1275,45 @@ hfs_vnop_removexattr(struct vnop_removexattr_args *ap) if ( !vnode_isreg(vp) ) { return (EPERM); } - if ( !RESOURCE_FORK_EXISTS(vp) ) { - return (ENOATTR); - } - if ((result = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK))) { + if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK))) { return (result); } - result = hfs_vgetrsrc(hfsmp, vp, &rvp, p); - hfs_unlock(VTOC(vp)); + if ( !RSRC_FORK_EXISTS(cp)) { + hfs_unlock(cp); + return (ENOATTR); + } + result = hfs_vgetrsrc(hfsmp, vp, &rvp, TRUE, FALSE); + hfs_unlock(cp); if (result) { return (result); } - hfs_lock_truncate(VTOC(rvp), TRUE); + + hfs_lock_truncate(VTOC(rvp), HFS_EXCLUSIVE_LOCK); if ((result = hfs_lock(VTOC(rvp), HFS_EXCLUSIVE_LOCK))) { - hfs_unlock_truncate(VTOC(vp)); + hfs_unlock_truncate(cp, 0); vnode_put(rvp); return (result); } - result = hfs_truncate(rvp, (off_t)0, IO_NDELAY, 0, ap->a_context); - hfs_unlock_truncate(VTOC(rvp)); + /* Start a transaction for encapsulating changes in + * hfs_truncate() and hfs_update() + */ + if ((result = hfs_start_transaction(hfsmp))) { + hfs_unlock_truncate(cp, 0); + hfs_unlock(cp); + vnode_put(rvp); + return (result); + } + + result = hfs_truncate(rvp, (off_t)0, IO_NDELAY, 0, 0, ap->a_context); + if (result == 0) { + cp->c_touch_chgtime = TRUE; + cp->c_flag |= C_MODIFIED; + result = hfs_update(vp, FALSE); + } + + hfs_end_transaction(hfsmp); + hfs_unlock_truncate(VTOC(rvp), 0); hfs_unlock(VTOC(rvp)); vnode_put(rvp); @@ -502,10 +1321,82 @@ hfs_vnop_removexattr(struct vnop_removexattr_args *ap) } /* Clear out the Finder Info. */ if (bcmp(ap->a_name, XATTR_FINDERINFO_NAME, sizeof(XATTR_FINDERINFO_NAME)) == 0) { - if (bcmp(VTOC(vp)->c_finderinfo, emptyfinfo, sizeof(emptyfinfo)) == 0) { + void * finderinfo_start; + int finderinfo_size; + u_int8_t finderinfo[32]; + u_int32_t date_added; + u_int8_t *finfo = NULL; + + if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK))) { + return (result); + } + + /* Use the local copy to store our temporary changes. */ + bcopy(cp->c_finderinfo, finderinfo, sizeof(finderinfo)); + + + /* Zero out the date added field in the local copy */ + hfs_zero_dateadded (cp, finderinfo); + + /* Don't expose a symlink's private type/creator. */ + if (vnode_islnk(vp)) { + struct FndrFileInfo *fip; + + fip = (struct FndrFileInfo *)&finderinfo; + fip->fdType = 0; + fip->fdCreator = 0; + } + + /* Do the byte compare against the local copy */ + if (bcmp(finderinfo, emptyfinfo, sizeof(emptyfinfo)) == 0) { + hfs_unlock (cp); return (ENOATTR); } - bzero(VTOC(vp)->c_finderinfo, sizeof(emptyfinfo)); + + /* + * If there was other content, zero out everything except + * type/creator and date added. First, save the date added. + */ + finfo = cp->c_finderinfo; + finfo = finfo + 16; + if (S_ISREG(cp->c_attr.ca_mode)) { + struct FndrExtendedFileInfo *extinfo = (struct FndrExtendedFileInfo *)finfo; + date_added = extinfo->date_added; + } + else if (S_ISDIR(cp->c_attr.ca_mode)) { + struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)finfo; + date_added = extinfo->date_added; + } + + if (vnode_islnk(vp)) { + /* Ignore type/creator */ + finderinfo_start = &cp->c_finderinfo[8]; + finderinfo_size = sizeof(cp->c_finderinfo) - 8; + } + else { + finderinfo_start = &cp->c_finderinfo[0]; + finderinfo_size = sizeof(cp->c_finderinfo); + } + bzero(finderinfo_start, finderinfo_size); + + + /* Now restore the date added */ + if (S_ISREG(cp->c_attr.ca_mode)) { + struct FndrExtendedFileInfo *extinfo = (struct FndrExtendedFileInfo *)finfo; + extinfo->date_added = date_added; + } + else if (S_ISDIR(cp->c_attr.ca_mode)) { + struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)finfo; + extinfo->date_added = date_added; + } + + /* Updating finderInfo updates change time and modified time */ + cp->c_touch_chgtime = TRUE; + cp->c_flag |= C_MODIFIED; + hfs_update(vp, FALSE); + + hfs_unlock(cp); + return (0); } /* @@ -517,53 +1408,213 @@ hfs_vnop_removexattr(struct vnop_removexattr_args *ap) if (hfsmp->hfs_attribute_vp == NULL) { return (ENOATTR); } - btfile = VTOF(hfsmp->hfs_attribute_vp); MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK); + if (iterator == NULL) { + return (ENOMEM); + } bzero(iterator, sizeof(*iterator)); + if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK))) { + goto exit_nolock; + } + + result = hfs_buildattrkey(cp->c_fileid, ap->a_name, (HFSPlusAttrKey *)&iterator->key); + if (result) { + goto exit; + } + if (hfs_start_transaction(hfsmp) != 0) { result = EINVAL; - goto exit2; + goto exit; } + lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE | SFL_BITMAP, HFS_EXCLUSIVE_LOCK); + + result = remove_attribute_records(hfsmp, iterator); - result = buildkey(VTOC(vp)->c_fileid, ap->a_name, (HFSPlusAttrKey *)&iterator->key); - if (result) - goto exit2; + hfs_systemfile_unlock(hfsmp, lockflags); - lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_EXCLUSIVE_LOCK); + if (result == 0) { + cp->c_touch_chgtime = TRUE; - btdata.bufferAddress = &attrdata; - btdata.itemSize = sizeof(attrdata); - btdata.itemCount = 1; - result = BTSearchRecord(btfile, iterator, &btdata, NULL, NULL); - if (result) - goto exit1; + lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK); - result = BTDeleteRecord(btfile, iterator); - (void) BTFlushPath(btfile); -exit1: - hfs_systemfile_unlock(hfsmp, lockflags); - if (result == 0) { - VTOC(vp)->c_touch_chgtime = TRUE; - HFS_KNOTE(vp, NOTE_ATTRIB); - } -exit2: - if (result == btNotFound) { - result = ENOATTR; + /* If no more attributes exist, clear attribute bit */ + result = file_attribute_exist(hfsmp, cp->c_fileid); + if (result == 0) { + cp->c_attr.ca_recflags &= ~kHFSHasAttributesMask; + } + if (result == EEXIST) { + result = 0; + } + + hfs_systemfile_unlock(hfsmp, lockflags); + + /* If ACL was removed, clear security bit */ + if ((bcmp(ap->a_name, KAUTH_FILESEC_XATTR, sizeof(KAUTH_FILESEC_XATTR)) == 0)) { + cp->c_attr.ca_recflags &= ~kHFSHasSecurityMask; + } + (void) hfs_update(vp, 0); } - hfs_end_transaction(hfsmp); + hfs_end_transaction(hfsmp); +exit: + hfs_unlock(cp); +exit_nolock: FREE(iterator, M_TEMP); + return MacToVFSError(result); +} + +/* Check if any attribute record exist for given fileID. This function + * is called by hfs_vnop_removexattr to determine if it should clear the + * attribute bit in the catalog record or not. + * + * Note - you must acquire a shared lock on the attribute btree before + * calling this function. + * + * Output: + * EEXIST - If attribute record was found + * 0 - Attribute was not found + * (other) - Other error (such as EIO) + */ +int +file_attribute_exist(struct hfsmount *hfsmp, uint32_t fileID) +{ + HFSPlusAttrKey *key; + struct BTreeIterator * iterator = NULL; + struct filefork *btfile; + int result = 0; + + // if there's no attribute b-tree we sure as heck + // can't have any attributes! + if (hfsmp->hfs_attribute_vp == NULL) { + return false; + } + + MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK); + if (iterator == NULL) { + result = ENOMEM; + goto out; + } + bzero(iterator, sizeof(*iterator)); + key = (HFSPlusAttrKey *)&iterator->key; + + result = hfs_buildattrkey(fileID, NULL, key); + if (result) { + goto out; + } + + btfile = VTOF(hfsmp->hfs_attribute_vp); + result = BTSearchRecord(btfile, iterator, NULL, NULL, NULL); + if (result && (result != btNotFound)) { + goto out; + } + + result = BTIterateRecord(btfile, kBTreeNextRecord, iterator, NULL, NULL); + /* If no next record was found or fileID for next record did not match, + * no more attributes exist for this fileID + */ + if ((result && (result == btNotFound)) || (key->fileID != fileID)) { + result = 0; + } else { + result = EEXIST; + } + +out: + if (iterator) { + FREE(iterator, M_TEMP); + } + return result; +} + + +/* + * Remove all the records for a given attribute. + * + * - Used by hfs_vnop_removexattr, hfs_vnop_setxattr and hfs_removeallattr. + * - A transaction must have been started. + * - The Attribute b-tree file must be locked exclusive. + * - The Allocation Bitmap file must be locked exclusive. + * - The iterator key must be initialized. + */ +int +remove_attribute_records(struct hfsmount *hfsmp, BTreeIterator * iterator) +{ + struct filefork *btfile; + FSBufferDescriptor btdata; + HFSPlusAttrRecord attrdata; /* 90 bytes */ + u_int16_t datasize; + int result; + + btfile = VTOF(hfsmp->hfs_attribute_vp); + + btdata.bufferAddress = &attrdata; + btdata.itemSize = sizeof(attrdata); + btdata.itemCount = 1; + result = BTSearchRecord(btfile, iterator, &btdata, &datasize, NULL); + if (result) { + goto exit; /* no records. */ + } + /* + * Free the blocks from extent based attributes. + * + * Note that the block references (btree records) are removed + * before releasing the blocks in the allocation bitmap. + */ + if (attrdata.recordType == kHFSPlusAttrForkData) { + int totalblks; + int extentblks; + u_int32_t *keystartblk; - return MacToVFSError(result); + if (datasize < sizeof(HFSPlusAttrForkData)) { + printf("hfs: remove_attribute_records: bad record size %d (expecting %lu)\n", datasize, sizeof(HFSPlusAttrForkData)); + } + totalblks = attrdata.forkData.theFork.totalBlocks; + + /* Process the first 8 extents. */ + extentblks = count_extent_blocks(totalblks, attrdata.forkData.theFork.extents); + if (extentblks > totalblks) + panic("hfs: remove_attribute_records: corruption..."); + if (BTDeleteRecord(btfile, iterator) == 0) { + free_attr_blks(hfsmp, extentblks, attrdata.forkData.theFork.extents); + } + totalblks -= extentblks; + keystartblk = &((HFSPlusAttrKey *)&iterator->key)->startBlock; + + /* Process any overflow extents. */ + while (totalblks) { + *keystartblk += (u_int32_t)extentblks; + + result = BTSearchRecord(btfile, iterator, &btdata, &datasize, NULL); + if (result || + (attrdata.recordType != kHFSPlusAttrExtents) || + (datasize < sizeof(HFSPlusAttrExtents))) { + printf("hfs: remove_attribute_records: BTSearchRecord %d (%d), totalblks %d\n", + MacToVFSError(result), attrdata.recordType != kHFSPlusAttrExtents, totalblks); + result = ENOATTR; + break; /* break from while */ + } + /* Process the next 8 extents. */ + extentblks = count_extent_blocks(totalblks, attrdata.overflowExtents.extents); + if (extentblks > totalblks) + panic("hfs: remove_attribute_records: corruption..."); + if (BTDeleteRecord(btfile, iterator) == 0) { + free_attr_blks(hfsmp, extentblks, attrdata.overflowExtents.extents); + } + totalblks -= extentblks; + } + } else { + result = BTDeleteRecord(btfile, iterator); + } + (void) BTFlushPath(btfile); +exit: + return (result == btNotFound ? ENOATTR : MacToVFSError(result)); } /* * Retrieve the list of extended attribute names. */ -__private_extern__ int hfs_vnop_listxattr(struct vnop_listxattr_args *ap) /* @@ -577,44 +1628,85 @@ hfs_vnop_listxattr(struct vnop_listxattr_args *ap) */ { struct vnode *vp = ap->a_vp; + struct cnode *cp = VTOC(vp); struct hfsmount *hfsmp; uio_t uio = ap->a_uio; struct BTreeIterator * iterator = NULL; struct filefork *btfile; struct listattr_callback_state state; + user_addr_t user_start = 0; + user_size_t user_len = 0; int lockflags; int result; + u_int8_t finderinfo[32]; if (VNODE_IS_RSRC(vp)) { return (EPERM); } + +#if HFS_COMPRESSION + int compressed = hfs_file_is_compressed(cp, 1); /* 1 == don't take the cnode lock */ +#endif /* HFS_COMPRESSION */ + hfsmp = VTOHFS(vp); *ap->a_size = 0; - /* If Finder Info is non-empty then export it. */ - if (bcmp(VTOC(vp)->c_finderinfo, emptyfinfo, sizeof(emptyfinfo)) != 0) { + if ((result = hfs_lock(cp, HFS_SHARED_LOCK))) { + return (result); + } + + /* + * Make a copy of the cnode's finderinfo to a local so we can + * zero out the date added field. Also zero out the private type/creator + * for symlinks. + */ + bcopy(cp->c_finderinfo, finderinfo, sizeof(finderinfo)); + hfs_zero_dateadded (cp, finderinfo); + + /* Don't expose a symlink's private type/creator. */ + if (vnode_islnk(vp)) { + struct FndrFileInfo *fip; + + fip = (struct FndrFileInfo *)&finderinfo; + fip->fdType = 0; + fip->fdCreator = 0; + } + + + /* If Finder Info is non-empty then export it's name. */ + if (bcmp(finderinfo, emptyfinfo, sizeof(emptyfinfo)) != 0) { if (uio == NULL) { *ap->a_size += sizeof(XATTR_FINDERINFO_NAME); - } else if (uio_resid(uio) < sizeof(XATTR_FINDERINFO_NAME)) { - return (ERANGE); + } else if ((user_size_t)uio_resid(uio) < sizeof(XATTR_FINDERINFO_NAME)) { + result = ERANGE; + goto exit; } else { - result = uiomove((caddr_t)XATTR_FINDERINFO_NAME, + result = uiomove(XATTR_FINDERINFO_NAME, sizeof(XATTR_FINDERINFO_NAME), uio); if (result) - return (result); + goto exit; } } - /* If Resource Fork is non-empty then export it. */ - if (vnode_isreg(vp) && RESOURCE_FORK_EXISTS(vp)) { - if (uio == NULL) { - *ap->a_size += sizeof(XATTR_RESOURCEFORK_NAME); - } else if (uio_resid(uio) < sizeof(XATTR_RESOURCEFORK_NAME)) { - return (ERANGE); - } else { - result = uiomove((caddr_t)XATTR_RESOURCEFORK_NAME, - sizeof(XATTR_RESOURCEFORK_NAME), uio); - if (result) - return (result); + /* If Resource Fork is non-empty then export it's name. */ + if (S_ISREG(cp->c_mode) && RSRC_FORK_EXISTS(cp)) { +#if HFS_COMPRESSION + if ((ap->a_options & XATTR_SHOWCOMPRESSION) || + !compressed || + !hfs_hides_rsrc(ap->a_context, VTOC(vp), 1) /* 1 == don't take the cnode lock */ + ) +#endif /* HFS_COMPRESSION */ + { + if (uio == NULL) { + *ap->a_size += sizeof(XATTR_RESOURCEFORK_NAME); + } else if ((user_size_t)uio_resid(uio) < sizeof(XATTR_RESOURCEFORK_NAME)) { + result = ERANGE; + goto exit; + } else { + result = uiomove(XATTR_RESOURCEFORK_NAME, + sizeof(XATTR_RESOURCEFORK_NAME), uio); + if (result) + goto exit; + } } } /* @@ -622,21 +1714,40 @@ hfs_vnop_listxattr(struct vnop_listxattr_args *ap) * Return at this point. */ if (hfsmp->hfs_flags & HFS_STANDARD) { - return (0); + result = 0; + goto exit; } /* Bail if we don't have any extended attributes. */ if ((hfsmp->hfs_attribute_vp == NULL) || - (VTOC(vp)->c_attr.ca_recflags & kHFSHasAttributesMask) == 0) { - return (0); + (cp->c_attr.ca_recflags & kHFSHasAttributesMask) == 0) { + result = 0; + goto exit; } btfile = VTOF(hfsmp->hfs_attribute_vp); MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK); + if (iterator == NULL) { + result = ENOMEM; + goto exit; + } bzero(iterator, sizeof(*iterator)); - result = buildkey(VTOC(vp)->c_fileid, NULL, (HFSPlusAttrKey *)&iterator->key); + result = hfs_buildattrkey(cp->c_fileid, NULL, (HFSPlusAttrKey *)&iterator->key); if (result) goto exit; + /* + * Lock the user's buffer here so that we won't fault on + * it in uiomove while holding the attributes b-tree lock. + */ + if (uio && uio_isuserspace(uio)) { + user_start = uio_curriovbase(uio); + user_len = uio_curriovlen(uio); + + if ((result = vslock(user_start, user_len)) != 0) { + user_start = 0; + goto exit; + } + } lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK); result = BTSearchRecord(btfile, iterator, NULL, NULL, NULL); @@ -645,10 +1756,15 @@ hfs_vnop_listxattr(struct vnop_listxattr_args *ap) goto exit; } - state.fileID = VTOC(vp)->c_fileid; + state.fileID = cp->c_fileid; state.result = 0; state.uio = uio; state.size = 0; +#if HFS_COMPRESSION + state.showcompressed = !compressed || ap->a_options & XATTR_SHOWCOMPRESSION; + state.ctx = ap->a_context; + state.vp = vp; +#endif /* HFS_COMPRESSION */ /* * Process entries starting just after iterator->key. @@ -659,24 +1775,31 @@ hfs_vnop_listxattr(struct vnop_listxattr_args *ap) if (uio == NULL) { *ap->a_size += state.size; } -exit: - FREE(iterator, M_TEMP); - + if (state.result || result == btNotFound) result = state.result; +exit: + if (user_start) { + vsunlock(user_start, user_len, TRUE); + } + if (iterator) { + FREE(iterator, M_TEMP); + } + hfs_unlock(cp); + return MacToVFSError(result); } /* - * Callback - called for each attribute + * Callback - called for each attribute record */ static int listattr_callback(const HFSPlusAttrKey *key, __unused const HFSPlusAttrData *data, struct listattr_callback_state *state) { char attrname[XATTR_MAXNAMELEN + 1]; - size_t bytecount; + ssize_t bytecount; int result; if (state->fileID != key->fileID) { @@ -690,8 +1813,9 @@ listattr_callback(const HFSPlusAttrKey *key, __unused const HFSPlusAttrData *dat return (1); /* continue */ } + /* Convert the attribute name into UTF-8. */ result = utf8_encodestr(key->attrName, key->attrNameLen * sizeof(UniChar), - attrname, &bytecount, sizeof(attrname), 0, 0); + (u_int8_t *)attrname, (size_t *)&bytecount, sizeof(attrname), '/', 0); if (result) { state->result = result; return (0); /* stop */ @@ -701,6 +1825,11 @@ listattr_callback(const HFSPlusAttrKey *key, __unused const HFSPlusAttrData *dat if (xattr_protected(attrname)) return (1); /* continue */ +#if HFS_COMPRESSION + if (!state->showcompressed && hfs_hides_xattr(state->ctx, VTOC(state->vp), attrname, 1) ) /* 1 == don't take the cnode lock */ + return 1; /* continue */ +#endif /* HFS_COMPRESSION */ + if (state->uio == NULL) { state->size += bytecount; } else { @@ -717,77 +1846,97 @@ listattr_callback(const HFSPlusAttrKey *key, __unused const HFSPlusAttrData *dat return (1); /* continue */ } - /* * Remove all the attributes from a cnode. * - * A jornal transaction must be already started. - * Attributes b-Tree must have exclusive lock held. + * This function creates/ends its own transaction so that each + * attribute is deleted in its own transaction (to avoid having + * a transaction grow too large). + * + * This function takes the necessary locks on the attribute + * b-tree file and the allocation (bitmap) file. */ -__private_extern__ int hfs_removeallattr(struct hfsmount *hfsmp, u_int32_t fileid) { - BTreeIterator *next_iterator, *del_iterator; - HFSPlusAttrKey *next_key; + BTreeIterator *iterator = NULL; + HFSPlusAttrKey *key; struct filefork *btfile; - int result, iter_result; + int result, lockflags; if (hfsmp->hfs_attribute_vp == NULL) { return (0); } btfile = VTOF(hfsmp->hfs_attribute_vp); - MALLOC(next_iterator, BTreeIterator *, sizeof(BTreeIterator) * 2, M_TEMP, M_WAITOK); - bzero(next_iterator, sizeof(BTreeIterator) * 2); - del_iterator = &next_iterator[1]; - next_key = (HFSPlusAttrKey *)&next_iterator->key; - - /* - * Go to first possible attribute key/record pair - */ - (void) buildkey(fileid, NULL, next_key); - result = BTIterateRecord(btfile, kBTreeNextRecord, next_iterator, NULL, NULL); - if (result || next_key->fileID != fileid) { - goto exit; + MALLOC(iterator, BTreeIterator *, sizeof(BTreeIterator), M_TEMP, M_WAITOK); + if (iterator == NULL) { + return (ENOMEM); } - /* Remember iterator of attribute to delete */ - bcopy(next_iterator, del_iterator, sizeof(BTreeIterator)); + bzero(iterator, sizeof(BTreeIterator)); + key = (HFSPlusAttrKey *)&iterator->key; /* Loop until there are no more attributes for this file id */ for(;;) { - iter_result = BTIterateRecord(btfile, kBTreeNextRecord, next_iterator, NULL, NULL); - - /* XXX need to free and extents for record types 0x20 and 0x30 */ - result = BTDeleteRecord(btfile, del_iterator); - if (result) { + if (hfs_start_transaction(hfsmp) != 0) { + result = EINVAL; goto exit; } - if (iter_result) { - result = iter_result; - break; + + /* Lock the attribute b-tree and the allocation (bitmap) files */ + lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE | SFL_BITMAP, HFS_EXCLUSIVE_LOCK); + + /* + * Go to first possible attribute key/record pair + */ + (void) hfs_buildattrkey(fileid, NULL, key); + result = BTIterateRecord(btfile, kBTreeNextRecord, iterator, NULL, NULL); + if (result || key->fileID != fileid) { + hfs_systemfile_unlock(hfsmp, lockflags); + hfs_end_transaction(hfsmp); + goto exit; } - if (iter_result || next_key->fileID != fileid) { - break; /* end of attributes for this file id */ + result = remove_attribute_records(hfsmp, iterator); + +#if HFS_XATTR_VERBOSE + if (result) { + printf("hfs_removeallattr: unexpected err %d\n", result); } - bcopy(next_iterator, del_iterator, sizeof(BTreeIterator)); +#endif + hfs_systemfile_unlock(hfsmp, lockflags); + hfs_end_transaction(hfsmp); + if (result) + break; } exit: - (void) BTFlushPath(btfile); + FREE(iterator, M_TEMP); + return (result == btNotFound ? 0: MacToVFSError(result)); +} - if (result == btNotFound) { - result = 0; +__private_extern__ +void +hfs_xattr_init(struct hfsmount * hfsmp) +{ + /* + * If there isn't an attributes b-tree then create one. + */ + if (!(hfsmp->hfs_flags & HFS_STANDARD) && + (hfsmp->hfs_attribute_vp == NULL) && + !(hfsmp->hfs_flags & HFS_READ_ONLY)) { + (void) hfs_create_attr_btree(hfsmp, ATTRIBUTE_FILE_NODE_SIZE, + getnodecount(hfsmp, ATTRIBUTE_FILE_NODE_SIZE)); } - FREE(next_iterator, M_TEMP); - return (result); + if (hfsmp->hfs_attribute_vp) + hfsmp->hfs_max_inline_attrsize = getmaxinlineattrsize(hfsmp->hfs_attribute_vp); } /* - * Enable/Disable extended security (ACLs). + * Enable/Disable volume attributes stored as EA for root file system. + * Supported attributes are - + * 1. Extent-based Extended Attributes */ -__private_extern__ int -hfs_setextendedsecurity(struct hfsmount *hfsmp, int state) +hfs_set_volxattr(struct hfsmount *hfsmp, unsigned int xattrtype, int state) { struct BTreeIterator * iterator = NULL; struct filefork *btfile; @@ -797,33 +1946,38 @@ hfs_setextendedsecurity(struct hfsmount *hfsmp, int state) if (hfsmp->hfs_flags & HFS_STANDARD) { return (ENOTSUP); } + if (xattrtype != HFS_SET_XATTREXTENTS_STATE) { + return EINVAL; + } + + /* + * If there isn't an attributes b-tree then create one. + */ + if (hfsmp->hfs_attribute_vp == NULL) { + result = hfs_create_attr_btree(hfsmp, ATTRIBUTE_FILE_NODE_SIZE, + getnodecount(hfsmp, ATTRIBUTE_FILE_NODE_SIZE)); + if (result) { + return (result); + } + } MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK); + if (iterator == NULL) { + return (ENOMEM); + } bzero(iterator, sizeof(*iterator)); /* * Build a b-tree key. * We use the root's parent id (1) to hold this volume attribute. */ - (void) buildkey(kHFSRootParentID, XATTR_EXTENDEDSECURITY_NAME, - (HFSPlusAttrKey *)&iterator->key); + (void) hfs_buildattrkey(kHFSRootParentID, XATTR_XATTREXTENTS_NAME, + (HFSPlusAttrKey *)&iterator->key); /* Start a transaction for our changes. */ if (hfs_start_transaction(hfsmp) != 0) { result = EINVAL; - goto exit2; - } - /* - * If there isn't an attributes b-tree then create one. - */ - if (hfsmp->hfs_attribute_vp == NULL) { - lockflags = hfs_systemfile_lock(hfsmp, SFL_EXTENTS, HFS_EXCLUSIVE_LOCK); - result = hfs_create_attr_btree(hfsmp, ATTRIBUTE_FILE_NODE_SIZE, - getnodecount(hfsmp, ATTRIBUTE_FILE_NODE_SIZE)); - hfs_systemfile_unlock(hfsmp, lockflags); - if (result) { - goto exit1; - } + goto exit; } btfile = VTOF(hfsmp->hfs_attribute_vp); @@ -837,7 +1991,7 @@ hfs_setextendedsecurity(struct hfsmount *hfsmp, int state) } else { FSBufferDescriptor btdata; HFSPlusAttrData attrdata; - UInt16 datasize; + u_int16_t datasize; datasize = sizeof(attrdata); btdata.bufferAddress = &attrdata; @@ -858,65 +2012,24 @@ hfs_setextendedsecurity(struct hfsmount *hfsmp, int state) (void) BTFlushPath(btfile); hfs_systemfile_unlock(hfsmp, lockflags); -exit1: + /* Finish the transaction of our changes. */ hfs_end_transaction(hfsmp); -exit2: - FREE(iterator, M_TEMP); - - if (result == 0) { - if (state == 0) - vfs_clearextendedsecurity(HFSTOVFS(hfsmp)); - else - vfs_setextendedsecurity(HFSTOVFS(hfsmp)); - printf("hfs: %s extended security on %s\n", - state == 0 ? "disabling" : "enabling", hfsmp->vcbVN); - } - - return MacToVFSError(result); -} - -/* - * Check for extended security (ACLs). - */ -__private_extern__ -void -hfs_checkextendedsecurity(struct hfsmount *hfsmp) -{ - struct BTreeIterator * iterator; - struct filefork *btfile; - int lockflags; - int result; - if (hfsmp->hfs_flags & HFS_STANDARD || - hfsmp->hfs_attribute_vp == NULL) { - return; + /* Update the state in the mount point */ + HFS_MOUNT_LOCK(hfsmp, TRUE); + if (state == 0) { + hfsmp->hfs_flags &= ~HFS_XATTR_EXTENTS; + } else { + hfsmp->hfs_flags |= HFS_XATTR_EXTENTS; } + HFS_MOUNT_UNLOCK(hfsmp, TRUE); - MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK); - bzero(iterator, sizeof(*iterator)); - - /* - * Build a b-tree key. - * We use the root's parent id (1) to hold this volume attribute. - */ - (void) buildkey(kHFSRootParentID, XATTR_EXTENDEDSECURITY_NAME, - (HFSPlusAttrKey *)&iterator->key); - - btfile = VTOF(hfsmp->hfs_attribute_vp); - - lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_EXCLUSIVE_LOCK); - - /* Check for our attribute. */ - result = BTSearchRecord(btfile, iterator, NULL, NULL, NULL); - - hfs_systemfile_unlock(hfsmp, lockflags); - FREE(iterator, M_TEMP); - - if (result == 0) { - vfs_setextendedsecurity(HFSTOVFS(hfsmp)); - printf("hfs mount: enabling extended security on %s\n", hfsmp->vcbVN); +exit: + if (iterator) { + FREE(iterator, M_TEMP); } + return MacToVFSError(result); } @@ -988,10 +2101,11 @@ hfs_attrkeycompare(HFSPlusAttrKey *searchKey, HFSPlusAttrKey *trialKey) /* - * buildkey - build an Attribute b-tree key + * hfs_buildattrkey - build an Attribute b-tree key */ -static int -buildkey(u_int32_t fileID, const char *attrname, HFSPlusAttrKey *key) +__private_extern__ +int +hfs_buildattrkey(u_int32_t fileID, const char *attrname, HFSPlusAttrKey *key) { int result = 0; size_t unicodeBytes = 0; @@ -1000,7 +2114,7 @@ buildkey(u_int32_t fileID, const char *attrname, HFSPlusAttrKey *key) /* * Convert filename from UTF-8 into Unicode */ - result = utf8_decodestr(attrname, strlen(attrname), key->attrName, + result = utf8_decodestr((const u_int8_t *)attrname, strlen(attrname), key->attrName, &unicodeBytes, sizeof(key->attrName), 0, 0); if (result) { if (result != ENAMETOOLONG) @@ -1026,22 +2140,24 @@ buildkey(u_int32_t fileID, const char *attrname, HFSPlusAttrKey *key) static int getnodecount(struct hfsmount *hfsmp, size_t nodesize) { - int avedatasize; - int recpernode; - int count; - - avedatasize = sizeof(u_int16_t); /* index slot */ - avedatasize += kHFSPlusAttrKeyMinimumLength + HFS_AVERAGE_NAME_SIZE * sizeof(u_int16_t); - avedatasize += sizeof(HFSPlusAttrData) + 32; + u_int64_t freebytes; + u_int64_t calcbytes; - recpernode = (nodesize - sizeof(BTNodeDescriptor)) / avedatasize; + /* + * 10.4: Scale base on current catalog file size (20 %) up to 20 MB. + * 10.5: Attempt to be as big as the catalog clump size. + * + * Use no more than 10 % of the remaining free space. + */ + freebytes = (u_int64_t)hfs_freeblks(hfsmp, 0) * (u_int64_t)hfsmp->blockSize; - count = (hfsmp->hfs_filecount + hfsmp->hfs_dircount) / 8; - count /= recpernode; + calcbytes = MIN(hfsmp->hfs_catalog_cp->c_datafork->ff_size / 5, 20 * 1024 * 1024); - /* XXX should also consider volume size XXX */ + calcbytes = MAX(calcbytes, hfsmp->hfs_catalog_cp->c_datafork->ff_clumpsize); + + calcbytes = MIN(calcbytes, freebytes / 10); - return (MAX(count, (int)(1024 * 1024) / (int)nodesize)); + return (MAX(2, (int)(calcbytes / nodesize))); } @@ -1065,7 +2181,7 @@ getmaxinlineattrsize(struct vnode * attrvp) } maxsize = nodesize; maxsize -= sizeof(BTNodeDescriptor); /* minus node descriptor */ - maxsize -= 3 * sizeof(UInt16); /* minus 3 index slots */ + maxsize -= 3 * sizeof(u_int16_t); /* minus 3 index slots */ maxsize /= 2; /* 2 key/rec pairs minumum */ maxsize -= sizeof(HFSPlusAttrKey); /* minus maximum key size */ maxsize -= sizeof(HFSPlusAttrData) - 2; /* minus data header */ @@ -1074,4 +2190,313 @@ getmaxinlineattrsize(struct vnode * attrvp) return (maxsize); } +/* + * Initialize vnode for attribute data I/O. + * + * On success, + * - returns zero + * - the attrdata vnode is initialized as hfsmp->hfs_attrdata_vp + * - an iocount is taken on the attrdata vnode which exists + * for the entire duration of the mount. It is only dropped + * during unmount + * - the attrdata cnode is not locked + * + * On failure, + * - returns non-zero value + * - the caller does not have to worry about any locks or references + */ +int init_attrdata_vnode(struct hfsmount *hfsmp) +{ + vnode_t vp; + int result = 0; + struct cat_desc cat_desc; + struct cat_attr cat_attr; + struct cat_fork cat_fork; + int newvnode_flags = 0; + + bzero(&cat_desc, sizeof(cat_desc)); + cat_desc.cd_parentcnid = kHFSRootParentID; + cat_desc.cd_nameptr = (const u_int8_t *)hfs_attrdatafilename; + cat_desc.cd_namelen = strlen(hfs_attrdatafilename); + cat_desc.cd_cnid = kHFSAttributeDataFileID; + /* Tag vnode as system file, note that we can still use cluster I/O */ + cat_desc.cd_flags |= CD_ISMETA; + + bzero(&cat_attr, sizeof(cat_attr)); + cat_attr.ca_linkcount = 1; + cat_attr.ca_mode = S_IFREG; + cat_attr.ca_fileid = cat_desc.cd_cnid; + cat_attr.ca_blocks = hfsmp->totalBlocks; + + /* + * The attribute data file is a virtual file that spans the + * entire file system space. + * + * Each extent-based attribute occupies a unique portion of + * in this virtual file. The cluster I/O is done using actual + * allocation block offsets so no additional mapping is needed + * for the VNOP_BLOCKMAP call. + * + * This approach allows the attribute data to be cached without + * incurring the high cost of using a separate vnode per attribute. + * + * Since we need to acquire the attribute b-tree file lock anyways, + * the virtual file doesn't introduce any additional serialization. + */ + bzero(&cat_fork, sizeof(cat_fork)); + cat_fork.cf_size = (u_int64_t)hfsmp->totalBlocks * (u_int64_t)hfsmp->blockSize; + cat_fork.cf_blocks = hfsmp->totalBlocks; + cat_fork.cf_extents[0].startBlock = 0; + cat_fork.cf_extents[0].blockCount = cat_fork.cf_blocks; + + result = hfs_getnewvnode(hfsmp, NULL, NULL, &cat_desc, 0, &cat_attr, + &cat_fork, &vp, &newvnode_flags); + if (result == 0) { + hfsmp->hfs_attrdata_vp = vp; + hfs_unlock(VTOC(vp)); + } + return (result); +} + +/* + * Read an extent based attribute. + */ +static int +read_attr_data(struct hfsmount *hfsmp, uio_t uio, size_t datasize, HFSPlusExtentDescriptor *extents) +{ + vnode_t evp = hfsmp->hfs_attrdata_vp; + int bufsize; + int iosize; + int attrsize; + int blksize; + int i; + int result = 0; + + hfs_lock_truncate(VTOC(evp), HFS_SHARED_LOCK); + + bufsize = (int)uio_resid(uio); + attrsize = (int)datasize; + blksize = (int)hfsmp->blockSize; + + /* + * Read the attribute data one extent at a time. + * For the typical case there is only one extent. + */ + for (i = 0; (attrsize > 0) && (bufsize > 0) && (extents[i].startBlock != 0); ++i) { + iosize = (int)extents[i].blockCount * blksize; + iosize = MIN(iosize, attrsize); + iosize = MIN(iosize, bufsize); + uio_setresid(uio, iosize); + uio_setoffset(uio, (u_int64_t)extents[i].startBlock * (u_int64_t)blksize); + + result = cluster_read(evp, uio, VTOF(evp)->ff_size, IO_SYNC | IO_UNIT); + +#if HFS_XATTR_VERBOSE + printf("hfs: read_attr_data: cr iosize %d [%d, %d] (%d)\n", + iosize, extents[i].startBlock, extents[i].blockCount, result); +#endif + if (result) + break; + attrsize -= iosize; + bufsize -= iosize; + } + uio_setresid(uio, bufsize); + uio_setoffset(uio, datasize); + + hfs_unlock_truncate(VTOC(evp), 0); + return (result); +} + +/* + * Write an extent based attribute. + */ +static int +write_attr_data(struct hfsmount *hfsmp, uio_t uio, size_t datasize, HFSPlusExtentDescriptor *extents) +{ + vnode_t evp = hfsmp->hfs_attrdata_vp; + off_t filesize; + int bufsize; + int attrsize; + int iosize; + int blksize; + int i; + int result = 0; + + hfs_lock_truncate(VTOC(evp), HFS_SHARED_LOCK); + + bufsize = uio_resid(uio); + attrsize = (int) datasize; + blksize = (int) hfsmp->blockSize; + filesize = VTOF(evp)->ff_size; + + /* + * Write the attribute data one extent at a time. + */ + for (i = 0; (attrsize > 0) && (bufsize > 0) && (extents[i].startBlock != 0); ++i) { + iosize = (int)extents[i].blockCount * blksize; + iosize = MIN(iosize, attrsize); + iosize = MIN(iosize, bufsize); + uio_setresid(uio, iosize); + uio_setoffset(uio, (u_int64_t)extents[i].startBlock * (u_int64_t)blksize); + + result = cluster_write(evp, uio, filesize, filesize, filesize, + (off_t) 0, IO_SYNC | IO_UNIT); +#if HFS_XATTR_VERBOSE + printf("hfs: write_attr_data: cw iosize %d [%d, %d] (%d)\n", + iosize, extents[i].startBlock, extents[i].blockCount, result); +#endif + if (result) + break; + attrsize -= iosize; + bufsize -= iosize; + } + uio_setresid(uio, bufsize); + uio_setoffset(uio, datasize); + + hfs_unlock_truncate(VTOC(evp), 0); + return (result); +} + +/* + * Allocate blocks for an extent based attribute. + */ +static int +alloc_attr_blks(struct hfsmount *hfsmp, size_t attrsize, size_t extentbufsize, HFSPlusExtentDescriptor *extents, int *blocks) +{ + int blkcnt; + int startblk; + int lockflags; + int i; + int maxextents; + int result = 0; + + startblk = hfsmp->hfs_metazone_end; + blkcnt = howmany(attrsize, hfsmp->blockSize); + if (blkcnt > (int)hfs_freeblks(hfsmp, 0)) { + return (ENOSPC); + } + *blocks = blkcnt; + maxextents = extentbufsize / sizeof(HFSPlusExtentDescriptor); + + lockflags = hfs_systemfile_lock(hfsmp, SFL_BITMAP, HFS_EXCLUSIVE_LOCK); + + for (i = 0; (blkcnt > 0) && (i < maxextents); i++) { + result = BlockAllocate(hfsmp, startblk, blkcnt, blkcnt, 0, + &extents[i].startBlock, &extents[i].blockCount); +#if HFS_XATTR_VERBOSE + printf("hfs: alloc_attr_blks: BA blkcnt %d [%d, %d] (%d)\n", + blkcnt, extents[i].startBlock, extents[i].blockCount, result); +#endif + if (result) { + extents[i].startBlock = 0; + extents[i].blockCount = 0; + break; + } + blkcnt -= extents[i].blockCount; + startblk = extents[i].startBlock + extents[i].blockCount; + } + /* + * If it didn't fit in the extents buffer then bail. + */ + if (blkcnt) { + result = ENOSPC; + +#if HFS_XATTR_VERBOSE + printf("hfs: alloc_attr_blks: unexpected failure, %d blocks unallocated\n", blkcnt); +#endif + for (; i >= 0; i--) { + if ((blkcnt = extents[i].blockCount) != 0) { + (void) BlockDeallocate(hfsmp, extents[i].startBlock, blkcnt, 0); + extents[i].startBlock = 0; + extents[i].blockCount = 0; + } + } + } + + hfs_systemfile_unlock(hfsmp, lockflags); + return MacToVFSError(result); +} + +/* + * Release blocks from an extent based attribute. + */ +static void +free_attr_blks(struct hfsmount *hfsmp, int blkcnt, HFSPlusExtentDescriptor *extents) +{ + vnode_t evp = hfsmp->hfs_attrdata_vp; + int remblks = blkcnt; + int lockflags; + int i; + + lockflags = hfs_systemfile_lock(hfsmp, SFL_BITMAP, HFS_EXCLUSIVE_LOCK); + + for (i = 0; (remblks > 0) && (extents[i].blockCount != 0); i++) { + if (extents[i].blockCount > (u_int32_t)blkcnt) { +#if HFS_XATTR_VERBOSE + printf("hfs: free_attr_blks: skipping bad extent [%d, %d]\n", + extents[i].startBlock, extents[i].blockCount); +#endif + extents[i].blockCount = 0; + continue; + } + if (extents[i].startBlock == 0) { + break; + } + (void)BlockDeallocate(hfsmp, extents[i].startBlock, extents[i].blockCount, 0); + remblks -= extents[i].blockCount; + extents[i].startBlock = 0; + extents[i].blockCount = 0; + +#if HFS_XATTR_VERBOSE + printf("hfs: free_attr_blks: BlockDeallocate [%d, %d]\n", + extents[i].startBlock, extents[i].blockCount); +#endif + /* Discard any resident pages for this block range. */ + if (evp) { + off_t start, end; + + start = (u_int64_t)extents[i].startBlock * (u_int64_t)hfsmp->blockSize; + end = start + (u_int64_t)extents[i].blockCount * (u_int64_t)hfsmp->blockSize; + (void) ubc_msync(hfsmp->hfs_attrdata_vp, start, end, &start, UBC_INVALIDATE); + } + } + + hfs_systemfile_unlock(hfsmp, lockflags); +} + +static int +has_overflow_extents(HFSPlusForkData *forkdata) +{ + u_int32_t blocks; + + if (forkdata->extents[7].blockCount == 0) + return (0); + + blocks = forkdata->extents[0].blockCount + + forkdata->extents[1].blockCount + + forkdata->extents[2].blockCount + + forkdata->extents[3].blockCount + + forkdata->extents[4].blockCount + + forkdata->extents[5].blockCount + + forkdata->extents[6].blockCount + + forkdata->extents[7].blockCount; + + return (forkdata->totalBlocks > blocks); +} +static int +count_extent_blocks(int maxblks, HFSPlusExtentRecord extents) +{ + int blocks; + int i; + + for (i = 0, blocks = 0; i < kHFSPlusExtentDensity; ++i) { + /* Ignore obvious bogus extents. */ + if (extents[i].blockCount > (u_int32_t)maxblks) + continue; + if (extents[i].startBlock == 0 || extents[i].blockCount == 0) + break; + blocks += extents[i].blockCount; + } + return (blocks); +}