#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/ubc.h>
#include <sys/utfconv.h>
#include <sys/vnode.h>
#include <sys/xattr.h>
#include <sys/fcntl.h>
#include <sys/fsctl.h>
#include <sys/vnode_internal.h>
#include <sys/kauth.h>
#include <sys/uio_internal.h>
#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 "hfs_cprotect.h"
#include "hfscommon/headers/BTreesInternal.h"
#define HFS_XATTR_VERBOSE 0
#define ATTRIBUTE_FILE_NODE_SIZE 8192
struct listattr_callback_state {
u_int32_t fileID;
int result;
uio_t uio;
size_t size;
#if HFS_COMPRESSION
int showcompressed;
vfs_context_t ctx;
vnode_t vp;
#endif
};
#define XATTR_EXTENDEDSECURITY_NAME "system.extendedsecurity"
#define XATTR_XATTREXTENTS_NAME "system.xattrextents"
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,
struct listattr_callback_state *state);
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
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;
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
if ((error = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) {
return (error);
}
if ((!hfs_has_rsrc(cp)
#if HFS_COMPRESSION
|| hide_rsrc
#endif
) && (ap->a_operation != NS_OPEN)) {
hfs_unlock(cp);
return (ENOATTR);
}
error = hfs_vgetrsrc(VTOHFS(vp), vp, svpp);
hfs_unlock(cp);
return (error);
}
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;
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
if ((error = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) {
return (error);
}
error = hfs_vgetrsrc(VTOHFS(vp), vp, svpp);
hfs_unlock(cp);
return (error);
}
int
hfs_vnop_removenamedstream(struct vnop_removenamedstream_args* ap)
{
vnode_t svp = ap->a_svp;
cnode_t *scp = VTOC(svp);
int error = 0;
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, scp, 1)) {
return 0;
}
#endif
hfs_lock_truncate(scp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT);
if (VTOF(svp)->ff_size) {
error = hfs_truncate(svp, 0, IO_NDELAY, 0, ap->a_context);
}
hfs_unlock_truncate(scp, HFS_LOCK_DEFAULT);
return error;
}
#endif
static int hfs_zero_hidden_fields (struct cnode *cp, u_int8_t *finderinfo)
{
u_int8_t *finfo = 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 -1;
}
return 0;
}
int
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;
size_t bufsize;
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);
if (decmpfs_hide && !(ap->a_options & XATTR_SHOWCOMPRESSION))
return ENOATTR;
#endif
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, HFS_LOCK_DEFAULT))) {
return (result);
}
bcopy(cp->c_finderinfo, finderinfo, sizeof(finderinfo));
hfs_unlock(cp);
hfs_zero_hidden_fields (cp, finderinfo);
if (vnode_islnk(vp)) {
struct FndrFileInfo *fip;
fip = (struct FndrFileInfo *)&finderinfo;
fip->fdType = 0;
fip->fdCreator = 0;
}
if (bcmp(finderinfo, emptyfinfo, sizeof(emptyfinfo)) == 0) {
return (ENOATTR);
}
if (uio == NULL) {
*ap->a_size = bufsize;
return (0);
}
if ((user_size_t)uio_resid(uio) < bufsize)
return (ERANGE);
result = uiomove((caddr_t)&finderinfo , bufsize, uio);
return (result);
}
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, HFS_LOCK_DEFAULT))) {
return (result);
}
namelen = cp->c_desc.cd_namelen;
if (!hfs_has_rsrc(cp)) {
hfs_unlock(cp);
return (ENOATTR);
}
hfsmp = VTOHFS(vp);
if ((cp->c_flag & C_DELETED) && (namelen == 0)) {
openunlinked = 1;
}
result = hfs_vgetrsrc(hfsmp, vp, &rvp);
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
result = VNOP_READ(rvp, uio, 0, ap->a_context);
#if HFS_COMPRESSION
if (decmpfs_hide &&
(result == 0) &&
(uio_resid(uio) == uio_size)) {
result = VNOP_READ(rvp, uio, 0, decmpfs_ctx);
}
#endif
}
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);
if (hfsmp->hfs_flags & HFS_STANDARD) {
return (EPERM);
}
if ((result = hfs_lock(cp, HFS_SHARED_LOCK, HFS_LOCK_DEFAULT))) {
return (result);
}
result = hfs_getxattr_internal (cp, ap, VTOHFS(cp->c_vp), 0);
hfs_unlock(cp);
return MacToVFSError(result);
}
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);
}
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;
}
if ((hfsmp->hfs_attribute_vp == NULL) ||
((cp) && (cp->c_attr.ca_recflags & kHFSHasAttributesMask) == 0)) {
result = ENOATTR;
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));
MALLOC(recp, HFSPlusAttrRecord *, sizeof(HFSPlusAttrRecord), M_TEMP, M_WAITOK);
if (recp == NULL) {
result = ENOMEM;
goto exit;
}
btdata.bufferAddress = recp;
btdata.itemSize = sizeof(HFSPlusAttrRecord);
btdata.itemCount = 1;
result = hfs_buildattrkey(target_id, ap->a_name, (HFSPlusAttrKey *)&iterator->key);
if (result) {
goto exit;
}
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;
}
switch (recp->recordType) {
case kHFSPlusAttrInlineData: {
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)) {
result = ERANGE;
} else {
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;
}
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;
}
*ap->a_size = recp->attrData.attrSize;
result = uiomove((caddr_t) &recp->attrData.attrData , recp->attrData.attrSize, uio);
}
}
break;
}
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;
}
*ap->a_size = recp->forkData.theFork.logicalSize;
if (uio == NULL) {
break;
}
if (*ap->a_size > (user_size_t)uio_resid(uio)) {
result = ERANGE;
break;
}
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;
if (totalblocks > howmany(HFS_XATTR_MAXSIZE, hfsmp->blockSize)) {
result = ERANGE;
break;
}
attrlen = recp->forkData.theFork.logicalSize;
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;
bcopy(&recp->forkData.theFork.extents[0], extentptr, sizeof(HFSPlusExtentRecord));
extentptr += kHFSPlusExtentDensity;
blkcnt = count_extent_blocks(totalblocks, recp->forkData.theFork.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;
}
bcopy(&recp->overflowExtents.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);
}
FREE(extentbuf, M_TEMP);
} else {
result = read_attr_data(hfsmp, uio, recp->forkData.theFork.logicalSize, recp->forkData.theFork.extents);
}
break;
}
default:
result = ENOATTR;
break;
}
exit:
if (iterator) {
FREE(iterator, M_TEMP);
}
if (recp) {
FREE(recp, M_TEMP);
}
return result;
}
int
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;
size_t attrsize;
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);
}
hfsmp = VTOHFS(vp);
if (VNODE_IS_RSRC(vp)) {
return (EPERM);
}
#if HFS_COMPRESSION
if (hfs_hides_xattr(ap->a_context, VTOC(vp), ap->a_name, 1) ) {
result = decmpfs_decompress_file(vp, VTOCMP(vp), -1, 1, 0);
if (result != 0)
return result;
}
#endif
check_for_tracked_file(vp, orig_ctime, NAMESPACE_HANDLER_METADATA_WRITE_OP, NSPACE_REARM_NO_ARG);
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 ((user_size_t)uio_resid(uio) != attrsize) {
return (ERANGE);
}
if ((result = uiomove((caddr_t)&finderinfo , attrsize, uio))) {
return (result);
}
fip = (struct FndrFileInfo *)&finderinfo;
if ((result = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) {
return (result);
}
cp = VTOC(vp);
if (vnode_islnk(vp)) {
finderinfo_start = &cp->c_finderinfo[8];
attrsize -= 8;
} else {
finderinfo_start = &cp->c_finderinfo[0];
if (fip->fdType == SWAP_BE32(kHardLinkFileType)) {
hfs_unlock(cp);
return (EPERM);
}
}
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);
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;
}
hfs_zero_hidden_fields(cp, finderinfo);
if (bcmp(finderinfo_start, emptyfinfo, attrsize)) {
if (ap->a_options & XATTR_CREATE) {
hfs_unlock(cp);
return (EEXIST);
}
} else {
if (ap->a_options & XATTR_REPLACE) {
hfs_unlock(cp);
return (ENOATTR);
}
}
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;
}
if (attrsize == sizeof(cp->c_finderinfo)) {
bcopy(&finderinfo[0], finderinfo_start, attrsize);
} else {
bcopy(&finderinfo[8], finderinfo_start, attrsize);
}
cp->c_touch_chgtime = TRUE;
cp->c_flag |= C_MODIFIED;
fdFlags = *((u_int16_t *) &cp->c_finderinfo[8]);
if (fdFlags & OSSwapHostToBigConstInt16(kFinderInvisibleMask)) {
cp->c_bsdflags |= UF_HIDDEN;
} else {
cp->c_bsdflags &= ~UF_HIDDEN;
}
result = hfs_update(vp, 0);
hfs_unlock(cp);
return (result);
}
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, HFS_LOCK_DEFAULT))) {
return (result);
}
cp = VTOC(vp);
namelen = cp->c_desc.cd_namelen;
if (hfs_has_rsrc(cp)) {
if (ap->a_options & XATTR_CREATE) {
hfs_unlock(cp);
return (EEXIST);
}
} else {
if (ap->a_options & XATTR_REPLACE) {
hfs_unlock(cp);
return (ENOATTR);
}
}
if ((cp->c_flag & C_DELETED) && (namelen == 0)) {
openunlinked = 1;
}
result = hfs_vgetrsrc(hfsmp, vp, &rvp);
hfs_unlock(cp);
if (result) {
return (result);
}
result = VNOP_WRITE(rvp, uio, 0, ap->a_context);
if (openunlinked) {
int vref;
vref = vnode_ref (rvp);
if (vref == 0) {
vnode_rele(rvp);
}
vnode_recycle (rvp);
} else {
if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) {
vnode_recycle (rvp);
vnode_put(rvp);
return result;
}
result = hfs_fsync (rvp, MNT_NOWAIT, 0, vfs_context_proc (ap->a_context));
hfs_unlock (cp);
}
vnode_put(rvp);
return (result);
}
if (hfsmp->hfs_flags & HFS_STANDARD) {
return (EPERM);
}
attrsize = uio_resid(uio);
if (attrsize > HFS_XATTR_MAXSIZE) {
result = E2BIG;
goto exit;
}
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;
}
result = uiomove((caddr_t)user_data_ptr, attrsize, uio);
if (result) {
goto exit;
}
}
result = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT);
if (result) {
goto exit;
}
cp = VTOC(vp);
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));
}
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);
}
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;
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;
}
if (hfs_start_transaction(hfsmp) != 0) {
result = EINVAL;
goto exit;
}
started_transaction = 1;
if ((cp) && (cp->c_flag & C_NOEXISTS)) {
result = ENOENT;
goto exit;
}
if (hfsmp->hfs_attribute_vp == NULL) {
result = hfs_create_attr_btree(hfsmp, ATTRIBUTE_FILE_NODE_SIZE,
getnodecount(hfsmp, ATTRIBUTE_FILE_NODE_SIZE));
if (result) {
goto exit;
}
}
if (hfsmp->hfs_max_inline_attrsize == 0) {
hfsmp->hfs_max_inline_attrsize = getmaxinlineattrsize(hfsmp->hfs_attribute_vp);
}
lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_EXCLUSIVE_LOCK);
MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
if (iterator == NULL) {
result = ENOMEM;
goto exit;
}
bzero(iterator, sizeof(*iterator));
result = hfs_buildattrkey(target_id, ap->a_name, (HFSPlusAttrKey *)&iterator->key);
if (result) {
goto exit;
}
btfile = VTOF(hfsmp->hfs_attribute_vp);
btdata.bufferAddress = &attrdata;
btdata.itemSize = sizeof(attrdata);
btdata.itemCount = 1;
exists = BTSearchRecord(btfile, iterator, &btdata, NULL, NULL) == 0;
if ((ap->a_options & XATTR_REPLACE) && !exists) {
result = ENOATTR;
goto exit;
}
if ((ap->a_options & XATTR_CREATE) && exists) {
result = EEXIST;
goto exit;
}
if (attrsize > hfsmp->hfs_max_inline_attrsize) {
size_t extentbufsize;
int blkcnt;
int extentblks;
u_int32_t *keystartblk;
int i;
if (uio == NULL) {
result = EPERM;
goto exit;
}
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;
}
result = write_attr_data(hfsmp, uio, attrsize, extentptr);
if (result) {
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;
}
if (exists) {
result = remove_attribute_records(hfsmp, iterator);
if (result) {
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;
}
}
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(): 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;
while (blkcnt > 0) {
*keystartblk += (u_int32_t)extentblks;
btdata.itemSize = sizeof(HFSPlusAttrExtents);
recp->recordType = kHFSPlusAttrExtents;
recp->overflowExtents.reserved = 0;
i += kHFSPlusExtentDensity;
bcopy(&extentptr[i], recp->overflowExtents.extents, sizeof(HFSPlusExtentRecord));
result = BTInsertRecord(btfile, iterator, &btdata, btdata.itemSize);
if (result) {
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 {
if (exists) {
result = remove_attribute_records(hfsmp, iterator);
if (result) {
goto exit;
}
}
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;
if (attrsize > 0) {
if (data_ptr) {
bcopy(data_ptr, &recp->attrData.attrData, attrsize);
} else {
if (uio == NULL) {
result = EPERM;
goto exit;
}
result = uiomove((caddr_t)&recp->attrData.attrData, attrsize, uio);
}
if (result) {
goto exit;
}
}
(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);
}
hfs_systemfile_unlock(hfsmp, lockflags);
if (result == 0) {
if (vp) {
cp = VTOC(vp);
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);
}
}
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;
}
int
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;
int lockflags;
int result;
time_t orig_ctime=VTOC(vp)->c_ctime;
if (ap->a_name == NULL || ap->a_name[0] == '\0') {
return (EINVAL);
}
hfsmp = VTOHFS(vp);
if (VNODE_IS_RSRC(vp)) {
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
check_for_tracked_file(vp, orig_ctime, NAMESPACE_HANDLER_METADATA_DELETE_OP, NSPACE_REARM_NO_ARG);
if (bcmp(ap->a_name, XATTR_RESOURCEFORK_NAME, sizeof(XATTR_RESOURCEFORK_NAME)) == 0) {
struct vnode *rvp = NULL;
if ( !vnode_isreg(vp) ) {
return (EPERM);
}
if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) {
return (result);
}
if (!hfs_has_rsrc(cp)) {
hfs_unlock(cp);
return (ENOATTR);
}
result = hfs_vgetrsrc(hfsmp, vp, &rvp);
hfs_unlock(cp);
if (result) {
return (result);
}
hfs_lock_truncate(VTOC(rvp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT);
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);
}
if ((result = hfs_start_transaction(hfsmp))) {
hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT);
hfs_unlock(cp);
vnode_put(rvp);
return (result);
}
result = hfs_truncate(rvp, (off_t)0, IO_NDELAY, 0, ap->a_context);
if (result == 0) {
cp->c_touch_chgtime = TRUE;
cp->c_flag |= C_MODIFIED;
result = hfs_update(vp, 0);
}
hfs_end_transaction(hfsmp);
hfs_unlock_truncate(VTOC(rvp), HFS_LOCK_DEFAULT);
hfs_unlock(VTOC(rvp));
vnode_put(rvp);
return (result);
}
if (bcmp(ap->a_name, XATTR_FINDERINFO_NAME, sizeof(XATTR_FINDERINFO_NAME)) == 0) {
void * finderinfo_start;
int finderinfo_size;
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);
}
bcopy(cp->c_finderinfo, finderinfo, sizeof(finderinfo));
hfs_zero_hidden_fields (cp, finderinfo);
if (vnode_islnk(vp)) {
struct FndrFileInfo *fip;
fip = (struct FndrFileInfo *)&finderinfo;
fip->fdType = 0;
fip->fdCreator = 0;
}
if (bcmp(finderinfo, emptyfinfo, sizeof(emptyfinfo)) == 0) {
hfs_unlock(cp);
return (ENOATTR);
}
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)) {
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);
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;
}
cp->c_touch_chgtime = TRUE;
cp->c_flag |= C_MODIFIED;
hfs_update(vp, 0);
hfs_unlock(cp);
return (0);
}
if (hfsmp->hfs_flags & HFS_STANDARD) {
return (EPERM);
}
if (hfsmp->hfs_attribute_vp == NULL) {
return (ENOATTR);
}
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, HFS_LOCK_DEFAULT))) {
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 exit;
}
lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE | SFL_BITMAP, HFS_EXCLUSIVE_LOCK);
result = remove_attribute_records(hfsmp, iterator);
hfs_systemfile_unlock(hfsmp, lockflags);
if (result == 0) {
cp->c_touch_chgtime = TRUE;
lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK);
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;
}
hfs_systemfile_unlock(hfsmp, lockflags);
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);
}
hfs_end_transaction(hfsmp);
exit:
hfs_unlock(cp);
exit_nolock:
FREE(iterator, M_TEMP);
return MacToVFSError(result);
}
int
file_attribute_exist(struct hfsmount *hfsmp, uint32_t fileID)
{
HFSPlusAttrKey *key;
struct BTreeIterator * iterator = NULL;
struct filefork *btfile;
int result = 0;
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 ((result && (result == btNotFound)) || (key->fileID != fileID)) {
result = 0;
} else {
result = EEXIST;
}
out:
if (iterator) {
FREE(iterator, M_TEMP);
}
return result;
}
int
remove_attribute_records(struct hfsmount *hfsmp, BTreeIterator * iterator)
{
struct filefork *btfile;
FSBufferDescriptor btdata;
HFSPlusAttrRecord attrdata;
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;
}
if (attrdata.recordType == kHFSPlusAttrForkData) {
int totalblks;
int extentblks;
u_int32_t *keystartblk;
if (datasize < sizeof(HFSPlusAttrForkData)) {
printf("hfs: remove_attribute_records: bad record size %d (expecting %lu)\n", datasize, sizeof(HFSPlusAttrForkData));
}
totalblks = attrdata.forkData.theFork.totalBlocks;
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;
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: vol=%s, err=%d (%d), totalblks %d\n",
hfsmp->vcbVN, MacToVFSError(result), attrdata.recordType != kHFSPlusAttrExtents, totalblks);
result = ENOATTR;
break;
}
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));
}
int
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);
#endif
hfsmp = VTOHFS(vp);
*ap->a_size = 0;
hfs_lock_truncate(cp, HFS_SHARED_LOCK, HFS_LOCK_DEFAULT);
if ((result = hfs_lock(cp, HFS_SHARED_LOCK, HFS_LOCK_DEFAULT))) {
hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT);
return (result);
}
bcopy(cp->c_finderinfo, finderinfo, sizeof(finderinfo));
hfs_zero_hidden_fields (cp, finderinfo);
if (vnode_islnk(vp)) {
struct FndrFileInfo *fip;
fip = (struct FndrFileInfo *)&finderinfo;
fip->fdType = 0;
fip->fdCreator = 0;
}
if (bcmp(finderinfo, emptyfinfo, sizeof(emptyfinfo)) != 0) {
if (uio == NULL) {
*ap->a_size += sizeof(XATTR_FINDERINFO_NAME);
} else if ((user_size_t)uio_resid(uio) < sizeof(XATTR_FINDERINFO_NAME)) {
result = ERANGE;
goto exit;
} else {
result = uiomove(XATTR_FINDERINFO_NAME,
sizeof(XATTR_FINDERINFO_NAME), uio);
if (result)
goto exit;
}
}
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
{
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;
}
}
}
if (hfsmp->hfs_flags & HFS_STANDARD) {
result = 0;
goto exit;
}
if ((hfsmp->hfs_attribute_vp == NULL) ||
(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 = hfs_buildattrkey(cp->c_fileid, NULL, (HFSPlusAttrKey *)&iterator->key);
if (result)
goto exit;
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);
if (result && result != btNotFound) {
hfs_systemfile_unlock(hfsmp, lockflags);
goto exit;
}
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
result = BTIterateRecords(btfile, kBTreeNextRecord, iterator,
(IterateCallBackProcPtr)listattr_callback, &state);
hfs_systemfile_unlock(hfsmp, lockflags);
if (uio == NULL) {
*ap->a_size += state.size;
}
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);
hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT);
return MacToVFSError(result);
}
static int
listattr_callback(const HFSPlusAttrKey *key, __unused const HFSPlusAttrData *data, struct listattr_callback_state *state)
{
char attrname[XATTR_MAXNAMELEN + 1];
ssize_t bytecount;
int result;
if (state->fileID != key->fileID) {
state->result = 0;
return (0);
}
if (key->startBlock != 0) {
return (1);
}
result = utf8_encodestr(key->attrName, key->attrNameLen * sizeof(UniChar),
(u_int8_t *)attrname, (size_t *)&bytecount, sizeof(attrname), '/', 0);
if (result) {
state->result = result;
return (0);
}
bytecount++;
if (xattr_protected(attrname))
return (1);
#if HFS_COMPRESSION
if (!state->showcompressed && decmpfs_hides_xattr(state->ctx, VTOCMP(state->vp), attrname) )
return 1;
#endif
if (state->uio == NULL) {
state->size += bytecount;
} else {
if (bytecount > uio_resid(state->uio)) {
state->result = ERANGE;
return (0);
}
result = uiomove((caddr_t) attrname, bytecount, state->uio);
if (result) {
state->result = result;
return (0);
}
}
return (1);
}
int
hfs_removeallattr(struct hfsmount *hfsmp, u_int32_t fileid,
bool *open_transaction)
{
BTreeIterator *iterator = NULL;
HFSPlusAttrKey *key;
struct filefork *btfile;
int result, lockflags = 0;
*open_transaction = false;
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;
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;
}
*open_transaction = true;
lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE | SFL_BITMAP, HFS_EXCLUSIVE_LOCK);
result = remove_attribute_records(hfsmp, iterator);
#if HFS_XATTR_VERBOSE
if (result) {
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;
}
return result;
}
__private_extern__
void
hfs_xattr_init(struct hfsmount * hfsmp)
{
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));
}
if (hfsmp->hfs_attribute_vp)
hfsmp->hfs_max_inline_attrsize = getmaxinlineattrsize(hfsmp->hfs_attribute_vp);
}
int
hfs_set_volxattr(struct hfsmount *hfsmp, unsigned int xattrtype, int state)
{
struct BTreeIterator * iterator = NULL;
struct filefork *btfile;
int lockflags;
int result;
if (hfsmp->hfs_flags & HFS_STANDARD) {
return (ENOTSUP);
}
if (xattrtype != HFS_SET_XATTREXTENTS_STATE) {
return EINVAL;
}
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));
(void) hfs_buildattrkey(kHFSRootParentID, XATTR_XATTREXTENTS_NAME,
(HFSPlusAttrKey *)&iterator->key);
if (hfs_start_transaction(hfsmp) != 0) {
result = EINVAL;
goto exit;
}
btfile = VTOF(hfsmp->hfs_attribute_vp);
lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_EXCLUSIVE_LOCK);
if (state == 0) {
result = BTDeleteRecord(btfile, iterator);
if (result == btNotFound)
result = 0;
} else {
FSBufferDescriptor btdata;
HFSPlusAttrData attrdata;
u_int16_t datasize;
datasize = sizeof(attrdata);
btdata.bufferAddress = &attrdata;
btdata.itemSize = datasize;
btdata.itemCount = 1;
attrdata.recordType = kHFSPlusAttrInlineData;
attrdata.reserved[0] = 0;
attrdata.reserved[1] = 0;
attrdata.attrSize = 2;
attrdata.attrData[0] = 0;
attrdata.attrData[1] = 0;
result = BTInsertRecord(btfile, iterator, &btdata, datasize);
if (result == btExists)
result = 0;
}
(void) BTFlushPath(btfile);
hfs_systemfile_unlock(hfsmp, lockflags);
hfs_end_transaction(hfsmp);
hfs_lock_mount (hfsmp);
if (state == 0) {
hfsmp->hfs_flags &= ~HFS_XATTR_EXTENTS;
} else {
hfsmp->hfs_flags |= HFS_XATTR_EXTENTS;
}
hfs_unlock_mount (hfsmp);
exit:
if (iterator) {
FREE(iterator, M_TEMP);
}
return MacToVFSError(result);
}
__private_extern__
int
hfs_attrkeycompare(HFSPlusAttrKey *searchKey, HFSPlusAttrKey *trialKey)
{
u_int32_t searchFileID, trialFileID;
int result;
searchFileID = searchKey->fileID;
trialFileID = trialKey->fileID;
result = 0;
if (searchFileID > trialFileID) {
++result;
} else if (searchFileID < trialFileID) {
--result;
} else {
u_int16_t * str1 = &searchKey->attrName[0];
u_int16_t * str2 = &trialKey->attrName[0];
int length1 = searchKey->attrNameLen;
int length2 = trialKey->attrNameLen;
u_int16_t c1, c2;
int length;
if (length1 < length2) {
length = length1;
--result;
} else if (length1 > length2) {
length = length2;
++result;
} else {
length = length1;
}
while (length--) {
c1 = *(str1++);
c2 = *(str2++);
if (c1 > c2) {
result = 1;
break;
}
if (c1 < c2) {
result = -1;
break;
}
}
if (result)
return (result);
if (searchKey->startBlock == trialKey->startBlock) {
return (0);
} else {
return (searchKey->startBlock < trialKey->startBlock ? -1 : 1);
}
}
return result;
}
__private_extern__
int
hfs_buildattrkey(u_int32_t fileID, const char *attrname, HFSPlusAttrKey *key)
{
int result = 0;
size_t unicodeBytes = 0;
if (attrname != NULL) {
result = utf8_decodestr((const u_int8_t *)attrname, strlen(attrname), key->attrName,
&unicodeBytes, sizeof(key->attrName), 0, 0);
if (result) {
if (result != ENAMETOOLONG)
result = EINVAL;
return (result);
}
key->attrNameLen = unicodeBytes / sizeof(UniChar);
key->keyLength = kHFSPlusAttrKeyMinimumLength + unicodeBytes;
} else {
key->attrNameLen = 0;
key->keyLength = kHFSPlusAttrKeyMinimumLength;
}
key->pad = 0;
key->fileID = fileID;
key->startBlock = 0;
return (0);
}
static int
getnodecount(struct hfsmount *hfsmp, size_t nodesize)
{
u_int64_t freebytes;
u_int64_t calcbytes;
freebytes = (u_int64_t)hfs_freeblks(hfsmp, 0) * (u_int64_t)hfsmp->blockSize;
calcbytes = MIN(hfsmp->hfs_catalog_cp->c_datafork->ff_size / 5, 20 * 1024 * 1024);
calcbytes = MAX(calcbytes, hfsmp->hfs_catalog_cp->c_datafork->ff_clumpsize);
calcbytes = MIN(calcbytes, freebytes / 10);
return (MAX(2, (int)(calcbytes / nodesize)));
}
static size_t
getmaxinlineattrsize(struct vnode * attrvp)
{
struct BTreeInfoRec btinfo;
size_t nodesize = ATTRIBUTE_FILE_NODE_SIZE;
size_t maxsize;
if (attrvp != NULL) {
(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));
}
maxsize = nodesize;
maxsize -= sizeof(BTNodeDescriptor);
maxsize -= 3 * sizeof(u_int16_t);
maxsize /= 2;
maxsize -= sizeof(HFSPlusAttrKey);
maxsize -= sizeof(HFSPlusAttrData) - 2;
maxsize &= 0xFFFFFFFE;
return (maxsize);
}
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;
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;
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);
}
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;
int64_t iosize;
int attrsize;
int blksize;
int i;
int result = 0;
hfs_lock_truncate(VTOC(evp), HFS_SHARED_LOCK, HFS_LOCK_DEFAULT);
bufsize = (int)uio_resid(uio);
attrsize = (int)datasize;
blksize = (int)hfsmp->blockSize;
for (i = 0; (attrsize > 0) && (bufsize > 0) && (extents[i].startBlock != 0); ++i) {
iosize = 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 %lld [%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), HFS_LOCK_DEFAULT);
return (result);
}
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;
int64_t iosize;
int blksize;
int i;
int result = 0;
hfs_lock_truncate(VTOC(evp), HFS_SHARED_LOCK, HFS_LOCK_DEFAULT);
bufsize = uio_resid(uio);
attrsize = (int) datasize;
blksize = (int) hfsmp->blockSize;
filesize = VTOF(evp)->ff_size;
for (i = 0; (attrsize > 0) && (bufsize > 0) && (extents[i].startBlock != 0); ++i) {
iosize = 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 %lld [%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), HFS_LOCK_DEFAULT);
return (result);
}
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 (result == dskFulErr) {
result = BlockAllocate(hfsmp, startblk, blkcnt, blkcnt, HFS_ALLOC_FLUSHTXN,
&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 (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);
}
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
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) {
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);
}