X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/2d21ac55c334faf3a56e5634905ed6987fc787d4..7e41aa883dd258f888d0470250eead40a53ef1f5:/bsd/hfs/hfs_xattr.c diff --git a/bsd/hfs/hfs_xattr.c b/bsd/hfs/hfs_xattr.c index 37dca768b..c63dce8ea 100644 --- a/bsd/hfs/hfs_xattr.c +++ b/bsd/hfs/hfs_xattr.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004-2007 Apple Inc. All rights reserved. + * Copyright (c) 2004-2014 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * @@ -35,8 +35,10 @@ #include #include #include +#include #include #include +#include #include "hfs.h" #include "hfs_cnode.h" @@ -45,6 +47,7 @@ #include "hfs_endian.h" #include "hfs_btreeio.h" #include "hfs_fsctl.h" +#include "hfs_cprotect.h" #include "hfscommon/headers/BTreesInternal.h" @@ -59,22 +62,22 @@ 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 (128 * 1024) -#define HFS_MAXATTRBLKS (32 * 1024) - /* HFS Internal Names */ #define XATTR_EXTENDEDSECURITY_NAME "system.extendedsecurity" #define XATTR_XATTREXTENTS_NAME "system.xattrextents" -/* 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_hidden_fields (struct cnode *cp, u_int8_t *finderinfo); + const char hfs_attrdatafilename[] = "Attribute Data"; static int listattr_callback(const HFSPlusAttrKey *key, const HFSPlusAttrData *data, @@ -122,14 +125,21 @@ hfs_vnop_getnamedstream(struct vnop_getnamedstream_args* ap) if ( !S_ISREG(cp->c_mode) ) { return (EPERM); } - if ((error = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK))) { +#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, HFS_LOCK_DEFAULT))) { return (error); } - if (!RSRC_FORK_EXISTS(cp) && (ap->a_operation != NS_OPEN)) { + if ((!hfs_has_rsrc(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); + error = hfs_vgetrsrc(VTOHFS(vp), vp, svpp); hfs_unlock(cp); return (error); @@ -158,10 +168,21 @@ hfs_vnop_makenamedstream(struct vnop_makenamedstream_args* ap) if ( !S_ISREG(cp->c_mode) ) { return (EPERM); } - if ((error = hfs_lock(cp, HFS_EXCLUSIVE_LOCK))) { +#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, HFS_LOCK_DEFAULT))) { return (error); } - error = hfs_vgetrsrc(VTOHFS(vp), vp, svpp, TRUE); + error = hfs_vgetrsrc(VTOHFS(vp), vp, svpp); hfs_unlock(cp); return (error); @@ -174,7 +195,7 @@ int hfs_vnop_removenamedstream(struct vnop_removenamedstream_args* ap) { vnode_t svp = ap->a_svp; - struct cnode *scp; + cnode_t *scp = VTOC(svp); int error = 0; /* @@ -183,28 +204,54 @@ hfs_vnop_removenamedstream(struct vnop_removenamedstream_args* ap) if (bcmp(ap->a_name, XATTR_RESOURCEFORK_NAME, sizeof(XATTR_RESOURCEFORK_NAME)) != 0) { return (ENOATTR); } - scp = VTOC(svp); - - /* Take truncate lock before taking cnode lock. */ - hfs_lock_truncate(scp, TRUE); - if ((error = hfs_lock(scp, HFS_EXCLUSIVE_LOCK))) { - goto out; +#if HFS_COMPRESSION + if (hfs_hides_rsrc(ap->a_context, scp, 1)) { + /* do nothing */ + return 0; } - if (VTOF(svp)->ff_size != 0) { +#endif /* HFS_COMPRESSION */ + + hfs_lock_truncate(scp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); + if (VTOF(svp)->ff_size) { + // hfs_truncate will deal with the cnode lock error = hfs_truncate(svp, 0, IO_NDELAY, 0, ap->a_context); } - hfs_unlock(scp); -out: - hfs_unlock_truncate(scp, TRUE); - return (error); + hfs_unlock_truncate(scp, HFS_LOCK_DEFAULT); + + return error; } #endif +/* Zero out the date added field for the specified cnode */ +static int hfs_zero_hidden_fields (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) || S_ISLNK(cp->c_attr.ca_mode)) { + struct FndrExtendedFileInfo *extinfo = (struct FndrExtendedFileInfo *)finfo; + extinfo->document_id = 0; + extinfo->date_added = 0; + extinfo->write_gen_counter = 0; + } else if (S_ISDIR(cp->c_attr.ca_mode)) { + struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)finfo; + extinfo->document_id = 0; + extinfo->date_added = 0; + extinfo->write_gen_counter = 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) /* @@ -223,28 +270,31 @@ hfs_vnop_getxattr(struct vnop_getxattr_args *ap) struct cnode *cp; struct hfsmount *hfsmp; uio_t uio = ap->a_uio; - struct BTreeIterator * iterator = NULL; - struct filefork *btfile; - FSBufferDescriptor btdata; - HFSPlusAttrRecord * recp = NULL; size_t bufsize; - u_int16_t datasize; - int lockflags; int result; 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))) { + if ((result = hfs_lock(cp, HFS_SHARED_LOCK, HFS_LOCK_DEFAULT))) { 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_hidden_fields (cp, finderinfo); /* Don't expose a symlink's private type/creator. */ if (vnode_islnk(vp)) { @@ -262,7 +312,7 @@ hfs_vnop_getxattr(struct vnop_getxattr_args *ap) *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)&finderinfo , bufsize, uio); @@ -272,19 +322,27 @@ hfs_vnop_getxattr(struct vnop_getxattr_args *ap) /* 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 ( !S_ISREG(cp->c_mode) ) { return (EPERM); } - if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK))) { + if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) { return (result); } - if ( !RSRC_FORK_EXISTS(cp)) { + namelen = cp->c_desc.cd_namelen; + + if (!hfs_has_rsrc(cp)) { hfs_unlock(cp); return (ENOATTR); } hfsmp = VTOHFS(vp); - result = hfs_vgetrsrc(hfsmp, vp, &rvp, TRUE); + if ((cp->c_flag & C_DELETED) && (namelen == 0)) { + openunlinked = 1; + } + + result = hfs_vgetrsrc(hfsmp, vp, &rvp); hfs_unlock(cp); if (result) { return (result); @@ -292,7 +350,41 @@ hfs_vnop_getxattr(struct vnop_getxattr_args *ap) 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); @@ -306,162 +398,279 @@ hfs_vnop_getxattr(struct vnop_getxattr_args *ap) return (EPERM); } - if ((result = hfs_lock(cp, HFS_SHARED_LOCK))) { + if ((result = hfs_lock(cp, HFS_SHARED_LOCK, HFS_LOCK_DEFAULT))) { return (result); } - /* Bail if we don't have any extended attributes. */ + + /* Check for non-rsrc, non-finderinfo EAs */ + result = hfs_getxattr_internal (cp, ap, VTOHFS(cp->c_vp), 0); + + hfs_unlock(cp); + + return MacToVFSError(result); +} + +// Has same limitations as hfs_getxattr_internal below +int hfs_xattr_read(vnode_t vp, const char *name, void *data, size_t *size) +{ + char uio_buf[UIO_SIZEOF(1)]; + uio_t uio = uio_createwithbuffer(1, 0, UIO_SYSSPACE, UIO_READ, uio_buf, + sizeof(uio_buf)); + + uio_addiov(uio, CAST_USER_ADDR_T(data), *size); + + struct vnop_getxattr_args args = { + .a_uio = uio, + .a_name = name, + .a_size = size + }; + + return hfs_getxattr_internal(VTOC(vp), &args, VTOHFS(vp), 0); +} + +/* + * 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) || - (cp->c_attr.ca_recflags & kHFSHasAttributesMask) == 0) { - result = 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) - bufsize += uio_resid(uio); - bufsize = MAX(bufsize, sizeof(HFSPlusAttrRecord)); - MALLOC(recp, HFSPlusAttrRecord *, bufsize, M_TEMP, M_WAITOK); + + /* Allocate memory for reading in the attribute record. This buffer is + * big enough to read in all types of attribute records. It is not big + * enough to read inline attribute data which is read in later. + */ + MALLOC(recp, HFSPlusAttrRecord *, sizeof(HFSPlusAttrRecord), M_TEMP, M_WAITOK); if (recp == NULL) { result = ENOMEM; goto exit; } btdata.bufferAddress = recp; - btdata.itemSize = bufsize; + btdata.itemSize = sizeof(HFSPlusAttrRecord); btdata.itemCount = 1; - result = hfs_buildattrkey(VTOC(vp)->c_fileid, ap->a_name, (HFSPlusAttrKey *)&iterator->key); - if (result) - goto exit; + result = hfs_buildattrkey(target_id, 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; } - + + /* + * 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) { - 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", - VTOC(vp)->c_fileid, 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 > uio_resid(uio)) - result = ERANGE; - else - result = uiomove((caddr_t) &recp->attrData.attrData , recp->attrData.attrSize, uio); - } - break; - case kHFSPlusAttrForkData: - if (datasize < sizeof(HFSPlusAttrForkData)) { - printf("hfs_getxattr: %d,%s invalid record size %d (expecting %lu)\n", - VTOC(vp)->c_fileid, ap->a_name, datasize, sizeof(HFSPlusAttrForkData)); - result = ENOATTR; - break; - } - *ap->a_size = recp->forkData.theFork.logicalSize; - if (uio == NULL) { - break; - } - if (*ap->a_size > uio_resid(uio)) { - result = ERANGE; + /* 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: vol=%s %d,%s invalid record size %d (expecting %lu)\n", + hfsmp->vcbVN, 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)) { + /* User provided buffer is not large enough for the xattr data */ + result = ERANGE; + } else { + /* Previous BTreeSearchRecord() read in only the attribute record, + * and not the attribute data. Now allocate enough memory for + * both attribute record and data, and read the attribute record again. + */ + bufsize = sizeof(HFSPlusAttrData) - 2 + recp->attrData.attrSize; + FREE(recp, M_TEMP); + MALLOC(recp, HFSPlusAttrRecord *, bufsize, M_TEMP, M_WAITOK); + if (recp == NULL) { + result = ENOMEM; + goto exit; + } + + btdata.bufferAddress = recp; + btdata.itemSize = bufsize; + btdata.itemCount = 1; + + bzero(iterator, sizeof(*iterator)); + result = hfs_buildattrkey(target_id, ap->a_name, (HFSPlusAttrKey *)&iterator->key); + if (result) { + goto exit; + } + + /* Lookup the attribute record and inline data */ + 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) { + result = ENOATTR; + } + goto exit; + } + + /* Copy-out the attribute data to the user buffer */ + *ap->a_size = recp->attrData.attrSize; + result = uiomove((caddr_t) &recp->attrData.attrData , recp->attrData.attrSize, uio); + } + } 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; + + /* Extent-Based EAs */ + case kHFSPlusAttrForkData: { + if (datasize < sizeof(HFSPlusAttrForkData)) { + printf("hfs_getxattr: vol=%s %d,%s invalid record size %d (expecting %lu)\n", + hfsmp->vcbVN, target_id, ap->a_name, datasize, sizeof(HFSPlusAttrForkData)); + result = ENOATTR; 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; + *ap->a_size = recp->forkData.theFork.logicalSize; + if (uio == NULL) { 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 */ + 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 > howmany(HFS_XATTR_MAXSIZE, hfsmp->blockSize)) { + 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; } - /* Grab the next 8 extents. */ - bcopy(&recp->overflowExtents.extents[0], extentptr, sizeof(HFSPlusExtentRecord)); + 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->overflowExtents.extents); - } - hfs_systemfile_unlock(hfsmp, lockflags); - - if (blkcnt < totalblocks) { - result = ENOATTR; - } else { - result = read_attr_data(hfsmp, uio, attrlen, extentbuf); + 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); } - FREE(extentbuf, M_TEMP); - - } else /* No overflow extents. */ { - result = read_attr_data(hfsmp, uio, recp->forkData.theFork.logicalSize, recp->forkData.theFork.extents); + break; } - break; - - default: - result = ENOATTR; - break; + + default: + /* We only support Extent or inline EAs. Default to ENOATTR for anything else */ + result = ENOATTR; + break; } -exit: - hfs_unlock(cp); - + +exit: if (iterator) { FREE(iterator, M_TEMP); } @@ -469,13 +678,14 @@ exit: FREE(recp, M_TEMP); } - return MacToVFSError(result); + return result; + } + /* * Set the data of an extended attribute. */ -__private_extern__ int hfs_vnop_setxattr(struct vnop_setxattr_args *ap) /* @@ -493,19 +703,10 @@ hfs_vnop_setxattr(struct vnop_setxattr_args *ap) struct cnode *cp = NULL; struct hfsmount *hfsmp; uio_t uio = ap->a_uio; - struct BTreeIterator * iterator = NULL; - struct filefork *btfile = NULL; size_t attrsize; - FSBufferDescriptor btdata; - HFSPlusAttrRecord *recp = NULL; - HFSPlusExtentDescriptor *extentptr = NULL; - HFSPlusAttrRecord attrdata; /* 90 bytes */ void * user_data_ptr = NULL; - int started_transaction = 0; - int lockflags = 0; - int exists; - int allocatedblks = 0; int result; + time_t orig_ctime=VTOC(vp)->c_ctime; if (ap->a_name == NULL || ap->a_name[0] == '\0') { return (EINVAL); /* invalid name */ @@ -514,16 +715,31 @@ 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; + } +#endif /* HFS_COMPRESSION */ + + check_for_tracked_file(vp, orig_ctime, NAMESPACE_HANDLER_METADATA_WRITE_OP, NSPACE_REARM_NO_ARG); + /* Set the Finder Info. */ if (bcmp(ap->a_name, XATTR_FINDERINFO_NAME, sizeof(XATTR_FINDERINFO_NAME)) == 0) { 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; + u_int32_t write_gen_counter = 0; + u_int32_t document_id = 0; attrsize = sizeof(VTOC(vp)->c_finderinfo); - if (uio_resid(uio) != attrsize) { + if ((user_size_t)uio_resid(uio) != attrsize) { return (ERANGE); } /* Grab the new Finder Info data. */ @@ -532,7 +748,7 @@ hfs_vnop_setxattr(struct vnop_setxattr_args *ap) } fip = (struct FndrFileInfo *)&finderinfo; - if ((result = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK))) { + if ((result = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) { return (result); } cp = VTOC(vp); @@ -554,25 +770,74 @@ hfs_vnop_setxattr(struct vnop_setxattr_args *ap) } } + /* Grab the current date added from the cnode */ + dateadded = hfs_get_dateadded (cp); + if (S_ISREG(cp->c_attr.ca_mode) || S_ISLNK(cp->c_attr.ca_mode)) { + struct FndrExtendedFileInfo *extinfo = (struct FndrExtendedFileInfo *)((u_int8_t*)cp->c_finderinfo + 16); + /* + * Grab generation counter directly from the cnode + * instead of calling hfs_get_gencount(), because + * for zero generation count values hfs_get_gencount() + * lies and bumps it up to one. + */ + write_gen_counter = extinfo->write_gen_counter; + document_id = extinfo->document_id; + } else if (S_ISDIR(cp->c_attr.ca_mode)) { + struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)((u_int8_t*)cp->c_finderinfo + 16); + write_gen_counter = extinfo->write_gen_counter; + document_id = extinfo->document_id; + } + + /* + * Zero out the finder info's reserved fields like date added, + * generation counter, and document id to ignore user's attempts + * to set it + */ + hfs_zero_hidden_fields(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 /* empty */ { + } else { /* empty */ /* attr doesn't exists and "replace" was specified. */ if (ap->a_options & XATTR_REPLACE) { hfs_unlock(cp); return (ENOATTR); } } + + /* + * Now restore the date added and other reserved fields to the finderinfo to + * be written out. Advance to the 2nd half of the finderinfo to write them + * out 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) || S_ISLNK(cp->c_attr.ca_mode)) { + struct FndrExtendedFileInfo *extinfo = (struct FndrExtendedFileInfo *)finfo; + extinfo->date_added = OSSwapHostToBigInt32(dateadded); + extinfo->write_gen_counter = write_gen_counter; + extinfo->document_id = document_id; + } else if (S_ISDIR(cp->c_attr.ca_mode)) { + struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)finfo; + extinfo->date_added = OSSwapHostToBigInt32(dateadded); + extinfo->write_gen_counter = write_gen_counter; + extinfo->document_id = document_id; + } + /* Set the cnode's Finder Info. */ - if (attrsize == sizeof(cp->c_finderinfo)) + if (attrsize == sizeof(cp->c_finderinfo)) { bcopy(&finderinfo[0], finderinfo_start, attrsize); - else + } 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; @@ -585,12 +850,13 @@ hfs_vnop_setxattr(struct vnop_setxattr_args *ap) * 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; + if (fdFlags & OSSwapHostToBigConstInt16(kFinderInvisibleMask)) { + cp->c_bsdflags |= UF_HIDDEN; + } else { + cp->c_bsdflags &= ~UF_HIDDEN; + } - result = hfs_update(vp, FALSE); + result = hfs_update(vp, 0); hfs_unlock(cp); return (result); @@ -598,16 +864,19 @@ hfs_vnop_setxattr(struct vnop_setxattr_args *ap) /* 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 ((result = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK))) { + if ((result = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) { return (result); } cp = VTOC(vp); + namelen = cp->c_desc.cd_namelen; - if (RSRC_FORK_EXISTS(cp)) { + if (hfs_has_rsrc(cp)) { /* attr exists and "create" was specified. */ if (ap->a_options & XATTR_CREATE) { hfs_unlock(cp); @@ -620,13 +889,44 @@ hfs_vnop_setxattr(struct vnop_setxattr_args *ap) return (ENOATTR); } } - result = hfs_vgetrsrc(hfsmp, vp, &rvp, TRUE); + + /* + * 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); hfs_unlock(cp); if (result) { return (result); } - /* VNOP_WRITE will update timestamps accordingly */ + /* 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, HFS_LOCK_DEFAULT))) { + 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); } @@ -639,19 +939,23 @@ hfs_vnop_setxattr(struct vnop_setxattr_args *ap) attrsize = uio_resid(uio); /* Enforce an upper limit. */ - if (attrsize > HFS_MAXATTRIBUTESIZE) { - return (E2BIG); + if (attrsize > HFS_XATTR_MAXSIZE) { + result = E2BIG; + goto exit; } /* - * Attempt to copy the users attr data before taking any locks. + * Attempt to copy the users attr data before taking any locks, + * only if it will be an inline attribute. For larger attributes, + * the data will be directly read from the uio. */ 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) { - return (ENOMEM); + result = ENOMEM; + goto exit; } result = uiomove((caddr_t)user_data_ptr, attrsize, uio); @@ -660,28 +964,97 @@ hfs_vnop_setxattr(struct vnop_setxattr_args *ap) } } - result = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK); + result = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); if (result) { goto exit; } 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)); +} + +// Has same limitations as hfs_setxattr_internal below +int hfs_xattr_write(vnode_t vp, const char *name, const void *data, size_t size) +{ + struct vnop_setxattr_args args = { + .a_vp = vp, + .a_name = name, + }; + + return hfs_setxattr_internal(VTOC(vp), data, size, &args, VTOHFS(vp), 0); +} + +/* + * 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, const void *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 exit; } started_transaction = 1; - + /* * Once we started the transaction, nobody can compete * with us, so make sure this file is still there. */ - if (cp->c_flag & C_NOEXISTS) { + if ((cp) && (cp->c_flag & C_NOEXISTS)) { result = ENOENT; goto exit; } - + /* * If there isn't an attributes b-tree then create one. */ @@ -696,7 +1069,6 @@ hfs_vnop_setxattr(struct vnop_setxattr_args *ap) 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); /* Build the b-tree key. */ @@ -706,18 +1078,18 @@ hfs_vnop_setxattr(struct vnop_setxattr_args *ap) goto exit; } bzero(iterator, sizeof(*iterator)); - result = hfs_buildattrkey(VTOC(vp)->c_fileid, ap->a_name, (HFSPlusAttrKey *)&iterator->key); + result = hfs_buildattrkey(target_id, ap->a_name, (HFSPlusAttrKey *)&iterator->key); if (result) { 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; @@ -728,6 +1100,7 @@ hfs_vnop_setxattr(struct vnop_setxattr_args *ap) 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; @@ -735,13 +1108,17 @@ hfs_vnop_setxattr(struct vnop_setxattr_args *ap) int extentblks; u_int32_t *keystartblk; int i; - - /* Check if volume supports extent-based attributes */ - if ((hfsmp->hfs_flags & HFS_XATTR_EXTENTS) == 0) { - result = E2BIG; + + 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); @@ -760,8 +1137,13 @@ hfs_vnop_setxattr(struct vnop_setxattr_args *ap) /* Copy data into the blocks. */ result = write_attr_data(hfsmp, uio, attrsize, extentptr); if (result) { - printf("hfs_setxattr: write_attr_data err (%d) %s:%s\n", - result, vnode_name(vp) ? vnode_name(vp) : "", ap->a_name); + if (vp) { + const char *name = vnode_getname(vp); + printf("hfs_setxattr: write_attr_data vol=%s err (%d) %s:%s\n", + hfsmp->vcbVN, result, name ? name : "", ap->a_name); + if (name) + vnode_putname(name); + } goto exit; } @@ -769,12 +1151,16 @@ hfs_vnop_setxattr(struct vnop_setxattr_args *ap) if (exists) { result = remove_attribute_records(hfsmp, iterator); if (result) { - printf("hfs_setxattr: remove_attribute_records err (%d) %s:%s\n", - result, vnode_name(vp) ? vnode_name(vp) : "", ap->a_name); - goto exit; + if (vp) { + const char *name = vnode_getname(vp); + printf("hfs_setxattr: remove_attribute_records vol=%s err (%d) %s:%s\n", + hfsmp->vcbVN, 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) { @@ -784,29 +1170,27 @@ hfs_vnop_setxattr(struct vnop_setxattr_args *ap) 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(VTOC(vp)->c_fileid, ap->a_name, (HFSPlusAttrKey *)&iterator->key); - + + (void) hfs_buildattrkey(target_id, ap->a_name, (HFSPlusAttrKey *)&iterator->key); + result = BTInsertRecord(btfile, iterator, &btdata, btdata.itemSize); if (result) { -#if HFS_XATTR_VERBOSE - printf("hfs_setxattr: BTInsertRecord err (%d) %s:%s\n", - MacToVFSError(result), vnode_name(vp) ? vnode_name(vp) : "", ap->a_name); -#endif + printf ("hfs_setxattr: BTInsertRecord(): vol=%s %d,%s err=%d\n", + hfsmp->vcbVN, 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. */ @@ -814,28 +1198,28 @@ hfs_vnop_setxattr(struct vnop_setxattr_args *ap) 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 err (%d) %s:%s\n", - MacToVFSError(result), vnode_name(vp) ? vnode_name(vp) : "", ap->a_name); + printf ("hfs_setxattr: BTInsertRecord() overflow: vol=%s %d,%s err=%d\n", + hfsmp->vcbVN, target_id, ap->a_name, result); goto exit; } extentblks = count_extent_blocks(blkcnt, recp->overflowExtents.extents); blkcnt -= extentblks; } - } else /* Inline data */ { + } 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); @@ -847,42 +1231,54 @@ hfs_vnop_setxattr(struct vnop_setxattr_args *ap) 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 (user_data_ptr) - bcopy(user_data_ptr, &recp->attrData.attrData, attrsize); - else + 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; } } - - (void) hfs_buildattrkey(VTOC(vp)->c_fileid, ap->a_name, (HFSPlusAttrKey *)&iterator->key); - + + (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: if (btfile && started_transaction) { (void) BTFlushPath(btfile); } - if (lockflags) { - hfs_systemfile_unlock(hfsmp, lockflags); - } + hfs_systemfile_unlock(hfsmp, lockflags); if (result == 0) { - 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; + 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_flag |= C_MODIFIED; + 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); } - (void) hfs_update(vp, 0); } if (started_transaction) { if (result && allocatedblks) { @@ -890,15 +1286,7 @@ exit: } hfs_end_transaction(hfsmp); } - if (cp) { - hfs_unlock(cp); - } - if (result == 0) { - HFS_KNOTE(vp, NOTE_ATTRIB); - } - if (user_data_ptr) { - FREE(user_data_ptr, M_TEMP); - } + if (recp) { FREE(recp, M_TEMP); } @@ -908,13 +1296,16 @@ exit: if (iterator) { FREE(iterator, M_TEMP); } - return (result == btNotFound ? ENOATTR : MacToVFSError(result)); + + return result; } + + + /* * Remove an extended attribute. */ -__private_extern__ int hfs_vnop_removexattr(struct vnop_removexattr_args *ap) /* @@ -933,6 +1324,7 @@ hfs_vnop_removexattr(struct vnop_removexattr_args *ap) struct BTreeIterator * iterator = NULL; 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 */ @@ -942,6 +1334,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; + } +#endif /* HFS_COMPRESSION */ + + check_for_tracked_file(vp, orig_ctime, NAMESPACE_HANDLER_METADATA_DELETE_OP, NSPACE_REARM_NO_ARG); + /* 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; @@ -949,22 +1349,26 @@ hfs_vnop_removexattr(struct vnop_removexattr_args *ap) if ( !vnode_isreg(vp) ) { return (EPERM); } - if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK))) { + if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) { return (result); } - if ( !RSRC_FORK_EXISTS(cp)) { + if (!hfs_has_rsrc(cp)) { hfs_unlock(cp); return (ENOATTR); } - result = hfs_vgetrsrc(hfsmp, vp, &rvp, TRUE); + result = hfs_vgetrsrc(hfsmp, vp, &rvp); hfs_unlock(cp); if (result) { return (result); } - hfs_lock_truncate(VTOC(rvp), TRUE); - if ((result = hfs_lock(VTOC(rvp), HFS_EXCLUSIVE_LOCK))) { - hfs_unlock_truncate(cp, TRUE); + hfs_lock_truncate(VTOC(rvp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); + + // Tell UBC now before we take the cnode lock and start the transaction + hfs_ubc_setsize(rvp, 0, false); + + if ((result = hfs_lock(VTOC(rvp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) { + hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT); vnode_put(rvp); return (result); } @@ -973,7 +1377,7 @@ hfs_vnop_removexattr(struct vnop_removexattr_args *ap) * hfs_truncate() and hfs_update() */ if ((result = hfs_start_transaction(hfsmp))) { - hfs_unlock_truncate(cp, TRUE); + hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT); hfs_unlock(cp); vnode_put(rvp); return (result); @@ -983,11 +1387,11 @@ hfs_vnop_removexattr(struct vnop_removexattr_args *ap) if (result == 0) { cp->c_touch_chgtime = TRUE; cp->c_flag |= C_MODIFIED; - result = hfs_update(vp, FALSE); + result = hfs_update(vp, 0); } hfs_end_transaction(hfsmp); - hfs_unlock_truncate(VTOC(rvp), TRUE); + hfs_unlock_truncate(VTOC(rvp), HFS_LOCK_DEFAULT); hfs_unlock(VTOC(rvp)); vnode_put(rvp); @@ -997,34 +1401,85 @@ hfs_vnop_removexattr(struct vnop_removexattr_args *ap) if (bcmp(ap->a_name, XATTR_FINDERINFO_NAME, sizeof(XATTR_FINDERINFO_NAME)) == 0) { void * finderinfo_start; int finderinfo_size; - - if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK))) { + u_int8_t finderinfo[32]; + u_int32_t date_added, write_gen_counter, document_id; + u_int8_t *finfo = NULL; + + if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) { return (result); } - - /* Symlink's don't have an external type/creator. */ + + /* 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_hidden_fields (cp, finderinfo); + + /* Don't expose a symlink's private type/creator. */ if (vnode_islnk(vp)) { - /* Skip over type/creator fields. */ + 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); + } + + /* + * 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) || S_ISLNK(cp->c_attr.ca_mode)) { + struct FndrExtendedFileInfo *extinfo = (struct FndrExtendedFileInfo *)finfo; + date_added = extinfo->date_added; + write_gen_counter = extinfo->write_gen_counter; + document_id = extinfo->document_id; + } else if (S_ISDIR(cp->c_attr.ca_mode)) { + struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)finfo; + date_added = extinfo->date_added; + write_gen_counter = extinfo->write_gen_counter; + document_id = extinfo->document_id; + } + + 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); } - if (bcmp(finderinfo_start, emptyfinfo, finderinfo_size) == 0) { - hfs_unlock(cp); - return (ENOATTR); - } - bzero(finderinfo_start, finderinfo_size); - + + + /* Now restore the date added */ + if (S_ISREG(cp->c_attr.ca_mode) || S_ISLNK(cp->c_attr.ca_mode)) { + struct FndrExtendedFileInfo *extinfo = (struct FndrExtendedFileInfo *)finfo; + extinfo->date_added = date_added; + extinfo->write_gen_counter = write_gen_counter; + extinfo->document_id = document_id; + } else if (S_ISDIR(cp->c_attr.ca_mode)) { + struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)finfo; + extinfo->date_added = date_added; + extinfo->write_gen_counter = write_gen_counter; + extinfo->document_id = document_id; + } + /* Updating finderInfo updates change time and modified time */ cp->c_touch_chgtime = TRUE; cp->c_flag |= C_MODIFIED; - hfs_update(vp, FALSE); - + hfs_update(vp, 0); + hfs_unlock(cp); - + return (0); } /* @@ -1043,8 +1498,8 @@ hfs_vnop_removexattr(struct vnop_removexattr_args *ap) } bzero(iterator, sizeof(*iterator)); - if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK))) { - return (result); + if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) { + goto exit_nolock; } result = hfs_buildattrkey(cp->c_fileid, ap->a_name, (HFSPlusAttrKey *)&iterator->key); @@ -1071,6 +1526,7 @@ hfs_vnop_removexattr(struct vnop_removexattr_args *ap) result = file_attribute_exist(hfsmp, cp->c_fileid); if (result == 0) { cp->c_attr.ca_recflags &= ~kHFSHasAttributesMask; + cp->c_flag |= C_MODIFIED; } if (result == EEXIST) { result = 0; @@ -1081,6 +1537,7 @@ hfs_vnop_removexattr(struct vnop_removexattr_args *ap) /* 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; + cp->c_flag |= C_MODIFIED; } (void) hfs_update(vp, 0); } @@ -1088,9 +1545,7 @@ hfs_vnop_removexattr(struct vnop_removexattr_args *ap) hfs_end_transaction(hfsmp); exit: hfs_unlock(cp); - if (result == 0) { - HFS_KNOTE(vp, NOTE_ATTRIB); - } +exit_nolock: FREE(iterator, M_TEMP); return MacToVFSError(result); } @@ -1167,7 +1622,7 @@ out: * - The Allocation Bitmap file must be locked exclusive. * - The iterator key must be initialized. */ -static int +int remove_attribute_records(struct hfsmount *hfsmp, BTreeIterator * iterator) { struct filefork *btfile; @@ -1196,17 +1651,15 @@ remove_attribute_records(struct hfsmount *hfsmp, BTreeIterator * iterator) int extentblks; u_int32_t *keystartblk; -#if HFS_XATTR_VERBOSE if (datasize < sizeof(HFSPlusAttrForkData)) { - printf("remove_attribute_records: bad record size %d (expecting %d)\n", datasize, sizeof(HFSPlusAttrForkData)); + printf("hfs: remove_attribute_records: bad record size %d (expecting %lu)\n", datasize, sizeof(HFSPlusAttrForkData)); } -#endif totalblks = attrdata.forkData.theFork.totalBlocks; /* Process the first 8 extents. */ extentblks = count_extent_blocks(totalblks, attrdata.forkData.theFork.extents); if (extentblks > totalblks) - panic("remove_attribute_records: corruption..."); + panic("hfs: remove_attribute_records: corruption..."); if (BTDeleteRecord(btfile, iterator) == 0) { free_attr_blks(hfsmp, extentblks, attrdata.forkData.theFork.extents); } @@ -1221,15 +1674,15 @@ remove_attribute_records(struct hfsmount *hfsmp, BTreeIterator * iterator) if (result || (attrdata.recordType != kHFSPlusAttrExtents) || (datasize < sizeof(HFSPlusAttrExtents))) { - printf("remove_attribute_records: BTSearchRecord %d (%d), totalblks %d\n", - MacToVFSError(result), attrdata.recordType != kHFSPlusAttrExtents, totalblks); + printf("hfs: remove_attribute_records: BTSearchRecord: vol=%s, err=%d (%d), totalblks %d\n", + hfsmp->vcbVN, 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("remove_attribute_records: corruption..."); + panic("hfs: remove_attribute_records: corruption..."); if (BTDeleteRecord(btfile, iterator) == 0) { free_attr_blks(hfsmp, extentblks, attrdata.overflowExtents.extents); } @@ -1247,7 +1700,6 @@ exit: /* * Retrieve the list of extended attribute names. */ -__private_extern__ int hfs_vnop_listxattr(struct vnop_listxattr_args *ap) /* @@ -1267,37 +1719,60 @@ hfs_vnop_listxattr(struct vnop_listxattr_args *ap) struct BTreeIterator * iterator = NULL; struct filefork *btfile; struct listattr_callback_state state; - void * finderinfo_start; - int finderinfo_size; 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; + + /* + * Take the truncate lock; this serializes us against the ioctl + * to truncate data & reset the decmpfs state + * in the compressed file handler. + */ + hfs_lock_truncate(cp, HFS_SHARED_LOCK, HFS_LOCK_DEFAULT); - if ((result = hfs_lock(cp, HFS_SHARED_LOCK))) { + /* Now the regular cnode lock (shared) */ + if ((result = hfs_lock(cp, HFS_SHARED_LOCK, HFS_LOCK_DEFAULT))) { + hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT); 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_hidden_fields (cp, finderinfo); + /* Don't expose a symlink's private type/creator. */ if (vnode_islnk(vp)) { - /* Skip over type/creator fields. */ - 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); - } + 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_start, emptyfinfo, finderinfo_size) != 0) { + 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)) { + } else if ((user_size_t)uio_resid(uio) < sizeof(XATTR_FINDERINFO_NAME)) { result = ERANGE; goto exit; } else { @@ -1308,17 +1783,25 @@ hfs_vnop_listxattr(struct vnop_listxattr_args *ap) } } /* If Resource Fork is non-empty then export it's name. */ - if (S_ISREG(cp->c_mode) && RSRC_FORK_EXISTS(cp)) { - if (uio == NULL) { - *ap->a_size += sizeof(XATTR_RESOURCEFORK_NAME); - } else if (uio_resid(uio) < sizeof(XATTR_RESOURCEFORK_NAME)) { - result = ERANGE; - goto exit; - } else { - result = uiomove(XATTR_RESOURCEFORK_NAME, - sizeof(XATTR_RESOURCEFORK_NAME), uio); - if (result) + if (S_ISREG(cp->c_mode) && hfs_has_rsrc(cp)) { +#if HFS_COMPRESSION + if ((ap->a_options & XATTR_SHOWCOMPRESSION) || + !compressed || + !decmpfs_hides_rsrc(ap->a_context, VTOCMP(vp)) + ) +#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; + } } } /* @@ -1372,6 +1855,11 @@ hfs_vnop_listxattr(struct vnop_listxattr_args *ap) 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. @@ -1390,22 +1878,24 @@ exit: if (user_start) { vsunlock(user_start, user_len, TRUE); } - FREE(iterator, M_TEMP); - + if (iterator) { + FREE(iterator, M_TEMP); + } hfs_unlock(cp); + hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT); 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) { @@ -1421,7 +1911,7 @@ listattr_callback(const HFSPlusAttrKey *key, __unused const HFSPlusAttrData *dat /* Convert the attribute name into UTF-8. */ result = utf8_encodestr(key->attrName, key->attrNameLen * sizeof(UniChar), - (u_int8_t *)attrname, &bytecount, sizeof(attrname), '/', 0); + (u_int8_t *)attrname, (size_t *)&bytecount, sizeof(attrname), '/', 0); if (result) { state->result = result; return (0); /* stop */ @@ -1431,6 +1921,11 @@ listattr_callback(const HFSPlusAttrKey *key, __unused const HFSPlusAttrData *dat if (xattr_protected(attrname)) return (1); /* continue */ +#if HFS_COMPRESSION + if (!state->showcompressed && decmpfs_hides_xattr(state->ctx, VTOCMP(state->vp), attrname) ) + return 1; /* continue */ +#endif /* HFS_COMPRESSION */ + if (state->uio == NULL) { state->size += bytecount; } else { @@ -1456,45 +1951,64 @@ listattr_callback(const HFSPlusAttrKey *key, __unused const HFSPlusAttrData *dat * * This function takes the necessary locks on the attribute * b-tree file and the allocation (bitmap) file. + * + * NOTE: Upon sucecss, this function will return with an open + * transaction. The reason we do it this way is because when we + * delete the last attribute, we must make sure the flag in the + * catalog record that indicates there are no more records is cleared. + * The caller is responsible for doing this and *must* do it before + * ending the transaction. */ -__private_extern__ int -hfs_removeallattr(struct hfsmount *hfsmp, u_int32_t fileid) +hfs_removeallattr(struct hfsmount *hfsmp, u_int32_t fileid, + bool *open_transaction) { - BTreeIterator *iterator; + BTreeIterator *iterator = NULL; HFSPlusAttrKey *key; struct filefork *btfile; - int result, lockflags; + int result, lockflags = 0; + + *open_transaction = false; + + if (hfsmp->hfs_attribute_vp == NULL) + return 0; - if (hfsmp->hfs_attribute_vp == NULL) { - return (0); - } btfile = VTOF(hfsmp->hfs_attribute_vp); MALLOC(iterator, BTreeIterator *, sizeof(BTreeIterator), M_TEMP, M_WAITOK); + if (iterator == NULL) { + return (ENOMEM); + } bzero(iterator, sizeof(BTreeIterator)); key = (HFSPlusAttrKey *)&iterator->key; /* Loop until there are no more attributes for this file id */ - for(;;) { + do { + if (!*open_transaction) + lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK); + + (void) hfs_buildattrkey(fileid, NULL, key); + result = BTIterateRecord(btfile, kBTreeNextRecord, iterator, NULL, NULL); + if (result || key->fileID != fileid) + goto exit; + + hfs_systemfile_unlock(hfsmp, lockflags); + lockflags = 0; + + if (*open_transaction) { + hfs_end_transaction(hfsmp); + *open_transaction = false; + } + if (hfs_start_transaction(hfsmp) != 0) { result = EINVAL; goto exit; } - /* Lock the attribute b-tree and the allocation (bitmap) files */ + *open_transaction = true; + 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; - } result = remove_attribute_records(hfsmp, iterator); #if HFS_XATTR_VERBOSE @@ -1502,12 +2016,22 @@ hfs_removeallattr(struct hfsmount *hfsmp, u_int32_t fileid) printf("hfs_removeallattr: unexpected err %d\n", result); } #endif + } while (!result); + +exit: + FREE(iterator, M_TEMP); + + if (lockflags) hfs_systemfile_unlock(hfsmp, lockflags); + + result = result == btNotFound ? 0 : MacToVFSError(result); + + if (result && *open_transaction) { hfs_end_transaction(hfsmp); + *open_transaction = false; } -exit: - FREE(iterator, M_TEMP); - return (result == btNotFound ? 0: MacToVFSError(result)); + + return result; } __private_extern__ @@ -1530,10 +2054,8 @@ hfs_xattr_init(struct hfsmount * hfsmp) /* * Enable/Disable volume attributes stored as EA for root file system. * Supported attributes are - - * 1. ACLs - * 2. Extent-based Extended Attributes + * 1. Extent-based Extended Attributes */ -__private_extern__ int hfs_set_volxattr(struct hfsmount *hfsmp, unsigned int xattrtype, int state) { @@ -1545,6 +2067,9 @@ hfs_set_volxattr(struct hfsmount *hfsmp, unsigned int xattrtype, 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. @@ -1567,18 +2092,8 @@ hfs_set_volxattr(struct hfsmount *hfsmp, unsigned int xattrtype, int state) * Build a b-tree key. * We use the root's parent id (1) to hold this volume attribute. */ - if (xattrtype == HFS_SETACLSTATE) { - /* ACL */ - (void) hfs_buildattrkey(kHFSRootParentID, XATTR_EXTENDEDSECURITY_NAME, - (HFSPlusAttrKey *)&iterator->key); - } else if (xattrtype == HFS_SET_XATTREXTENTS_STATE) { - /* Extent-based extended attributes */ - (void) hfs_buildattrkey(kHFSRootParentID, XATTR_XATTREXTENTS_NAME, - (HFSPlusAttrKey *)&iterator->key); - } else { - result = EINVAL; - goto exit; - } + (void) hfs_buildattrkey(kHFSRootParentID, XATTR_XATTREXTENTS_NAME, + (HFSPlusAttrKey *)&iterator->key); /* Start a transaction for our changes. */ if (hfs_start_transaction(hfsmp) != 0) { @@ -1621,91 +2136,21 @@ hfs_set_volxattr(struct hfsmount *hfsmp, unsigned int xattrtype, int state) /* Finish the transaction of our changes. */ hfs_end_transaction(hfsmp); -exit: - if (iterator) { - FREE(iterator, M_TEMP); - } - if (result == 0) { - if (xattrtype == HFS_SETACLSTATE) { - if (state == 0) { - vfs_clearextendedsecurity(HFSTOVFS(hfsmp)); - } else { - vfs_setextendedsecurity(HFSTOVFS(hfsmp)); - } - } else { - /* HFS_SET_XATTREXTENTS_STATE */ - 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); - } - } - - return MacToVFSError(result); -} - - /* - * Check for volume attributes stored as EA for root file system. - * Supported attributes are - - * 1. ACLs - * 2. Extent-based Extended Attributes - */ -__private_extern__ -void -hfs_check_volxattr(struct hfsmount *hfsmp, unsigned int xattrtype) -{ - struct BTreeIterator * iterator; - struct filefork *btfile; - int lockflags; - int result; - - if (hfsmp->hfs_flags & HFS_STANDARD || - hfsmp->hfs_attribute_vp == NULL) { - return; - } - - MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK); - if (iterator == NULL) { - return; - } - bzero(iterator, sizeof(*iterator)); - - /* - * Build a b-tree key. - * We use the root's parent id (1) to hold this volume attribute. - */ - if (xattrtype == HFS_SETACLSTATE) { - /* ACLs */ - (void) hfs_buildattrkey(kHFSRootParentID, XATTR_EXTENDEDSECURITY_NAME, - (HFSPlusAttrKey *)&iterator->key); + /* Update the state in the mount point */ + hfs_lock_mount (hfsmp); + if (state == 0) { + hfsmp->hfs_flags &= ~HFS_XATTR_EXTENTS; } else { - /* Extent-based extended attributes */ - (void) hfs_buildattrkey(kHFSRootParentID, XATTR_XATTREXTENTS_NAME, - (HFSPlusAttrKey *)&iterator->key); + hfsmp->hfs_flags |= HFS_XATTR_EXTENTS; } - btfile = VTOF(hfsmp->hfs_attribute_vp); - - lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_EXCLUSIVE_LOCK); + hfs_unlock_mount (hfsmp); - /* Check for our attribute. */ - result = BTSearchRecord(btfile, iterator, NULL, NULL, NULL); - - hfs_systemfile_unlock(hfsmp, lockflags); - FREE(iterator, M_TEMP); - - if (result == 0) { - if (xattrtype == HFS_SETACLSTATE) { - vfs_setextendedsecurity(HFSTOVFS(hfsmp)); - } else { - HFS_MOUNT_LOCK(hfsmp, TRUE); - hfsmp->hfs_flags |= HFS_XATTR_EXTENTS; - HFS_MOUNT_UNLOCK(hfsmp, TRUE); - } +exit: + if (iterator) { + FREE(iterator, M_TEMP); } + return MacToVFSError(result); } @@ -1766,11 +2211,12 @@ hfs_attrkeycompare(HFSPlusAttrKey *searchKey, HFSPlusAttrKey *trialKey) /* * Names are equal; compare startBlock */ - if (searchKey->startBlock == trialKey->startBlock) + if (searchKey->startBlock == trialKey->startBlock) { return (0); - else + } else { return (searchKey->startBlock < trialKey->startBlock ? -1 : 1); } + } return result; } @@ -1850,7 +2296,7 @@ getmaxinlineattrsize(struct vnode * attrvp) size_t maxsize; if (attrvp != NULL) { - (void) hfs_lock(VTOC(attrvp), HFS_SHARED_LOCK); + (void) hfs_lock(VTOC(attrvp), HFS_SHARED_LOCK, HFS_LOCK_DEFAULT); if (BTGetInformation(VTOF(attrvp), 0, &btinfo) == 0) nodesize = btinfo.nodeSize; hfs_unlock(VTOC(attrvp)); @@ -1867,76 +2313,70 @@ getmaxinlineattrsize(struct vnode * attrvp) } /* - * Get a referenced vnode for attribute data I/O. + * 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 */ -static int -get_attr_data_vnode(struct hfsmount *hfsmp, vnode_t *vpp) +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; - vp = hfsmp->hfs_attrdata_vp; - if (vp == NULLVP) { - struct cat_desc cat_desc; - struct cat_attr cat_attr; - struct cat_fork cat_fork; - - /* We don't tag it as a system file since we intend to use cluster I/O. */ - 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; - - 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); - if (result == 0) { - HFS_MOUNT_LOCK(hfsmp, 1); - /* Check if someone raced us for creating this vnode. */ - if (hfsmp->hfs_attrdata_vp != NULLVP) { - HFS_MOUNT_UNLOCK(hfsmp, 1); - vnode_put(vp); - vnode_recycle(vp); - vp = hfsmp->hfs_attrdata_vp; - } else { - hfsmp->hfs_attrdata_vp = vp; - HFS_MOUNT_UNLOCK(hfsmp, 1); - /* Keep a reference on this vnode until unmount */ - vnode_ref_ext(vp, O_EVTONLY); - hfs_unlock(VTOC(vp)); - } - } - } else { - if ((result = vnode_get(vp))) - vp = NULLVP; + /* + * 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)); } - *vpp = vp; return (result); } @@ -1946,18 +2386,15 @@ get_attr_data_vnode(struct hfsmount *hfsmp, vnode_t *vpp) static int read_attr_data(struct hfsmount *hfsmp, uio_t uio, size_t datasize, HFSPlusExtentDescriptor *extents) { - vnode_t evp = NULLVP; + vnode_t evp = hfsmp->hfs_attrdata_vp; int bufsize; - int iosize; + int64_t iosize; int attrsize; int blksize; int i; int result = 0; - if ((result = get_attr_data_vnode(hfsmp, &evp))) { - return (result); - } - hfs_lock_truncate(VTOC(evp), 0); + hfs_lock_truncate(VTOC(evp), HFS_SHARED_LOCK, HFS_LOCK_DEFAULT); bufsize = (int)uio_resid(uio); attrsize = (int)datasize; @@ -1968,7 +2405,7 @@ read_attr_data(struct hfsmount *hfsmp, uio_t uio, size_t datasize, HFSPlusExtent * 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 = extents[i].blockCount * blksize; iosize = MIN(iosize, attrsize); iosize = MIN(iosize, bufsize); uio_setresid(uio, iosize); @@ -1977,7 +2414,7 @@ read_attr_data(struct hfsmount *hfsmp, uio_t uio, size_t datasize, HFSPlusExtent result = cluster_read(evp, uio, VTOF(evp)->ff_size, IO_SYNC | IO_UNIT); #if HFS_XATTR_VERBOSE - printf("read_attr_data: cr iosize %d [%d, %d] (%d)\n", + printf("hfs: read_attr_data: cr iosize %lld [%d, %d] (%d)\n", iosize, extents[i].startBlock, extents[i].blockCount, result); #endif if (result) @@ -1988,8 +2425,7 @@ read_attr_data(struct hfsmount *hfsmp, uio_t uio, size_t datasize, HFSPlusExtent uio_setresid(uio, bufsize); uio_setoffset(uio, datasize); - hfs_unlock_truncate(VTOC(evp), 0); - vnode_put(evp); + hfs_unlock_truncate(VTOC(evp), HFS_LOCK_DEFAULT); return (result); } @@ -1999,20 +2435,16 @@ read_attr_data(struct hfsmount *hfsmp, uio_t uio, size_t datasize, HFSPlusExtent static int write_attr_data(struct hfsmount *hfsmp, uio_t uio, size_t datasize, HFSPlusExtentDescriptor *extents) { - vnode_t evp = NULLVP; + vnode_t evp = hfsmp->hfs_attrdata_vp; off_t filesize; int bufsize; int attrsize; - int iosize; + int64_t iosize; int blksize; int i; int result = 0; - /* Get exclusive use of attribute data vnode. */ - if ((result = get_attr_data_vnode(hfsmp, &evp))) { - return (result); - } - hfs_lock_truncate(VTOC(evp), 0); + hfs_lock_truncate(VTOC(evp), HFS_SHARED_LOCK, HFS_LOCK_DEFAULT); bufsize = uio_resid(uio); attrsize = (int) datasize; @@ -2023,7 +2455,7 @@ write_attr_data(struct hfsmount *hfsmp, uio_t uio, size_t datasize, HFSPlusExten * 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 = extents[i].blockCount * blksize; iosize = MIN(iosize, attrsize); iosize = MIN(iosize, bufsize); uio_setresid(uio, iosize); @@ -2032,7 +2464,7 @@ write_attr_data(struct hfsmount *hfsmp, uio_t uio, size_t datasize, HFSPlusExten result = cluster_write(evp, uio, filesize, filesize, filesize, (off_t) 0, IO_SYNC | IO_UNIT); #if HFS_XATTR_VERBOSE - printf("write_attr_data: cw iosize %d [%d, %d] (%d)\n", + printf("hfs: write_attr_data: cw iosize %lld [%d, %d] (%d)\n", iosize, extents[i].startBlock, extents[i].blockCount, result); #endif if (result) @@ -2043,8 +2475,7 @@ write_attr_data(struct hfsmount *hfsmp, uio_t uio, size_t datasize, HFSPlusExten uio_setresid(uio, bufsize); uio_setoffset(uio, datasize); - hfs_unlock_truncate(VTOC(evp), 0); - vnode_put(evp); + hfs_unlock_truncate(VTOC(evp), HFS_LOCK_DEFAULT); return (result); } @@ -2072,10 +2503,21 @@ alloc_attr_blks(struct hfsmount *hfsmp, size_t attrsize, size_t extentbufsize, H 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, 0, + /* Try allocating and see if we find something decent */ + result = BlockAllocate(hfsmp, startblk, blkcnt, blkcnt, 0, &extents[i].startBlock, &extents[i].blockCount); + /* + * If we couldn't find anything, then re-try the allocation but allow + * journal flushes. + */ + if (result == dskFulErr) { + result = BlockAllocate(hfsmp, startblk, blkcnt, blkcnt, HFS_ALLOC_FLUSHTXN, + &extents[i].startBlock, &extents[i].blockCount); + } + + #if HFS_XATTR_VERBOSE - printf("alloc_attr_blks: BA blkcnt %d [%d, %d] (%d)\n", + printf("hfs: alloc_attr_blks: BA blkcnt %d [%d, %d] (%d)\n", blkcnt, extents[i].startBlock, extents[i].blockCount, result); #endif if (result) { @@ -2093,11 +2535,11 @@ alloc_attr_blks(struct hfsmount *hfsmp, size_t attrsize, size_t extentbufsize, H result = ENOSPC; #if HFS_XATTR_VERBOSE - printf("alloc_attr_blks: unexpected failure, %d blocks unallocated\n", blkcnt); + printf("hfs: alloc_attr_blks: unexpected failure, %d blocks unallocated\n", blkcnt); #endif - for (; i <= 0; i--) { + for (; i >= 0; i--) { if ((blkcnt = extents[i].blockCount) != 0) { - (void) BlockDeallocate(hfsmp, extents[i].startBlock, blkcnt); + (void) BlockDeallocate(hfsmp, extents[i].startBlock, blkcnt, 0); extents[i].startBlock = 0; extents[i].blockCount = 0; } @@ -2114,20 +2556,17 @@ alloc_attr_blks(struct hfsmount *hfsmp, size_t attrsize, size_t extentbufsize, H static void free_attr_blks(struct hfsmount *hfsmp, int blkcnt, HFSPlusExtentDescriptor *extents) { - vnode_t evp = NULLVP; + vnode_t evp = hfsmp->hfs_attrdata_vp; int remblks = blkcnt; int lockflags; int i; - if (get_attr_data_vnode(hfsmp, &evp) != 0) { - evp = NULLVP; - } 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("free_attr_blks: skipping bad extent [%d, %d]\n", + printf("hfs: free_attr_blks: skipping bad extent [%d, %d]\n", extents[i].startBlock, extents[i].blockCount); #endif extents[i].blockCount = 0; @@ -2136,13 +2575,13 @@ free_attr_blks(struct hfsmount *hfsmp, int blkcnt, HFSPlusExtentDescriptor *exte if (extents[i].startBlock == 0) { break; } - (void)BlockDeallocate(hfsmp, extents[i].startBlock, extents[i].blockCount); + (void)BlockDeallocate(hfsmp, extents[i].startBlock, extents[i].blockCount, 0); + remblks -= extents[i].blockCount; extents[i].startBlock = 0; extents[i].blockCount = 0; - remblks -= extents[i].blockCount; #if HFS_XATTR_VERBOSE - printf("free_attr_blks: BlockDeallocate [%d, %d]\n", + printf("hfs: free_attr_blks: BlockDeallocate [%d, %d]\n", extents[i].startBlock, extents[i].blockCount); #endif /* Discard any resident pages for this block range. */ @@ -2156,9 +2595,6 @@ free_attr_blks(struct hfsmount *hfsmp, int blkcnt, HFSPlusExtentDescriptor *exte } hfs_systemfile_unlock(hfsmp, lockflags); - if (evp) { - vnode_put(evp); - } } static int @@ -2197,3 +2633,4 @@ count_extent_blocks(int maxblks, HFSPlusExtentRecord extents) } return (blocks); } +