mirror of
https://github.com/darlinghq/darling-xnu.git
synced 2024-11-27 06:20:34 +00:00
3468 lines
93 KiB
C
3468 lines
93 KiB
C
/*
|
|
* Copyright (c) 2004-2012 Apple Inc. All rights reserved.
|
|
*
|
|
* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
|
|
*
|
|
* This file contains Original Code and/or Modifications of Original Code
|
|
* as defined in and that are subject to the Apple Public Source License
|
|
* Version 2.0 (the 'License'). You may not use this file except in
|
|
* compliance with the License. The rights granted to you under the License
|
|
* may not be used to create, or enable the creation or redistribution of,
|
|
* unlawful or unlicensed copies of an Apple operating system, or to
|
|
* circumvent, violate, or enable the circumvention or violation of, any
|
|
* terms of an Apple operating system software license agreement.
|
|
*
|
|
* Please obtain a copy of the License at
|
|
* http://www.opensource.apple.com/apsl/ and read it before using this file.
|
|
*
|
|
* The Original Code and all software distributed under the License are
|
|
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
|
|
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
|
|
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
|
|
* Please see the License for the specific language governing rights and
|
|
* limitations under the License.
|
|
*
|
|
* @APPLE_OSREFERENCE_LICENSE_HEADER_END@
|
|
*/
|
|
/*
|
|
* NOTICE: This file was modified by SPARTA, Inc. in 2005 to introduce
|
|
* support for mandatory and extensible security protections. This notice
|
|
* is included in support of clause 2.2 (b) of the Apple Public License,
|
|
* Version 2.0.
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
|
|
#include <sys/fcntl.h>
|
|
#include <sys/fsevents.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/kauth.h>
|
|
#include <kern/kalloc.h>
|
|
#include <sys/mount_internal.h>
|
|
#include <sys/namei.h>
|
|
#include <sys/proc_internal.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/uio.h>
|
|
#include <sys/utfconv.h>
|
|
#include <sys/vnode.h>
|
|
#include <sys/vnode_internal.h>
|
|
#include <sys/xattr.h>
|
|
|
|
#include <libkern/OSByteOrder.h>
|
|
#include <vm/vm_kern.h>
|
|
|
|
#if CONFIG_MACF
|
|
#include <security/mac_framework.h>
|
|
#endif
|
|
|
|
|
|
#if NAMEDSTREAMS
|
|
|
|
static int shadow_sequence;
|
|
|
|
/*
|
|
* We use %p to prevent loss of precision for pointers on varying architectures.
|
|
*/
|
|
|
|
#define SHADOW_NAME_FMT ".vfs_rsrc_stream_%p%08x%p"
|
|
#define SHADOW_DIR_FMT ".vfs_rsrc_streams_%p%x"
|
|
#define SHADOW_DIR_CONTAINER "/var/run"
|
|
|
|
#define MAKE_SHADOW_NAME(VP, NAME) \
|
|
snprintf((NAME), sizeof((NAME)), (SHADOW_NAME_FMT), \
|
|
((void*)(VM_KERNEL_ADDRPERM(VP))), \
|
|
(VP)->v_id, \
|
|
((void*)(VM_KERNEL_ADDRPERM((VP)->v_data))))
|
|
|
|
/* The full path to the shadow directory */
|
|
#define MAKE_SHADOW_DIRNAME(VP, NAME) \
|
|
snprintf((NAME), sizeof((NAME)), (SHADOW_DIR_CONTAINER "/" SHADOW_DIR_FMT), \
|
|
((void*)(VM_KERNEL_ADDRPERM(VP))), shadow_sequence)
|
|
|
|
/* The shadow directory as a 'leaf' entry */
|
|
#define MAKE_SHADOW_DIR_LEAF(VP, NAME) \
|
|
snprintf((NAME), sizeof((NAME)), (SHADOW_DIR_FMT), \
|
|
((void*)(VM_KERNEL_ADDRPERM(VP))), shadow_sequence)
|
|
|
|
static int default_getnamedstream(vnode_t vp, vnode_t *svpp, const char *name, enum nsoperation op, vfs_context_t context);
|
|
|
|
static int default_makenamedstream(vnode_t vp, vnode_t *svpp, const char *name, vfs_context_t context);
|
|
|
|
static int default_removenamedstream(vnode_t vp, const char *name, vfs_context_t context);
|
|
|
|
static int getshadowfile(vnode_t vp, vnode_t *svpp, int makestream, size_t *rsrcsize, int *creator, vfs_context_t context);
|
|
|
|
static int get_shadow_dir(vnode_t *sdvpp);
|
|
|
|
#endif /* NAMEDSTREAMS */
|
|
|
|
/*
|
|
* Default xattr support routines.
|
|
*/
|
|
|
|
static int default_getxattr(vnode_t vp, const char *name, uio_t uio, size_t *size, int options,
|
|
vfs_context_t context);
|
|
static int default_setxattr(vnode_t vp, const char *name, uio_t uio, int options,
|
|
vfs_context_t context);
|
|
static int default_listxattr(vnode_t vp, uio_t uio, size_t *size, int options,
|
|
vfs_context_t context);
|
|
static int default_removexattr(vnode_t vp, const char *name, int options,
|
|
vfs_context_t context);
|
|
|
|
/*
|
|
* Retrieve the data of an extended attribute.
|
|
*/
|
|
int
|
|
vn_getxattr(vnode_t vp, const char *name, uio_t uio, size_t *size,
|
|
int options, vfs_context_t context)
|
|
{
|
|
int error;
|
|
|
|
if (!XATTR_VNODE_SUPPORTED(vp)) {
|
|
return EPERM;
|
|
}
|
|
#if NAMEDSTREAMS
|
|
/* getxattr calls are not allowed for streams. */
|
|
if (vp->v_flag & VISNAMEDSTREAM) {
|
|
error = EPERM;
|
|
goto out;
|
|
}
|
|
#endif
|
|
/*
|
|
* Non-kernel request need extra checks performed.
|
|
*
|
|
* The XATTR_NOSECURITY flag implies a kernel request.
|
|
*/
|
|
if (!(options & XATTR_NOSECURITY)) {
|
|
#if CONFIG_MACF
|
|
error = mac_vnode_check_getextattr(context, vp, name, uio);
|
|
if (error) {
|
|
goto out;
|
|
}
|
|
#endif /* MAC */
|
|
if ((error = xattr_validatename(name))) {
|
|
goto out;
|
|
}
|
|
if ((error = vnode_authorize(vp, NULL, KAUTH_VNODE_READ_EXTATTRIBUTES, context))) {
|
|
goto out;
|
|
}
|
|
/* The offset can only be non-zero for resource forks. */
|
|
if (uio != NULL && uio_offset(uio) != 0 &&
|
|
strncmp(name, XATTR_RESOURCEFORK_NAME, sizeof(XATTR_RESOURCEFORK_NAME)) != 0) {
|
|
error = EINVAL;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/* The offset can only be non-zero for resource forks. */
|
|
if (uio != NULL && uio_offset(uio) != 0 &&
|
|
strncmp(name, XATTR_RESOURCEFORK_NAME, sizeof(XATTR_RESOURCEFORK_NAME)) != 0) {
|
|
error = EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
error = VNOP_GETXATTR(vp, name, uio, size, options, context);
|
|
if (error == ENOTSUP && !(options & XATTR_NODEFAULT)) {
|
|
/*
|
|
* A filesystem may keep some EAs natively and return ENOTSUP for others.
|
|
*/
|
|
error = default_getxattr(vp, name, uio, size, options, context);
|
|
}
|
|
out:
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Set the data of an extended attribute.
|
|
*/
|
|
int
|
|
vn_setxattr(vnode_t vp, const char *name, uio_t uio, int options, vfs_context_t context)
|
|
{
|
|
int error;
|
|
|
|
if (!XATTR_VNODE_SUPPORTED(vp)) {
|
|
return EPERM;
|
|
}
|
|
#if NAMEDSTREAMS
|
|
/* setxattr calls are not allowed for streams. */
|
|
if (vp->v_flag & VISNAMEDSTREAM) {
|
|
error = EPERM;
|
|
goto out;
|
|
}
|
|
#endif
|
|
if ((options & (XATTR_REPLACE | XATTR_CREATE)) == (XATTR_REPLACE | XATTR_CREATE)) {
|
|
return EINVAL;
|
|
}
|
|
if ((error = xattr_validatename(name))) {
|
|
return error;
|
|
}
|
|
if (!(options & XATTR_NOSECURITY)) {
|
|
#if CONFIG_MACF
|
|
error = mac_vnode_check_setextattr(context, vp, name, uio);
|
|
if (error) {
|
|
goto out;
|
|
}
|
|
#endif /* MAC */
|
|
error = vnode_authorize(vp, NULL, KAUTH_VNODE_WRITE_EXTATTRIBUTES, context);
|
|
if (error) {
|
|
goto out;
|
|
}
|
|
}
|
|
/* The offset can only be non-zero for resource forks. */
|
|
if (uio_offset(uio) != 0 &&
|
|
strncmp(name, XATTR_RESOURCEFORK_NAME, sizeof(XATTR_RESOURCEFORK_NAME)) != 0) {
|
|
error = EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
error = VNOP_SETXATTR(vp, name, uio, options, context);
|
|
#ifdef DUAL_EAS
|
|
/*
|
|
* An EJUSTRETURN is from a filesystem which keeps this xattr
|
|
* natively as well as in a dot-underscore file. In this case the
|
|
* EJUSTRETURN means the filesytem has done nothing, but identifies the
|
|
* EA as one which may be represented natively and/or in a DU, and
|
|
* since XATTR_CREATE or XATTR_REPLACE was specified, only up here in
|
|
* in vn_setxattr can we do the getxattrs needed to ascertain whether
|
|
* the XATTR_{CREATE,REPLACE} should yield an error.
|
|
*/
|
|
if (error == EJUSTRETURN) {
|
|
int native = 0, dufile = 0;
|
|
size_t sz; /* not used */
|
|
|
|
native = VNOP_GETXATTR(vp, name, NULL, &sz, 0, context) ? 0 : 1;
|
|
dufile = default_getxattr(vp, name, NULL, &sz, 0, context) ? 0 : 1;
|
|
if (options & XATTR_CREATE && (native || dufile)) {
|
|
error = EEXIST;
|
|
goto out;
|
|
}
|
|
if (options & XATTR_REPLACE && !(native || dufile)) {
|
|
error = ENOATTR;
|
|
goto out;
|
|
}
|
|
/*
|
|
* Having determined no CREATE/REPLACE error should result, we
|
|
* zero those bits, so both backing stores get written to.
|
|
*/
|
|
options &= ~(XATTR_CREATE | XATTR_REPLACE);
|
|
error = VNOP_SETXATTR(vp, name, uio, options, context);
|
|
/* the mainline path here is to have error==ENOTSUP ... */
|
|
}
|
|
#endif /* DUAL_EAS */
|
|
if (error == ENOTSUP && !(options & XATTR_NODEFAULT)) {
|
|
/*
|
|
* A filesystem may keep some EAs natively and return ENOTSUP for others.
|
|
*/
|
|
error = default_setxattr(vp, name, uio, options, context);
|
|
}
|
|
#if CONFIG_MACF
|
|
if ((error == 0) && !(options & XATTR_NOSECURITY)) {
|
|
mac_vnode_notify_setextattr(context, vp, name, uio);
|
|
if (vfs_flags(vnode_mount(vp)) & MNT_MULTILABEL) {
|
|
mac_vnode_label_update_extattr(vnode_mount(vp), vp, name);
|
|
}
|
|
}
|
|
#endif
|
|
out:
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Remove an extended attribute.
|
|
*/
|
|
int
|
|
vn_removexattr(vnode_t vp, const char * name, int options, vfs_context_t context)
|
|
{
|
|
int error;
|
|
|
|
if (!XATTR_VNODE_SUPPORTED(vp)) {
|
|
return EPERM;
|
|
}
|
|
#if NAMEDSTREAMS
|
|
/* removexattr calls are not allowed for streams. */
|
|
if (vp->v_flag & VISNAMEDSTREAM) {
|
|
error = EPERM;
|
|
goto out;
|
|
}
|
|
#endif
|
|
if ((error = xattr_validatename(name))) {
|
|
return error;
|
|
}
|
|
if (!(options & XATTR_NOSECURITY)) {
|
|
#if CONFIG_MACF
|
|
error = mac_vnode_check_deleteextattr(context, vp, name);
|
|
if (error) {
|
|
goto out;
|
|
}
|
|
#endif /* MAC */
|
|
error = vnode_authorize(vp, NULL, KAUTH_VNODE_WRITE_EXTATTRIBUTES, context);
|
|
if (error) {
|
|
goto out;
|
|
}
|
|
}
|
|
error = VNOP_REMOVEXATTR(vp, name, options, context);
|
|
if (error == ENOTSUP && !(options & XATTR_NODEFAULT)) {
|
|
/*
|
|
* A filesystem may keep some EAs natively and return ENOTSUP for others.
|
|
*/
|
|
error = default_removexattr(vp, name, options, context);
|
|
#ifdef DUAL_EAS
|
|
} else if (error == EJUSTRETURN) {
|
|
/*
|
|
* EJUSTRETURN is from a filesystem which keeps this xattr natively as well
|
|
* as in a dot-underscore file. EJUSTRETURN means the filesytem did remove
|
|
* a native xattr, so failure to find it in a DU file during
|
|
* default_removexattr should not be considered an error.
|
|
*/
|
|
error = default_removexattr(vp, name, options, context);
|
|
if (error == ENOATTR) {
|
|
error = 0;
|
|
}
|
|
#endif /* DUAL_EAS */
|
|
}
|
|
#if CONFIG_MACF
|
|
if ((error == 0) && !(options & XATTR_NOSECURITY)) {
|
|
mac_vnode_notify_deleteextattr(context, vp, name);
|
|
if (vfs_flags(vnode_mount(vp)) & MNT_MULTILABEL) {
|
|
mac_vnode_label_update_extattr(vnode_mount(vp), vp, name);
|
|
}
|
|
}
|
|
#endif
|
|
out:
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Retrieve the list of extended attribute names.
|
|
*/
|
|
int
|
|
vn_listxattr(vnode_t vp, uio_t uio, size_t *size, int options, vfs_context_t context)
|
|
{
|
|
int error;
|
|
|
|
if (!XATTR_VNODE_SUPPORTED(vp)) {
|
|
return EPERM;
|
|
}
|
|
#if NAMEDSTREAMS
|
|
/* listxattr calls are not allowed for streams. */
|
|
if (vp->v_flag & VISNAMEDSTREAM) {
|
|
return EPERM;
|
|
}
|
|
#endif
|
|
|
|
if (!(options & XATTR_NOSECURITY)) {
|
|
#if CONFIG_MACF
|
|
error = mac_vnode_check_listextattr(context, vp);
|
|
if (error) {
|
|
goto out;
|
|
}
|
|
#endif /* MAC */
|
|
|
|
error = vnode_authorize(vp, NULL, KAUTH_VNODE_READ_EXTATTRIBUTES, context);
|
|
if (error) {
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
error = VNOP_LISTXATTR(vp, uio, size, options, context);
|
|
if (error == ENOTSUP && !(options & XATTR_NODEFAULT)) {
|
|
/*
|
|
* A filesystem may keep some but not all EAs natively, in which case
|
|
* the native EA names will have been uiomove-d out (or *size updated)
|
|
* and the default_listxattr here will finish the job.
|
|
*/
|
|
error = default_listxattr(vp, uio, size, options, context);
|
|
}
|
|
out:
|
|
return error;
|
|
}
|
|
|
|
int
|
|
xattr_validatename(const char *name)
|
|
{
|
|
size_t namelen;
|
|
|
|
if (name == NULL || name[0] == '\0') {
|
|
return EINVAL;
|
|
}
|
|
namelen = strlen(name);
|
|
|
|
if (utf8_validatestr((const unsigned char *)name, namelen) != 0) {
|
|
return EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Determine whether an EA is a protected system attribute.
|
|
*/
|
|
int
|
|
xattr_protected(const char *attrname)
|
|
{
|
|
return !strncmp(attrname, "com.apple.system.", 17);
|
|
}
|
|
|
|
|
|
static void
|
|
vnode_setasnamedstream_internal(vnode_t vp, vnode_t svp)
|
|
{
|
|
uint32_t streamflags = VISNAMEDSTREAM;
|
|
|
|
if ((vp->v_mount->mnt_kern_flag & MNTK_NAMED_STREAMS) == 0) {
|
|
streamflags |= VISSHADOW;
|
|
}
|
|
|
|
/* Tag the vnode. */
|
|
vnode_lock_spin(svp);
|
|
svp->v_flag |= streamflags;
|
|
vnode_unlock(svp);
|
|
|
|
/* Tag the parent so we know to flush credentials for streams on setattr */
|
|
vnode_lock_spin(vp);
|
|
vp->v_lflag |= VL_HASSTREAMS;
|
|
vnode_unlock(vp);
|
|
|
|
/* Make the file it's parent.
|
|
* Note: This parent link helps us distinguish vnodes for
|
|
* shadow stream files from vnodes for resource fork on file
|
|
* systems that support namedstream natively (both have
|
|
* VISNAMEDSTREAM set) by allowing access to mount structure
|
|
* for checking MNTK_NAMED_STREAMS bit at many places in the
|
|
* code.
|
|
*/
|
|
vnode_update_identity(svp, vp, NULL, 0, 0, VNODE_UPDATE_NAMEDSTREAM_PARENT);
|
|
|
|
if (vnode_isdyldsharedcache(vp)) {
|
|
vnode_lock_spin(svp);
|
|
svp->v_flag |= VSHARED_DYLD;
|
|
vnode_unlock(svp);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
errno_t
|
|
vnode_setasnamedstream(vnode_t vp, vnode_t svp)
|
|
{
|
|
if ((vp->v_mount->mnt_kern_flag & MNTK_NAMED_STREAMS) == 0) {
|
|
return EINVAL;
|
|
}
|
|
|
|
vnode_setasnamedstream_internal(vp, svp);
|
|
return 0;
|
|
}
|
|
|
|
#if NAMEDSTREAMS
|
|
|
|
/*
|
|
* Obtain a named stream from vnode vp.
|
|
*/
|
|
errno_t
|
|
vnode_getnamedstream(vnode_t vp, vnode_t *svpp, const char *name, enum nsoperation op, int flags, vfs_context_t context)
|
|
{
|
|
int error;
|
|
|
|
if (vp->v_mount->mnt_kern_flag & MNTK_NAMED_STREAMS) {
|
|
error = VNOP_GETNAMEDSTREAM(vp, svpp, name, op, flags, context);
|
|
} else {
|
|
if (flags) {
|
|
error = ENOTSUP;
|
|
} else {
|
|
error = default_getnamedstream(vp, svpp, name, op, context);
|
|
}
|
|
}
|
|
|
|
if (error == 0) {
|
|
vnode_setasnamedstream_internal(vp, *svpp);
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Make a named stream for vnode vp.
|
|
*/
|
|
errno_t
|
|
vnode_makenamedstream(vnode_t vp, vnode_t *svpp, const char *name, int flags, vfs_context_t context)
|
|
{
|
|
int error;
|
|
|
|
if (vp->v_mount->mnt_kern_flag & MNTK_NAMED_STREAMS) {
|
|
error = VNOP_MAKENAMEDSTREAM(vp, svpp, name, flags, context);
|
|
} else {
|
|
error = default_makenamedstream(vp, svpp, name, context);
|
|
}
|
|
|
|
if (error == 0) {
|
|
vnode_setasnamedstream_internal(vp, *svpp);
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Remove a named stream from vnode vp.
|
|
*/
|
|
errno_t
|
|
vnode_removenamedstream(vnode_t vp, vnode_t svp, const char *name, int flags, vfs_context_t context)
|
|
{
|
|
int error;
|
|
|
|
if (vp->v_mount->mnt_kern_flag & MNTK_NAMED_STREAMS) {
|
|
error = VNOP_REMOVENAMEDSTREAM(vp, svp, name, flags, context);
|
|
} else {
|
|
error = default_removenamedstream(vp, name, context);
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
#define NS_IOBUFSIZE (128 * 1024)
|
|
|
|
/*
|
|
* Release a named stream shadow file.
|
|
*
|
|
* Note: This function is called from two places where we do not need
|
|
* to check if the vnode has any references held before deleting the
|
|
* shadow file. Once from vclean() when the vnode is being reclaimed
|
|
* and we do not hold any references on the vnode. Second time from
|
|
* default_getnamedstream() when we get an error during shadow stream
|
|
* file initialization so that other processes who are waiting for the
|
|
* shadow stream file initialization by the creator will get opportunity
|
|
* to create and initialize the file again.
|
|
*/
|
|
errno_t
|
|
vnode_relenamedstream(vnode_t vp, vnode_t svp)
|
|
{
|
|
vnode_t dvp;
|
|
struct componentname cn;
|
|
char tmpname[80];
|
|
errno_t err;
|
|
|
|
/*
|
|
* We need to use the kernel context here. If we used the supplied
|
|
* VFS context we have no clue whether or not it originated from userland
|
|
* where it could be subject to a chroot jail. We need to ensure that all
|
|
* filesystem access to shadow files is done on the same FS regardless of
|
|
* userland process restrictions.
|
|
*/
|
|
vfs_context_t kernelctx = vfs_context_kernel();
|
|
|
|
cache_purge(svp);
|
|
|
|
vnode_lock(svp);
|
|
MAKE_SHADOW_NAME(vp, tmpname);
|
|
vnode_unlock(svp);
|
|
|
|
cn.cn_nameiop = DELETE;
|
|
cn.cn_flags = ISLASTCN;
|
|
cn.cn_context = kernelctx;
|
|
cn.cn_pnbuf = tmpname;
|
|
cn.cn_pnlen = sizeof(tmpname);
|
|
cn.cn_nameptr = cn.cn_pnbuf;
|
|
cn.cn_namelen = (int)strlen(tmpname);
|
|
|
|
/*
|
|
* Obtain the vnode for the shadow files directory. Make sure to
|
|
* use the kernel ctx as described above.
|
|
*/
|
|
err = get_shadow_dir(&dvp);
|
|
if (err != 0) {
|
|
return err;
|
|
}
|
|
|
|
(void) VNOP_REMOVE(dvp, svp, &cn, 0, kernelctx);
|
|
vnode_put(dvp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Flush a named stream shadow file.
|
|
*
|
|
* 'vp' represents the AppleDouble file.
|
|
* 'svp' represents the shadow file.
|
|
*/
|
|
errno_t
|
|
vnode_flushnamedstream(vnode_t vp, vnode_t svp, vfs_context_t context)
|
|
{
|
|
struct vnode_attr va;
|
|
uio_t auio = NULL;
|
|
caddr_t bufptr = NULL;
|
|
size_t bufsize = 0;
|
|
size_t offset;
|
|
size_t iosize;
|
|
size_t datasize;
|
|
int error;
|
|
/*
|
|
* The kernel context must be used for all I/O to the shadow file
|
|
* and its namespace operations
|
|
*/
|
|
vfs_context_t kernelctx = vfs_context_kernel();
|
|
|
|
/* The supplied context is used for access to the AD file itself */
|
|
|
|
VATTR_INIT(&va);
|
|
VATTR_WANTED(&va, va_data_size);
|
|
if (VNOP_GETATTR(svp, &va, context) != 0 ||
|
|
!VATTR_IS_SUPPORTED(&va, va_data_size)) {
|
|
return 0;
|
|
}
|
|
if (va.va_data_size > UINT32_MAX) {
|
|
return EINVAL;
|
|
}
|
|
datasize = (size_t)va.va_data_size;
|
|
if (datasize == 0) {
|
|
(void) default_removexattr(vp, XATTR_RESOURCEFORK_NAME, 0, context);
|
|
return 0;
|
|
}
|
|
|
|
iosize = bufsize = MIN(datasize, NS_IOBUFSIZE);
|
|
if (kmem_alloc(kernel_map, (vm_offset_t *)&bufptr, bufsize, VM_KERN_MEMORY_FILE)) {
|
|
return ENOMEM;
|
|
}
|
|
auio = uio_create(1, 0, UIO_SYSSPACE, UIO_READ);
|
|
offset = 0;
|
|
|
|
/*
|
|
* Copy the shadow stream file data into the resource fork.
|
|
*/
|
|
error = VNOP_OPEN(svp, 0, kernelctx);
|
|
if (error) {
|
|
printf("vnode_flushnamedstream: err %d opening file\n", error);
|
|
goto out;
|
|
}
|
|
while (offset < datasize) {
|
|
iosize = MIN(datasize - offset, iosize);
|
|
|
|
uio_reset(auio, offset, UIO_SYSSPACE, UIO_READ);
|
|
uio_addiov(auio, (uintptr_t)bufptr, iosize);
|
|
error = VNOP_READ(svp, auio, 0, kernelctx);
|
|
if (error) {
|
|
break;
|
|
}
|
|
/* Since there's no truncate xattr we must remove the resource fork. */
|
|
if (offset == 0) {
|
|
error = default_removexattr(vp, XATTR_RESOURCEFORK_NAME, 0, context);
|
|
if ((error != 0) && (error != ENOATTR)) {
|
|
break;
|
|
}
|
|
}
|
|
uio_reset(auio, offset, UIO_SYSSPACE, UIO_WRITE);
|
|
uio_addiov(auio, (uintptr_t)bufptr, iosize);
|
|
error = vn_setxattr(vp, XATTR_RESOURCEFORK_NAME, auio, XATTR_NOSECURITY, context);
|
|
if (error) {
|
|
break;
|
|
}
|
|
offset += iosize;
|
|
}
|
|
|
|
/* close shadowfile */
|
|
(void) VNOP_CLOSE(svp, 0, kernelctx);
|
|
out:
|
|
if (bufptr) {
|
|
kmem_free(kernel_map, (vm_offset_t)bufptr, bufsize);
|
|
}
|
|
if (auio) {
|
|
uio_free(auio);
|
|
}
|
|
return error;
|
|
}
|
|
|
|
|
|
/*
|
|
* Verify that the vnode 'vp' is a vnode that lives in the shadow
|
|
* directory. We can't just query the parent pointer directly since
|
|
* the shadowfile is hooked up to the actual file it's a stream for.
|
|
*/
|
|
errno_t
|
|
vnode_verifynamedstream(vnode_t vp)
|
|
{
|
|
int error;
|
|
struct vnode *shadow_dvp = NULL;
|
|
struct vnode *shadowfile = NULL;
|
|
struct componentname cn;
|
|
|
|
/*
|
|
* We need to use the kernel context here. If we used the supplied
|
|
* VFS context we have no clue whether or not it originated from userland
|
|
* where it could be subject to a chroot jail. We need to ensure that all
|
|
* filesystem access to shadow files is done on the same FS regardless of
|
|
* userland process restrictions.
|
|
*/
|
|
vfs_context_t kernelctx = vfs_context_kernel();
|
|
char tmpname[80];
|
|
|
|
|
|
/* Get the shadow directory vnode */
|
|
error = get_shadow_dir(&shadow_dvp);
|
|
if (error) {
|
|
return error;
|
|
}
|
|
|
|
/* Re-generate the shadow name in the buffer */
|
|
MAKE_SHADOW_NAME(vp, tmpname);
|
|
|
|
/* Look up item in shadow dir */
|
|
bzero(&cn, sizeof(cn));
|
|
cn.cn_nameiop = LOOKUP;
|
|
cn.cn_flags = ISLASTCN | CN_ALLOWRSRCFORK;
|
|
cn.cn_context = kernelctx;
|
|
cn.cn_pnbuf = tmpname;
|
|
cn.cn_pnlen = sizeof(tmpname);
|
|
cn.cn_nameptr = cn.cn_pnbuf;
|
|
cn.cn_namelen = (int)strlen(tmpname);
|
|
|
|
if (VNOP_LOOKUP(shadow_dvp, &shadowfile, &cn, kernelctx) == 0) {
|
|
/* is the pointer the same? */
|
|
if (shadowfile == vp) {
|
|
error = 0;
|
|
} else {
|
|
error = EPERM;
|
|
}
|
|
/* drop the iocount acquired */
|
|
vnode_put(shadowfile);
|
|
}
|
|
|
|
/* Drop iocount on shadow dir */
|
|
vnode_put(shadow_dvp);
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Access or create the shadow file as needed.
|
|
*
|
|
* 'makestream' with non-zero value means that we need to guarantee we were the
|
|
* creator of the shadow file.
|
|
*
|
|
* 'context' is the user supplied context for the original VFS operation that
|
|
* caused us to need a shadow file.
|
|
*
|
|
* int pointed to by 'creator' is nonzero if we created the shadowfile.
|
|
*/
|
|
static int
|
|
getshadowfile(vnode_t vp, vnode_t *svpp, int makestream, size_t *rsrcsize,
|
|
int *creator, vfs_context_t context)
|
|
{
|
|
vnode_t dvp = NULLVP;
|
|
vnode_t svp = NULLVP;
|
|
struct componentname cn;
|
|
struct vnode_attr va;
|
|
char tmpname[80];
|
|
size_t datasize = 0;
|
|
int error = 0;
|
|
int retries = 0;
|
|
vfs_context_t kernelctx = vfs_context_kernel();
|
|
|
|
retry_create:
|
|
*creator = 0;
|
|
/* Establish a unique file name. */
|
|
MAKE_SHADOW_NAME(vp, tmpname);
|
|
bzero(&cn, sizeof(cn));
|
|
cn.cn_nameiop = LOOKUP;
|
|
cn.cn_flags = ISLASTCN;
|
|
cn.cn_context = context;
|
|
cn.cn_pnbuf = tmpname;
|
|
cn.cn_pnlen = sizeof(tmpname);
|
|
cn.cn_nameptr = cn.cn_pnbuf;
|
|
cn.cn_namelen = (int)strlen(tmpname);
|
|
|
|
/* Pick up uid, gid, mode and date from original file. */
|
|
VATTR_INIT(&va);
|
|
VATTR_WANTED(&va, va_uid);
|
|
VATTR_WANTED(&va, va_gid);
|
|
VATTR_WANTED(&va, va_mode);
|
|
VATTR_WANTED(&va, va_create_time);
|
|
VATTR_WANTED(&va, va_modify_time);
|
|
if (VNOP_GETATTR(vp, &va, context) != 0 ||
|
|
!VATTR_IS_SUPPORTED(&va, va_uid) ||
|
|
!VATTR_IS_SUPPORTED(&va, va_gid) ||
|
|
!VATTR_IS_SUPPORTED(&va, va_mode)) {
|
|
va.va_uid = KAUTH_UID_NONE;
|
|
va.va_gid = KAUTH_GID_NONE;
|
|
va.va_mode = S_IRUSR | S_IWUSR;
|
|
}
|
|
va.va_vaflags = VA_EXCLUSIVE;
|
|
VATTR_SET(&va, va_type, VREG);
|
|
/* We no longer change the access, but we still hide it. */
|
|
VATTR_SET(&va, va_flags, UF_HIDDEN);
|
|
|
|
/* Obtain the vnode for the shadow files directory. */
|
|
if (get_shadow_dir(&dvp) != 0) {
|
|
error = ENOTDIR;
|
|
goto out;
|
|
}
|
|
if (!makestream) {
|
|
/* See if someone else already has it open. */
|
|
if (VNOP_LOOKUP(dvp, &svp, &cn, kernelctx) == 0) {
|
|
/* Double check existence by asking for size. */
|
|
VATTR_INIT(&va);
|
|
VATTR_WANTED(&va, va_data_size);
|
|
if (VNOP_GETATTR(svp, &va, context) == 0 &&
|
|
VATTR_IS_SUPPORTED(&va, va_data_size)) {
|
|
goto out; /* OK to use. */
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Otherwise make sure the resource fork data exists.
|
|
* Use the supplied context for accessing the AD file.
|
|
*/
|
|
error = vn_getxattr(vp, XATTR_RESOURCEFORK_NAME, NULL, &datasize,
|
|
XATTR_NOSECURITY, context);
|
|
/*
|
|
* To maintain binary compatibility with legacy Carbon
|
|
* emulated resource fork support, if the resource fork
|
|
* doesn't exist but the Finder Info does, then act as
|
|
* if an empty resource fork is present (see 4724359).
|
|
*/
|
|
if ((error == ENOATTR) &&
|
|
(vn_getxattr(vp, XATTR_FINDERINFO_NAME, NULL, &datasize,
|
|
XATTR_NOSECURITY, context) == 0)) {
|
|
datasize = 0;
|
|
error = 0;
|
|
} else {
|
|
if (error) {
|
|
goto out;
|
|
}
|
|
|
|
/* If the resource fork exists, its size is expected to be non-zero. */
|
|
if (datasize == 0) {
|
|
error = ENOATTR;
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
/* Create the shadow stream file. */
|
|
error = VNOP_CREATE(dvp, &svp, &cn, &va, kernelctx);
|
|
if (error == 0) {
|
|
vnode_recycle(svp);
|
|
*creator = 1;
|
|
} else if ((error == EEXIST) && !makestream) {
|
|
error = VNOP_LOOKUP(dvp, &svp, &cn, kernelctx);
|
|
} else if ((error == ENOENT) && !makestream) {
|
|
/*
|
|
* We could have raced with a rmdir on the shadow directory
|
|
* post-lookup. Retry from the beginning, 1x only, to
|
|
* try and see if we need to re-create the shadow directory
|
|
* in get_shadow_dir.
|
|
*/
|
|
if (retries == 0) {
|
|
retries++;
|
|
if (dvp) {
|
|
vnode_put(dvp);
|
|
dvp = NULLVP;
|
|
}
|
|
if (svp) {
|
|
vnode_put(svp);
|
|
svp = NULLVP;
|
|
}
|
|
goto retry_create;
|
|
}
|
|
/* Otherwise, just error out normally below */
|
|
}
|
|
|
|
out:
|
|
if (dvp) {
|
|
vnode_put(dvp);
|
|
}
|
|
if (error) {
|
|
/* On errors, clean up shadow stream file. */
|
|
if (svp) {
|
|
vnode_put(svp);
|
|
svp = NULLVP;
|
|
}
|
|
}
|
|
*svpp = svp;
|
|
if (rsrcsize) {
|
|
*rsrcsize = datasize;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
|
|
static int
|
|
default_getnamedstream(vnode_t vp, vnode_t *svpp, const char *name, enum nsoperation op, vfs_context_t context)
|
|
{
|
|
vnode_t svp = NULLVP;
|
|
uio_t auio = NULL;
|
|
caddr_t bufptr = NULL;
|
|
size_t bufsize = 0;
|
|
size_t datasize = 0;
|
|
int creator;
|
|
int error;
|
|
|
|
/* need the kernel context for accessing the shadowfile */
|
|
vfs_context_t kernelctx = vfs_context_kernel();
|
|
|
|
/*
|
|
* Only the "com.apple.ResourceFork" stream is supported here.
|
|
*/
|
|
if (strncmp(name, XATTR_RESOURCEFORK_NAME, sizeof(XATTR_RESOURCEFORK_NAME)) != 0) {
|
|
*svpp = NULLVP;
|
|
return ENOATTR;
|
|
}
|
|
retry:
|
|
/*
|
|
* Obtain a shadow file for the resource fork I/O.
|
|
*
|
|
* Need to pass along the supplied context so that getshadowfile
|
|
* can access the AD file as needed, using it.
|
|
*/
|
|
error = getshadowfile(vp, &svp, 0, &datasize, &creator, context);
|
|
if (error) {
|
|
*svpp = NULLVP;
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* The creator of the shadow file provides its file data,
|
|
* all other threads should wait until its ready. In order to
|
|
* prevent a deadlock during error codepaths, we need to check if the
|
|
* vnode is being created, or if it has failed out. Regardless of success or
|
|
* failure, we set the VISSHADOW bit on the vnode, so we check that
|
|
* if the vnode's flags don't have VISNAMEDSTREAM set. If it doesn't,
|
|
* then we can infer the creator isn't done yet. If it's there, but
|
|
* VISNAMEDSTREAM is not set, then we can infer it errored out and we should
|
|
* try again.
|
|
*/
|
|
if (!creator) {
|
|
vnode_lock(svp);
|
|
if (svp->v_flag & VISNAMEDSTREAM) {
|
|
/* data is ready, go use it */
|
|
vnode_unlock(svp);
|
|
goto out;
|
|
} else {
|
|
/* It's not ready, wait for it (sleep using v_parent as channel) */
|
|
if ((svp->v_flag & VISSHADOW)) {
|
|
/*
|
|
* No VISNAMEDSTREAM, but we did see VISSHADOW, indicating that the other
|
|
* thread is done with this vnode. Just unlock the vnode and try again
|
|
*/
|
|
vnode_unlock(svp);
|
|
} else {
|
|
/* Otherwise, sleep if the shadow file is not created yet */
|
|
msleep((caddr_t)&svp->v_parent, &svp->v_lock, PINOD | PDROP,
|
|
"getnamedstream", NULL);
|
|
}
|
|
vnode_put(svp);
|
|
svp = NULLVP;
|
|
goto retry;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Copy the real resource fork data into shadow stream file.
|
|
*/
|
|
if (op == NS_OPEN && datasize != 0) {
|
|
size_t offset;
|
|
size_t iosize;
|
|
|
|
iosize = bufsize = MIN(datasize, NS_IOBUFSIZE);
|
|
if (kmem_alloc(kernel_map, (vm_offset_t *)&bufptr, bufsize, VM_KERN_MEMORY_FILE)) {
|
|
error = ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
auio = uio_create(1, 0, UIO_SYSSPACE, UIO_READ);
|
|
offset = 0;
|
|
|
|
/* open the shadow file */
|
|
error = VNOP_OPEN(svp, 0, kernelctx);
|
|
if (error) {
|
|
goto out;
|
|
}
|
|
while (offset < datasize) {
|
|
size_t tmpsize;
|
|
|
|
iosize = MIN(datasize - offset, iosize);
|
|
|
|
uio_reset(auio, offset, UIO_SYSSPACE, UIO_READ);
|
|
uio_addiov(auio, (uintptr_t)bufptr, iosize);
|
|
/* use supplied ctx for AD file */
|
|
error = vn_getxattr(vp, XATTR_RESOURCEFORK_NAME, auio, &tmpsize,
|
|
XATTR_NOSECURITY, context);
|
|
if (error) {
|
|
break;
|
|
}
|
|
|
|
uio_reset(auio, offset, UIO_SYSSPACE, UIO_WRITE);
|
|
uio_addiov(auio, (uintptr_t)bufptr, iosize);
|
|
/* kernel context for writing shadowfile */
|
|
error = VNOP_WRITE(svp, auio, 0, kernelctx);
|
|
if (error) {
|
|
break;
|
|
}
|
|
offset += iosize;
|
|
}
|
|
|
|
/* close shadow file */
|
|
(void) VNOP_CLOSE(svp, 0, kernelctx);
|
|
}
|
|
out:
|
|
/* Wake up anyone waiting for svp file content */
|
|
if (creator) {
|
|
if (error == 0) {
|
|
vnode_lock(svp);
|
|
/* VISSHADOW would be set later on anyway, so we set it now */
|
|
svp->v_flag |= (VISNAMEDSTREAM | VISSHADOW);
|
|
wakeup((caddr_t)&svp->v_parent);
|
|
vnode_unlock(svp);
|
|
} else {
|
|
/* On post create errors, get rid of the shadow file. This
|
|
* way if there is another process waiting for initialization
|
|
* of the shadowfile by the current process will wake up and
|
|
* retry by creating and initializing the shadow file again.
|
|
* Also add the VISSHADOW bit here to indicate we're done operating
|
|
* on this vnode.
|
|
*/
|
|
(void)vnode_relenamedstream(vp, svp);
|
|
vnode_lock(svp);
|
|
svp->v_flag |= VISSHADOW;
|
|
wakeup((caddr_t)&svp->v_parent);
|
|
vnode_unlock(svp);
|
|
}
|
|
}
|
|
|
|
if (bufptr) {
|
|
kmem_free(kernel_map, (vm_offset_t)bufptr, bufsize);
|
|
}
|
|
if (auio) {
|
|
uio_free(auio);
|
|
}
|
|
if (error) {
|
|
/* On errors, clean up shadow stream file. */
|
|
if (svp) {
|
|
vnode_put(svp);
|
|
svp = NULLVP;
|
|
}
|
|
}
|
|
*svpp = svp;
|
|
return error;
|
|
}
|
|
|
|
static int
|
|
default_makenamedstream(vnode_t vp, vnode_t *svpp, const char *name, vfs_context_t context)
|
|
{
|
|
int creator;
|
|
int error;
|
|
|
|
/*
|
|
* Only the "com.apple.ResourceFork" stream is supported here.
|
|
*/
|
|
if (strncmp(name, XATTR_RESOURCEFORK_NAME, sizeof(XATTR_RESOURCEFORK_NAME)) != 0) {
|
|
*svpp = NULLVP;
|
|
return ENOATTR;
|
|
}
|
|
|
|
/* Supply the context to getshadowfile so it can manipulate the AD file */
|
|
error = getshadowfile(vp, svpp, 1, NULL, &creator, context);
|
|
|
|
/*
|
|
* Wake up any waiters over in default_getnamedstream().
|
|
*/
|
|
if ((error == 0) && (*svpp != NULL) && creator) {
|
|
vnode_t svp = *svpp;
|
|
|
|
vnode_lock(svp);
|
|
/* If we're the creator, mark it as a named stream */
|
|
svp->v_flag |= (VISNAMEDSTREAM | VISSHADOW);
|
|
/* Wakeup any waiters on the v_parent channel */
|
|
wakeup((caddr_t)&svp->v_parent);
|
|
vnode_unlock(svp);
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
static int
|
|
default_removenamedstream(vnode_t vp, const char *name, vfs_context_t context)
|
|
{
|
|
/*
|
|
* Only the "com.apple.ResourceFork" stream is supported here.
|
|
*/
|
|
if (strncmp(name, XATTR_RESOURCEFORK_NAME, sizeof(XATTR_RESOURCEFORK_NAME)) != 0) {
|
|
return ENOATTR;
|
|
}
|
|
/*
|
|
* XXX - what about other opened instances?
|
|
*/
|
|
return default_removexattr(vp, XATTR_RESOURCEFORK_NAME, 0, context);
|
|
}
|
|
|
|
static int
|
|
get_shadow_dir(vnode_t *sdvpp)
|
|
{
|
|
vnode_t dvp = NULLVP;
|
|
vnode_t sdvp = NULLVP;
|
|
struct componentname cn;
|
|
struct vnode_attr va;
|
|
char tmpname[80];
|
|
uint32_t tmp_fsid;
|
|
int error;
|
|
vfs_context_t kernelctx = vfs_context_kernel();
|
|
|
|
bzero(tmpname, sizeof(tmpname));
|
|
MAKE_SHADOW_DIRNAME(rootvnode, tmpname);
|
|
/*
|
|
* Look up the shadow directory to ensure that it still exists.
|
|
* By looking it up, we get an iocounted dvp to use, and avoid some coherency issues
|
|
* in caching it when multiple threads may be trying to manipulate the pointers.
|
|
*
|
|
* Make sure to use the kernel context. We want a singular view of
|
|
* the shadow dir regardless of chrooted processes.
|
|
*/
|
|
error = vnode_lookup(tmpname, 0, &sdvp, kernelctx);
|
|
if (error == 0) {
|
|
/*
|
|
* If we get here, then we have successfully looked up the shadow dir,
|
|
* and it has an iocount from the lookup. Return the vp in the output argument.
|
|
*/
|
|
*sdvpp = sdvp;
|
|
return 0;
|
|
}
|
|
/* In the failure case, no iocount is acquired */
|
|
sdvp = NULLVP;
|
|
bzero(tmpname, sizeof(tmpname));
|
|
|
|
/*
|
|
* Obtain the vnode for "/var/run" directory using the kernel
|
|
* context.
|
|
*
|
|
* This is defined in the SHADOW_DIR_CONTAINER macro
|
|
*/
|
|
if (vnode_lookup(SHADOW_DIR_CONTAINER, 0, &dvp, kernelctx) != 0) {
|
|
error = ENOTSUP;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Create the shadow stream directory.
|
|
* 'dvp' below suggests the parent directory so
|
|
* we only need to provide the leaf entry name
|
|
*/
|
|
MAKE_SHADOW_DIR_LEAF(rootvnode, tmpname);
|
|
bzero(&cn, sizeof(cn));
|
|
cn.cn_nameiop = LOOKUP;
|
|
cn.cn_flags = ISLASTCN;
|
|
cn.cn_context = kernelctx;
|
|
cn.cn_pnbuf = tmpname;
|
|
cn.cn_pnlen = sizeof(tmpname);
|
|
cn.cn_nameptr = cn.cn_pnbuf;
|
|
cn.cn_namelen = (int)strlen(tmpname);
|
|
|
|
/*
|
|
* owned by root, only readable by root, hidden
|
|
*/
|
|
VATTR_INIT(&va);
|
|
VATTR_SET(&va, va_uid, 0);
|
|
VATTR_SET(&va, va_gid, 0);
|
|
VATTR_SET(&va, va_mode, S_IRUSR | S_IXUSR);
|
|
VATTR_SET(&va, va_type, VDIR);
|
|
VATTR_SET(&va, va_flags, UF_HIDDEN);
|
|
va.va_vaflags = VA_EXCLUSIVE;
|
|
|
|
error = VNOP_MKDIR(dvp, &sdvp, &cn, &va, kernelctx);
|
|
|
|
/*
|
|
* There can be only one winner for an exclusive create.
|
|
*/
|
|
if (error == EEXIST) {
|
|
/* loser has to look up directory */
|
|
error = VNOP_LOOKUP(dvp, &sdvp, &cn, kernelctx);
|
|
if (error == 0) {
|
|
/* Make sure its in fact a directory */
|
|
if (sdvp->v_type != VDIR) {
|
|
goto baddir;
|
|
}
|
|
/* Obtain the fsid for /var/run directory */
|
|
VATTR_INIT(&va);
|
|
VATTR_WANTED(&va, va_fsid);
|
|
if (VNOP_GETATTR(dvp, &va, kernelctx) != 0 ||
|
|
!VATTR_IS_SUPPORTED(&va, va_fsid)) {
|
|
goto baddir;
|
|
}
|
|
tmp_fsid = va.va_fsid;
|
|
|
|
VATTR_INIT(&va);
|
|
VATTR_WANTED(&va, va_uid);
|
|
VATTR_WANTED(&va, va_gid);
|
|
VATTR_WANTED(&va, va_mode);
|
|
VATTR_WANTED(&va, va_fsid);
|
|
VATTR_WANTED(&va, va_dirlinkcount);
|
|
VATTR_WANTED(&va, va_acl);
|
|
/* Provide defaults for attrs that may not be supported */
|
|
va.va_dirlinkcount = 1;
|
|
va.va_acl = (kauth_acl_t) KAUTH_FILESEC_NONE;
|
|
|
|
if (VNOP_GETATTR(sdvp, &va, kernelctx) != 0 ||
|
|
!VATTR_IS_SUPPORTED(&va, va_uid) ||
|
|
!VATTR_IS_SUPPORTED(&va, va_gid) ||
|
|
!VATTR_IS_SUPPORTED(&va, va_mode) ||
|
|
!VATTR_IS_SUPPORTED(&va, va_fsid)) {
|
|
goto baddir;
|
|
}
|
|
/*
|
|
* Make sure its what we want:
|
|
* - owned by root
|
|
* - not writable by anyone
|
|
* - on same file system as /var/run
|
|
* - not a hard-linked directory
|
|
* - no ACLs (they might grant write access)
|
|
*/
|
|
if ((va.va_uid != 0) || (va.va_gid != 0) ||
|
|
(va.va_mode & (S_IWUSR | S_IRWXG | S_IRWXO)) ||
|
|
(va.va_fsid != tmp_fsid) ||
|
|
(va.va_dirlinkcount != 1) ||
|
|
(va.va_acl != (kauth_acl_t) KAUTH_FILESEC_NONE)) {
|
|
goto baddir;
|
|
}
|
|
}
|
|
}
|
|
out:
|
|
if (dvp) {
|
|
vnode_put(dvp);
|
|
}
|
|
if (error) {
|
|
/* On errors, clean up shadow stream directory. */
|
|
if (sdvp) {
|
|
vnode_put(sdvp);
|
|
sdvp = NULLVP;
|
|
}
|
|
}
|
|
*sdvpp = sdvp;
|
|
return error;
|
|
|
|
baddir:
|
|
/* This is not the dir we're looking for, move along */
|
|
++shadow_sequence; /* try something else next time */
|
|
error = ENOTDIR;
|
|
goto out;
|
|
}
|
|
#endif /* NAMEDSTREAMS */
|
|
|
|
|
|
#if CONFIG_APPLEDOUBLE
|
|
/*
|
|
* Default Implementation (Non-native EA)
|
|
*/
|
|
|
|
|
|
/*
|
|
* Typical "._" AppleDouble Header File layout:
|
|
* ------------------------------------------------------------
|
|
* MAGIC 0x00051607
|
|
* VERSION 0x00020000
|
|
* FILLER 0
|
|
* COUNT 2
|
|
* .-- AD ENTRY[0] Finder Info Entry (must be first)
|
|
* .--+-- AD ENTRY[1] Resource Fork Entry (must be last)
|
|
* | '-> FINDER INFO
|
|
* | ///////////// Fixed Size Data (32 bytes)
|
|
* | EXT ATTR HDR
|
|
* | /////////////
|
|
* | ATTR ENTRY[0] --.
|
|
* | ATTR ENTRY[1] --+--.
|
|
* | ATTR ENTRY[2] --+--+--.
|
|
* | ... | | |
|
|
* | ATTR ENTRY[N] --+--+--+--.
|
|
* | ATTR DATA 0 <-' | | |
|
|
* | //////////// | | |
|
|
* | ATTR DATA 1 <----' | |
|
|
* | ///////////// | |
|
|
* | ATTR DATA 2 <-------' |
|
|
* | ///////////// |
|
|
* | ... |
|
|
* | ATTR DATA N <----------'
|
|
* | /////////////
|
|
* | Attribute Free Space
|
|
* |
|
|
* '----> RESOURCE FORK
|
|
* ///////////// Variable Sized Data
|
|
* /////////////
|
|
* /////////////
|
|
* /////////////
|
|
* /////////////
|
|
* /////////////
|
|
* ...
|
|
* /////////////
|
|
*
|
|
* ------------------------------------------------------------
|
|
*
|
|
* NOTE: The EXT ATTR HDR, ATTR ENTRY's and ATTR DATA's are
|
|
* stored as part of the Finder Info. The length in the Finder
|
|
* Info AppleDouble entry includes the length of the extended
|
|
* attribute header, attribute entries, and attribute data.
|
|
*/
|
|
|
|
/*
|
|
* On Disk Data Structures
|
|
*
|
|
* Note: Motorola 68K alignment and big-endian.
|
|
*
|
|
* See RFC 1740 for additional information about the AppleDouble file format.
|
|
*
|
|
*/
|
|
|
|
#define ADH_MAGIC 0x00051607
|
|
#define ADH_VERSION 0x00020000
|
|
#define ADH_MACOSX "Mac OS X "
|
|
|
|
/*
|
|
* AppleDouble Entry ID's
|
|
*/
|
|
#define AD_DATA 1 /* Data fork */
|
|
#define AD_RESOURCE 2 /* Resource fork */
|
|
#define AD_REALNAME 3 /* File's name on home file system */
|
|
#define AD_COMMENT 4 /* Standard Mac comment */
|
|
#define AD_ICONBW 5 /* Mac black & white icon */
|
|
#define AD_ICONCOLOR 6 /* Mac color icon */
|
|
#define AD_UNUSED 7 /* Not used */
|
|
#define AD_FILEDATES 8 /* File dates; create, modify, etc */
|
|
#define AD_FINDERINFO 9 /* Mac Finder info & extended info */
|
|
#define AD_MACINFO 10 /* Mac file info, attributes, etc */
|
|
#define AD_PRODOSINFO 11 /* Pro-DOS file info, attrib., etc */
|
|
#define AD_MSDOSINFO 12 /* MS-DOS file info, attributes, etc */
|
|
#define AD_AFPNAME 13 /* Short name on AFP server */
|
|
#define AD_AFPINFO 14 /* AFP file info, attrib., etc */
|
|
#define AD_AFPDIRID 15 /* AFP directory ID */
|
|
#define AD_ATTRIBUTES AD_FINDERINFO
|
|
|
|
|
|
#define ATTR_FILE_PREFIX "._"
|
|
#define ATTR_HDR_MAGIC 0x41545452 /* 'ATTR' */
|
|
|
|
#define ATTR_BUF_SIZE 4096 /* default size of the attr file and how much we'll grow by */
|
|
|
|
/* Implementation Limits */
|
|
#define ATTR_MAX_SIZE AD_XATTR_MAXSIZE
|
|
#define ATTR_MAX_HDR_SIZE 65536
|
|
/*
|
|
* Note: ATTR_MAX_HDR_SIZE is the largest attribute header
|
|
* size supported (including the attribute entries). All of
|
|
* the attribute entries must reside within this limit. If
|
|
* any of the attribute data crosses the ATTR_MAX_HDR_SIZE
|
|
* boundry, then all of the attribute data I/O is performed
|
|
* separately from the attribute header I/O.
|
|
*
|
|
* In particular, all of the attr_entry structures must lie
|
|
* completely within the first ATTR_MAX_HDR_SIZE bytes of the
|
|
* AppleDouble file. However, the attribute data (i.e. the
|
|
* contents of the extended attributes) may extend beyond the
|
|
* first ATTR_MAX_HDR_SIZE bytes of the file. Note that this
|
|
* limit is to allow the implementation to optimize by reading
|
|
* the first ATTR_MAX_HDR_SIZE bytes of the file.
|
|
*/
|
|
|
|
|
|
#define FINDERINFOSIZE 32
|
|
|
|
typedef struct apple_double_entry {
|
|
u_int32_t type; /* entry type: see list, 0 invalid */
|
|
u_int32_t offset; /* entry data offset from the beginning of the file. */
|
|
u_int32_t length; /* entry data length in bytes. */
|
|
} __attribute__((aligned(2), packed)) apple_double_entry_t;
|
|
|
|
|
|
typedef struct apple_double_header {
|
|
u_int32_t magic; /* == ADH_MAGIC */
|
|
u_int32_t version; /* format version: 2 = 0x00020000 */
|
|
u_int32_t filler[4];
|
|
u_int16_t numEntries; /* number of entries which follow */
|
|
apple_double_entry_t entries[2]; /* 'finfo' & 'rsrc' always exist */
|
|
u_int8_t finfo[FINDERINFOSIZE]; /* Must start with Finder Info (32 bytes) */
|
|
u_int8_t pad[2]; /* get better alignment inside attr_header */
|
|
} __attribute__((aligned(2), packed)) apple_double_header_t;
|
|
|
|
#define ADHDRSIZE (4+4+16+2)
|
|
|
|
/* Entries are aligned on 4 byte boundaries */
|
|
typedef struct attr_entry {
|
|
u_int32_t offset; /* file offset to data */
|
|
u_int32_t length; /* size of attribute data */
|
|
u_int16_t flags;
|
|
u_int8_t namelen;
|
|
u_int8_t name[1]; /* NULL-terminated UTF-8 name (up to 128 bytes max) */
|
|
} __attribute__((aligned(2), packed)) attr_entry_t;
|
|
|
|
|
|
/* Header + entries must fit into 64K. Data may extend beyond 64K. */
|
|
typedef struct attr_header {
|
|
apple_double_header_t appledouble;
|
|
u_int32_t magic; /* == ATTR_HDR_MAGIC */
|
|
u_int32_t debug_tag; /* for debugging == file id of owning file */
|
|
u_int32_t total_size; /* file offset of end of attribute header + entries + data */
|
|
u_int32_t data_start; /* file offset to attribute data area */
|
|
u_int32_t data_length; /* length of attribute data area */
|
|
u_int32_t reserved[3];
|
|
u_int16_t flags;
|
|
u_int16_t num_attrs;
|
|
} __attribute__((aligned(2), packed)) attr_header_t;
|
|
|
|
|
|
/* Empty Resource Fork Header */
|
|
typedef struct rsrcfork_header {
|
|
u_int32_t fh_DataOffset;
|
|
u_int32_t fh_MapOffset;
|
|
u_int32_t fh_DataLength;
|
|
u_int32_t fh_MapLength;
|
|
u_int8_t systemData[112];
|
|
u_int8_t appData[128];
|
|
u_int32_t mh_DataOffset;
|
|
u_int32_t mh_MapOffset;
|
|
u_int32_t mh_DataLength;
|
|
u_int32_t mh_MapLength;
|
|
u_int32_t mh_Next;
|
|
u_int16_t mh_RefNum;
|
|
u_int8_t mh_Attr;
|
|
u_int8_t mh_InMemoryAttr;
|
|
u_int16_t mh_Types;
|
|
u_int16_t mh_Names;
|
|
u_int16_t typeCount;
|
|
} __attribute__((aligned(2), packed)) rsrcfork_header_t;
|
|
|
|
#define RF_FIRST_RESOURCE 256
|
|
#define RF_NULL_MAP_LENGTH 30
|
|
#define RF_EMPTY_TAG "This resource fork intentionally left blank "
|
|
|
|
/* Runtime information about the attribute file. */
|
|
typedef struct attr_info {
|
|
vfs_context_t context;
|
|
vnode_t filevp;
|
|
size_t filesize;
|
|
size_t iosize;
|
|
u_int8_t *rawdata;
|
|
size_t rawsize; /* minimum of filesize or ATTR_MAX_HDR_SIZE */
|
|
apple_double_header_t *filehdr;
|
|
apple_double_entry_t *finderinfo;
|
|
apple_double_entry_t *rsrcfork;
|
|
attr_header_t *attrhdr;
|
|
attr_entry_t *attr_entry;
|
|
u_int8_t readonly;
|
|
u_int8_t emptyfinderinfo;
|
|
} attr_info_t;
|
|
|
|
|
|
#define ATTR_SETTING 1
|
|
|
|
#define ATTR_ALIGN 3L /* Use four-byte alignment */
|
|
|
|
#define ATTR_ENTRY_LENGTH(namelen) \
|
|
((sizeof(attr_entry_t) - 1 + (namelen) + ATTR_ALIGN) & (~ATTR_ALIGN))
|
|
|
|
#define ATTR_NEXT(ae) \
|
|
(attr_entry_t *)((u_int8_t *)(ae) + ATTR_ENTRY_LENGTH((ae)->namelen))
|
|
|
|
#define ATTR_VALID(ae, ai) \
|
|
((&(ae)->namelen < ((ai).rawdata + (ai).rawsize)) && \
|
|
(u_int8_t *)ATTR_NEXT(ae) <= ((ai).rawdata + (ai).rawsize))
|
|
|
|
#define SWAP16(x) OSSwapBigToHostInt16((x))
|
|
#define SWAP32(x) OSSwapBigToHostInt32((x))
|
|
#define SWAP64(x) OSSwapBigToHostInt64((x))
|
|
|
|
|
|
static u_int32_t emptyfinfo[8] = {0};
|
|
|
|
|
|
/*
|
|
* Local support routines
|
|
*/
|
|
static void close_xattrfile(vnode_t xvp, int fileflags, vfs_context_t context);
|
|
|
|
static int open_xattrfile(vnode_t vp, int fileflags, vnode_t *xvpp, vfs_context_t context);
|
|
|
|
static int create_xattrfile(vnode_t xvp, u_int32_t fileid, vfs_context_t context);
|
|
|
|
static int remove_xattrfile(vnode_t xvp, vfs_context_t context);
|
|
|
|
static int get_xattrinfo(vnode_t xvp, int setting, attr_info_t *ainfop, vfs_context_t context);
|
|
|
|
static void rel_xattrinfo(attr_info_t *ainfop);
|
|
|
|
static int write_xattrinfo(attr_info_t *ainfop);
|
|
|
|
static void init_empty_resource_fork(rsrcfork_header_t * rsrcforkhdr);
|
|
|
|
static int lock_xattrfile(vnode_t xvp, short locktype, vfs_context_t context);
|
|
|
|
static int unlock_xattrfile(vnode_t xvp, vfs_context_t context);
|
|
|
|
|
|
#if BYTE_ORDER == LITTLE_ENDIAN
|
|
static void swap_adhdr(apple_double_header_t *adh);
|
|
static void swap_attrhdr(attr_header_t *ah, attr_info_t* info);
|
|
|
|
#else
|
|
#define swap_adhdr(x)
|
|
#define swap_attrhdr(x, y)
|
|
#endif
|
|
|
|
static int check_and_swap_attrhdr(attr_header_t *ah, attr_info_t* ainfop);
|
|
static int shift_data_down(vnode_t xvp, off_t start, size_t len, off_t delta, vfs_context_t context);
|
|
static int shift_data_up(vnode_t xvp, off_t start, size_t len, off_t delta, vfs_context_t context);
|
|
|
|
|
|
/*
|
|
* Sanity check and swap the header of an AppleDouble file. Assumes the buffer
|
|
* is in big endian (as it would exist on disk). Verifies the following:
|
|
* - magic field
|
|
* - version field
|
|
* - number of entries
|
|
* - that each entry fits within the file size
|
|
*
|
|
* If the header is invalid, ENOATTR is returned.
|
|
*
|
|
* NOTE: Does not attempt to validate the extended attributes header that
|
|
* may be embedded in the Finder Info entry.
|
|
*/
|
|
static int
|
|
check_and_swap_apple_double_header(attr_info_t *ainfop)
|
|
{
|
|
int i, j;
|
|
u_int32_t header_end;
|
|
u_int32_t entry_end;
|
|
size_t rawsize;
|
|
apple_double_header_t *header;
|
|
|
|
rawsize = ainfop->rawsize;
|
|
header = (apple_double_header_t *) ainfop->rawdata;
|
|
|
|
/* Is the file big enough to contain an AppleDouble header? */
|
|
if (rawsize < offsetof(apple_double_header_t, entries)) {
|
|
return ENOATTR;
|
|
}
|
|
|
|
/* Swap the AppleDouble header fields to native order */
|
|
header->magic = SWAP32(header->magic);
|
|
header->version = SWAP32(header->version);
|
|
header->numEntries = SWAP16(header->numEntries);
|
|
|
|
/* Sanity check the AppleDouble header fields */
|
|
if (header->magic != ADH_MAGIC ||
|
|
header->version != ADH_VERSION ||
|
|
header->numEntries < 1 ||
|
|
header->numEntries > 15) {
|
|
return ENOATTR;
|
|
}
|
|
|
|
/* Calculate where the entries[] array ends */
|
|
header_end = offsetof(apple_double_header_t, entries) +
|
|
header->numEntries * sizeof(apple_double_entry_t);
|
|
|
|
/* Is the file big enough to contain the AppleDouble entries? */
|
|
if (rawsize < header_end) {
|
|
return ENOATTR;
|
|
}
|
|
|
|
/* Swap and sanity check each AppleDouble entry */
|
|
for (i = 0; i < header->numEntries; i++) {
|
|
/* Swap the per-entry fields to native order */
|
|
header->entries[i].type = SWAP32(header->entries[i].type);
|
|
header->entries[i].offset = SWAP32(header->entries[i].offset);
|
|
header->entries[i].length = SWAP32(header->entries[i].length);
|
|
|
|
entry_end = header->entries[i].offset + header->entries[i].length;
|
|
|
|
/*
|
|
* Does the entry's content start within the header itself,
|
|
* did the addition overflow, or does the entry's content
|
|
* extend past the end of the file?
|
|
*/
|
|
if (header->entries[i].offset < header_end ||
|
|
entry_end < header->entries[i].offset ||
|
|
entry_end > ainfop->filesize) {
|
|
return ENOATTR;
|
|
}
|
|
|
|
/*
|
|
* Does the current entry's content overlap with a previous
|
|
* entry's content?
|
|
*
|
|
* Yes, this is O(N**2), and there are more efficient algorithms
|
|
* for testing pairwise overlap of N ranges when N is large.
|
|
* But we have already ensured N < 16, and N is almost always 2.
|
|
* So there's no point in using a more complex algorithm.
|
|
*/
|
|
|
|
for (j = 0; j < i; j++) {
|
|
if (entry_end > header->entries[j].offset &&
|
|
header->entries[j].offset + header->entries[j].length > header->entries[i].offset) {
|
|
return ENOATTR;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Retrieve the data of an extended attribute.
|
|
*/
|
|
static int
|
|
default_getxattr(vnode_t vp, const char *name, uio_t uio, size_t *size,
|
|
__unused int options, vfs_context_t context)
|
|
{
|
|
vnode_t xvp = NULL;
|
|
attr_info_t ainfo;
|
|
attr_header_t *header;
|
|
attr_entry_t *entry;
|
|
u_int8_t *attrdata;
|
|
u_int32_t datalen;
|
|
size_t namelen;
|
|
int isrsrcfork;
|
|
int fileflags;
|
|
int i;
|
|
int error;
|
|
|
|
fileflags = FREAD;
|
|
if (strncmp(name, XATTR_RESOURCEFORK_NAME, sizeof(XATTR_RESOURCEFORK_NAME)) == 0) {
|
|
isrsrcfork = 1;
|
|
/*
|
|
* Open the file locked (shared) since the Carbon
|
|
* File Manager may have the Apple Double file open
|
|
* and could be changing the resource fork.
|
|
*/
|
|
fileflags |= O_SHLOCK;
|
|
} else {
|
|
isrsrcfork = 0;
|
|
}
|
|
|
|
if ((error = open_xattrfile(vp, fileflags, &xvp, context))) {
|
|
return error;
|
|
}
|
|
if ((error = get_xattrinfo(xvp, 0, &ainfo, context))) {
|
|
close_xattrfile(xvp, fileflags, context);
|
|
return error;
|
|
}
|
|
|
|
/* Get the Finder Info. */
|
|
if (strncmp(name, XATTR_FINDERINFO_NAME, sizeof(XATTR_FINDERINFO_NAME)) == 0) {
|
|
if (ainfo.finderinfo == NULL || ainfo.emptyfinderinfo) {
|
|
error = ENOATTR;
|
|
} else if (uio == NULL) {
|
|
*size = FINDERINFOSIZE;
|
|
error = 0;
|
|
} else if (uio_offset(uio) != 0) {
|
|
error = EINVAL;
|
|
} else if (uio_resid(uio) < FINDERINFOSIZE) {
|
|
error = ERANGE;
|
|
} else {
|
|
attrdata = (u_int8_t*)ainfo.filehdr + ainfo.finderinfo->offset;
|
|
error = uiomove((caddr_t)attrdata, FINDERINFOSIZE, uio);
|
|
}
|
|
goto out;
|
|
}
|
|
|
|
/* Read the Resource Fork. */
|
|
if (isrsrcfork) {
|
|
if (!vnode_isreg(vp)) {
|
|
error = EPERM;
|
|
} else if (ainfo.rsrcfork == NULL) {
|
|
error = ENOATTR;
|
|
} else if (uio == NULL) {
|
|
*size = (size_t)ainfo.rsrcfork->length;
|
|
} else {
|
|
uio_setoffset(uio, uio_offset(uio) + ainfo.rsrcfork->offset);
|
|
error = VNOP_READ(xvp, uio, 0, context);
|
|
if (error == 0) {
|
|
uio_setoffset(uio, uio_offset(uio) - ainfo.rsrcfork->offset);
|
|
}
|
|
}
|
|
goto out;
|
|
}
|
|
|
|
if (ainfo.attrhdr == NULL || ainfo.attr_entry == NULL) {
|
|
error = ENOATTR;
|
|
goto out;
|
|
}
|
|
if (uio_offset(uio) != 0) {
|
|
error = EINVAL;
|
|
goto out;
|
|
}
|
|
error = ENOATTR;
|
|
namelen = strlen(name) + 1;
|
|
header = ainfo.attrhdr;
|
|
entry = ainfo.attr_entry;
|
|
/*
|
|
* Search for attribute name in the header.
|
|
*/
|
|
for (i = 0; i < header->num_attrs && ATTR_VALID(entry, ainfo); i++) {
|
|
if (strncmp((const char *)entry->name, name, namelen) == 0) {
|
|
datalen = entry->length;
|
|
if (uio == NULL) {
|
|
*size = datalen;
|
|
error = 0;
|
|
break;
|
|
}
|
|
if (uio_resid(uio) < (user_ssize_t)datalen) {
|
|
error = ERANGE;
|
|
break;
|
|
}
|
|
if (entry->offset + datalen < ATTR_MAX_HDR_SIZE) {
|
|
attrdata = ((u_int8_t *)header + entry->offset);
|
|
error = uiomove((caddr_t)attrdata, datalen, uio);
|
|
} else {
|
|
uio_setoffset(uio, entry->offset);
|
|
error = VNOP_READ(xvp, uio, 0, context);
|
|
uio_setoffset(uio, 0);
|
|
}
|
|
break;
|
|
}
|
|
entry = ATTR_NEXT(entry);
|
|
}
|
|
out:
|
|
rel_xattrinfo(&ainfo);
|
|
close_xattrfile(xvp, fileflags, context);
|
|
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Set the data of an extended attribute.
|
|
*/
|
|
static int
|
|
default_setxattr(vnode_t vp, const char *name, uio_t uio, int options, vfs_context_t context)
|
|
{
|
|
vnode_t xvp = NULL;
|
|
attr_info_t ainfo;
|
|
attr_header_t *header;
|
|
attr_entry_t *entry;
|
|
attr_entry_t *lastentry;
|
|
u_int8_t *attrdata;
|
|
size_t datalen;
|
|
size_t entrylen;
|
|
size_t datafreespace;
|
|
int namelen;
|
|
int found = 0;
|
|
int i;
|
|
int splitdata;
|
|
int fileflags;
|
|
int error;
|
|
char finfo[FINDERINFOSIZE];
|
|
|
|
datalen = uio_resid(uio);
|
|
if (datalen > XATTR_MAXSIZE) {
|
|
return EINVAL;
|
|
}
|
|
namelen = (int)strlen(name) + 1;
|
|
if (namelen > UINT8_MAX) {
|
|
return EINVAL;
|
|
}
|
|
entrylen = ATTR_ENTRY_LENGTH(namelen);
|
|
|
|
/*
|
|
* By convention, Finder Info that is all zeroes is equivalent to not
|
|
* having a Finder Info EA. So if we're trying to set the Finder Info
|
|
* to all zeroes, then delete it instead. If a file didn't have an
|
|
* AppleDouble file before, this prevents creating an AppleDouble file
|
|
* with no useful content.
|
|
*
|
|
* If neither XATTR_CREATE nor XATTR_REPLACE were specified, we check
|
|
* for all zeroes Finder Info before opening the AppleDouble file.
|
|
* But if either of those options were specified, we need to open the
|
|
* AppleDouble file to see whether there was already Finder Info (so we
|
|
* can return an error if needed); this case is handled further below.
|
|
*
|
|
* NOTE: this copies the Finder Info data into the "finfo" local.
|
|
*/
|
|
if (strncmp(name, XATTR_FINDERINFO_NAME, sizeof(XATTR_FINDERINFO_NAME)) == 0) {
|
|
/*
|
|
* TODO: check the XATTR_CREATE and XATTR_REPLACE flags.
|
|
* That means we probably have to open_xattrfile and get_xattrinfo.
|
|
*/
|
|
if (uio_offset(uio) != 0 || datalen != FINDERINFOSIZE) {
|
|
return EINVAL;
|
|
}
|
|
error = uiomove(finfo, (int)datalen, uio);
|
|
if (error) {
|
|
return error;
|
|
}
|
|
if ((options & (XATTR_CREATE | XATTR_REPLACE)) == 0 &&
|
|
bcmp(finfo, emptyfinfo, FINDERINFOSIZE) == 0) {
|
|
error = default_removexattr(vp, name, 0, context);
|
|
if (error == ENOATTR) {
|
|
error = 0;
|
|
}
|
|
return error;
|
|
}
|
|
}
|
|
|
|
start:
|
|
/*
|
|
* Open the file locked since setting an attribute
|
|
* can change the layout of the Apple Double file.
|
|
*/
|
|
fileflags = FREAD | FWRITE | O_EXLOCK;
|
|
if ((error = open_xattrfile(vp, O_CREAT | fileflags, &xvp, context))) {
|
|
return error;
|
|
}
|
|
if ((error = get_xattrinfo(xvp, ATTR_SETTING, &ainfo, context))) {
|
|
close_xattrfile(xvp, fileflags, context);
|
|
return error;
|
|
}
|
|
|
|
/* Set the Finder Info. */
|
|
if (strncmp(name, XATTR_FINDERINFO_NAME, sizeof(XATTR_FINDERINFO_NAME)) == 0) {
|
|
if (ainfo.finderinfo && !ainfo.emptyfinderinfo) {
|
|
/* attr exists and "create" was specified? */
|
|
if (options & XATTR_CREATE) {
|
|
error = EEXIST;
|
|
goto out;
|
|
}
|
|
} else {
|
|
/* attr doesn't exists and "replace" was specified? */
|
|
if (options & XATTR_REPLACE) {
|
|
error = ENOATTR;
|
|
goto out;
|
|
}
|
|
}
|
|
if (options != 0 && bcmp(finfo, emptyfinfo, FINDERINFOSIZE) == 0) {
|
|
/*
|
|
* Setting the Finder Info to all zeroes is equivalent to
|
|
* removing it. Close the xattr file and let
|
|
* default_removexattr do the work (including deleting
|
|
* the xattr file if there are no other xattrs).
|
|
*
|
|
* Note that we have to handle the case where the
|
|
* Finder Info was already all zeroes, and we ignore
|
|
* ENOATTR.
|
|
*
|
|
* The common case where options == 0 was handled above.
|
|
*/
|
|
rel_xattrinfo(&ainfo);
|
|
close_xattrfile(xvp, fileflags, context);
|
|
error = default_removexattr(vp, name, 0, context);
|
|
if (error == ENOATTR) {
|
|
error = 0;
|
|
}
|
|
return error;
|
|
}
|
|
if (ainfo.finderinfo) {
|
|
attrdata = (u_int8_t *)ainfo.filehdr + ainfo.finderinfo->offset;
|
|
bcopy(finfo, attrdata, datalen);
|
|
ainfo.iosize = sizeof(attr_header_t);
|
|
error = write_xattrinfo(&ainfo);
|
|
goto out;
|
|
}
|
|
error = ENOATTR;
|
|
goto out;
|
|
}
|
|
|
|
/* Write the Resource Fork. */
|
|
if (strncmp(name, XATTR_RESOURCEFORK_NAME, sizeof(XATTR_RESOURCEFORK_NAME)) == 0) {
|
|
off_t endoffset;
|
|
|
|
if (!vnode_isreg(vp)) {
|
|
error = EPERM;
|
|
goto out;
|
|
}
|
|
/* Make sure we have a rsrc fork pointer.. */
|
|
if (ainfo.rsrcfork == NULL) {
|
|
error = ENOATTR;
|
|
goto out;
|
|
}
|
|
if (ainfo.rsrcfork) {
|
|
if (ainfo.rsrcfork->length != 0) {
|
|
if (options & XATTR_CREATE) {
|
|
/* attr exists, and create specified ? */
|
|
error = EEXIST;
|
|
goto out;
|
|
}
|
|
} else {
|
|
/* Zero length AD rsrc fork */
|
|
if (options & XATTR_REPLACE) {
|
|
/* attr doesn't exist (0-length), but replace specified ? */
|
|
error = ENOATTR;
|
|
goto out;
|
|
}
|
|
}
|
|
} else {
|
|
/* We can't do much if we somehow didn't get an AD rsrc pointer */
|
|
error = ENOATTR;
|
|
goto out;
|
|
}
|
|
|
|
endoffset = uio_resid(uio) + uio_offset(uio); /* new size */
|
|
if (endoffset > UINT32_MAX || endoffset < 0) {
|
|
error = EINVAL;
|
|
goto out;
|
|
}
|
|
uio_setoffset(uio, uio_offset(uio) + ainfo.rsrcfork->offset);
|
|
error = VNOP_WRITE(xvp, uio, 0, context);
|
|
if (error) {
|
|
goto out;
|
|
}
|
|
uio_setoffset(uio, uio_offset(uio) - ainfo.rsrcfork->offset);
|
|
if (endoffset > ainfo.rsrcfork->length) {
|
|
ainfo.rsrcfork->length = (u_int32_t)endoffset;
|
|
ainfo.iosize = sizeof(attr_header_t);
|
|
error = write_xattrinfo(&ainfo);
|
|
goto out;
|
|
}
|
|
goto out;
|
|
}
|
|
|
|
if (datalen > ATTR_MAX_SIZE) {
|
|
return E2BIG; /* EINVAL instead ? */
|
|
}
|
|
|
|
if (ainfo.attrhdr == NULL) {
|
|
error = ENOATTR;
|
|
goto out;
|
|
}
|
|
header = ainfo.attrhdr;
|
|
entry = ainfo.attr_entry;
|
|
|
|
/* Check if data area crosses the maximum header size. */
|
|
if ((header->data_start + header->data_length + entrylen + datalen) > ATTR_MAX_HDR_SIZE) {
|
|
splitdata = 1; /* do data I/O separately */
|
|
} else {
|
|
splitdata = 0;
|
|
}
|
|
|
|
/*
|
|
* See if attribute already exists.
|
|
*/
|
|
for (i = 0; i < header->num_attrs && ATTR_VALID(entry, ainfo); i++) {
|
|
if (strncmp((const char *)entry->name, name, namelen) == 0) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
entry = ATTR_NEXT(entry);
|
|
}
|
|
|
|
if (found) {
|
|
if (options & XATTR_CREATE) {
|
|
error = EEXIST;
|
|
goto out;
|
|
}
|
|
if (datalen == entry->length) {
|
|
if (splitdata) {
|
|
uio_setoffset(uio, entry->offset);
|
|
error = VNOP_WRITE(xvp, uio, 0, context);
|
|
uio_setoffset(uio, 0);
|
|
if (error) {
|
|
printf("setxattr: VNOP_WRITE error %d\n", error);
|
|
}
|
|
} else {
|
|
attrdata = (u_int8_t *)header + entry->offset;
|
|
error = uiomove((caddr_t)attrdata, (int)datalen, uio);
|
|
if (error) {
|
|
goto out;
|
|
}
|
|
ainfo.iosize = ainfo.attrhdr->data_start + ainfo.attrhdr->data_length;
|
|
error = write_xattrinfo(&ainfo);
|
|
if (error) {
|
|
printf("setxattr: write_xattrinfo error %d\n", error);
|
|
}
|
|
}
|
|
goto out;
|
|
} else {
|
|
/*
|
|
* Brute force approach - just remove old entry and set new entry.
|
|
*/
|
|
found = 0;
|
|
rel_xattrinfo(&ainfo);
|
|
close_xattrfile(xvp, fileflags, context);
|
|
error = default_removexattr(vp, name, options, context);
|
|
if (error) {
|
|
return error;
|
|
}
|
|
/* Clear XATTR_REPLACE option since we just removed the attribute. */
|
|
options &= ~XATTR_REPLACE;
|
|
goto start; /* start over */
|
|
}
|
|
} else {
|
|
if (!ATTR_VALID(entry, ainfo)) {
|
|
error = ENOSPC;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (options & XATTR_REPLACE) {
|
|
error = ENOATTR; /* nothing there to replace */
|
|
goto out;
|
|
}
|
|
/* Check if header size limit has been reached. */
|
|
if ((header->data_start + entrylen) > ATTR_MAX_HDR_SIZE) {
|
|
error = ENOSPC;
|
|
goto out;
|
|
}
|
|
|
|
datafreespace = header->total_size - (header->data_start + header->data_length);
|
|
|
|
/* Check if we need more space. */
|
|
if ((datalen + entrylen) > datafreespace) {
|
|
size_t growsize;
|
|
|
|
growsize = roundup((datalen + entrylen) - datafreespace, ATTR_BUF_SIZE);
|
|
|
|
/* Clip roundup size when we can still fit in ATTR_MAX_HDR_SIZE. */
|
|
if (!splitdata && (header->total_size + growsize) > ATTR_MAX_HDR_SIZE) {
|
|
growsize = ATTR_MAX_HDR_SIZE - header->total_size;
|
|
}
|
|
|
|
ainfo.filesize += growsize;
|
|
error = vnode_setsize(xvp, ainfo.filesize, 0, context);
|
|
if (error) {
|
|
printf("setxattr: VNOP_TRUNCATE error %d\n", error);
|
|
}
|
|
if (error) {
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Move the resource fork out of the way.
|
|
*/
|
|
if (ainfo.rsrcfork) {
|
|
if (ainfo.rsrcfork->length != 0) {
|
|
shift_data_down(xvp,
|
|
ainfo.rsrcfork->offset,
|
|
ainfo.rsrcfork->length,
|
|
growsize, context);
|
|
}
|
|
ainfo.rsrcfork->offset += growsize;
|
|
}
|
|
ainfo.finderinfo->length += growsize;
|
|
header->total_size += growsize;
|
|
}
|
|
|
|
/* Make space for a new entry. */
|
|
if (splitdata) {
|
|
shift_data_down(xvp,
|
|
header->data_start,
|
|
header->data_length,
|
|
entrylen, context);
|
|
} else {
|
|
bcopy((u_int8_t *)header + header->data_start,
|
|
(u_int8_t *)header + header->data_start + entrylen,
|
|
header->data_length);
|
|
}
|
|
header->data_start += entrylen;
|
|
|
|
/* Fix up entry data offsets. */
|
|
lastentry = entry;
|
|
for (entry = ainfo.attr_entry; entry != lastentry && ATTR_VALID(entry, ainfo); entry = ATTR_NEXT(entry)) {
|
|
entry->offset += entrylen;
|
|
}
|
|
|
|
/*
|
|
* If the attribute data area is entirely within
|
|
* the header buffer, then just update the buffer,
|
|
* otherwise we'll write it separately to the file.
|
|
*/
|
|
if (splitdata) {
|
|
off_t offset;
|
|
|
|
/* Write new attribute data after the end of existing data. */
|
|
offset = header->data_start + header->data_length;
|
|
uio_setoffset(uio, offset);
|
|
error = VNOP_WRITE(xvp, uio, 0, context);
|
|
uio_setoffset(uio, 0);
|
|
if (error) {
|
|
printf("setxattr: VNOP_WRITE error %d\n", error);
|
|
goto out;
|
|
}
|
|
} else {
|
|
attrdata = (u_int8_t *)header + header->data_start + header->data_length;
|
|
|
|
error = uiomove((caddr_t)attrdata, (int)datalen, uio);
|
|
if (error) {
|
|
printf("setxattr: uiomove error %d\n", error);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/* Create the attribute entry. */
|
|
lastentry->length = (u_int32_t)datalen;
|
|
lastentry->offset = header->data_start + header->data_length;
|
|
lastentry->namelen = (u_int8_t)namelen;
|
|
lastentry->flags = 0;
|
|
bcopy(name, &lastentry->name[0], namelen);
|
|
|
|
/* Update the attributes header. */
|
|
header->num_attrs++;
|
|
header->data_length += datalen;
|
|
|
|
if (splitdata) {
|
|
/* Only write the entries, since the data was written separately. */
|
|
ainfo.iosize = ainfo.attrhdr->data_start;
|
|
} else {
|
|
/* The entry and data are both in the header; write them together. */
|
|
ainfo.iosize = ainfo.attrhdr->data_start + ainfo.attrhdr->data_length;
|
|
}
|
|
error = write_xattrinfo(&ainfo);
|
|
if (error) {
|
|
printf("setxattr: write_xattrinfo error %d\n", error);
|
|
}
|
|
|
|
out:
|
|
rel_xattrinfo(&ainfo);
|
|
close_xattrfile(xvp, fileflags, context);
|
|
|
|
/* Touch the change time if we changed an attribute. */
|
|
if (error == 0) {
|
|
struct vnode_attr va;
|
|
|
|
/* Re-write the mtime to cause a ctime change. */
|
|
VATTR_INIT(&va);
|
|
VATTR_WANTED(&va, va_modify_time);
|
|
if (vnode_getattr(vp, &va, context) == 0) {
|
|
VATTR_INIT(&va);
|
|
VATTR_SET(&va, va_modify_time, va.va_modify_time);
|
|
(void) vnode_setattr(vp, &va, context);
|
|
}
|
|
}
|
|
|
|
post_event_if_success(vp, error, NOTE_ATTRIB);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
/*
|
|
* Remove an extended attribute.
|
|
*/
|
|
static int
|
|
default_removexattr(vnode_t vp, const char *name, __unused int options, vfs_context_t context)
|
|
{
|
|
vnode_t xvp = NULL;
|
|
attr_info_t ainfo;
|
|
attr_header_t *header;
|
|
attr_entry_t *entry;
|
|
attr_entry_t *oldslot;
|
|
u_int8_t *attrdata;
|
|
u_int32_t dataoff;
|
|
size_t datalen;
|
|
size_t entrylen;
|
|
int namelen;
|
|
int found = 0, lastone = 0;
|
|
int i;
|
|
int splitdata;
|
|
int attrcount = 0;
|
|
int isrsrcfork;
|
|
int fileflags;
|
|
int error;
|
|
|
|
fileflags = FREAD | FWRITE;
|
|
if (strncmp(name, XATTR_RESOURCEFORK_NAME, sizeof(XATTR_RESOURCEFORK_NAME)) == 0) {
|
|
isrsrcfork = 1;
|
|
/*
|
|
* Open the file locked (exclusive) since the Carbon
|
|
* File Manager may have the Apple Double file open
|
|
* and could be changing the resource fork.
|
|
*/
|
|
fileflags |= O_EXLOCK;
|
|
} else {
|
|
isrsrcfork = 0;
|
|
}
|
|
|
|
if ((error = open_xattrfile(vp, fileflags, &xvp, context))) {
|
|
return error;
|
|
}
|
|
if ((error = get_xattrinfo(xvp, 0, &ainfo, context))) {
|
|
close_xattrfile(xvp, fileflags, context);
|
|
return error;
|
|
}
|
|
if (ainfo.attrhdr) {
|
|
attrcount += ainfo.attrhdr->num_attrs;
|
|
}
|
|
if (ainfo.rsrcfork) {
|
|
++attrcount;
|
|
}
|
|
if (ainfo.finderinfo && !ainfo.emptyfinderinfo) {
|
|
++attrcount;
|
|
}
|
|
|
|
/* Clear the Finder Info. */
|
|
if (strncmp(name, XATTR_FINDERINFO_NAME, sizeof(XATTR_FINDERINFO_NAME)) == 0) {
|
|
if (ainfo.finderinfo == NULL || ainfo.emptyfinderinfo) {
|
|
error = ENOATTR;
|
|
goto out;
|
|
}
|
|
/* On removal of last attribute the ._ file is removed. */
|
|
if (--attrcount == 0) {
|
|
goto out;
|
|
}
|
|
attrdata = (u_int8_t *)ainfo.filehdr + ainfo.finderinfo->offset;
|
|
bzero((caddr_t)attrdata, FINDERINFOSIZE);
|
|
error = write_xattrinfo(&ainfo);
|
|
goto out;
|
|
}
|
|
|
|
/* Clear the Resource Fork. */
|
|
if (isrsrcfork) {
|
|
if (!vnode_isreg(vp)) {
|
|
error = EPERM;
|
|
goto out;
|
|
}
|
|
if (ainfo.rsrcfork == NULL || ainfo.rsrcfork->length == 0) {
|
|
error = ENOATTR;
|
|
goto out;
|
|
}
|
|
/* On removal of last attribute the ._ file is removed. */
|
|
if (--attrcount == 0) {
|
|
goto out;
|
|
}
|
|
/*
|
|
* XXX
|
|
* If the resource fork isn't the last AppleDouble
|
|
* entry then the space needs to be reclaimed by
|
|
* shifting the entries after the resource fork.
|
|
*/
|
|
if ((ainfo.rsrcfork->offset + ainfo.rsrcfork->length) == ainfo.filesize) {
|
|
ainfo.filesize -= ainfo.rsrcfork->length;
|
|
error = vnode_setsize(xvp, ainfo.filesize, 0, context);
|
|
}
|
|
if (error == 0) {
|
|
ainfo.rsrcfork->length = 0;
|
|
ainfo.iosize = sizeof(attr_header_t);
|
|
error = write_xattrinfo(&ainfo);
|
|
}
|
|
goto out;
|
|
}
|
|
|
|
if (ainfo.attrhdr == NULL) {
|
|
error = ENOATTR;
|
|
goto out;
|
|
}
|
|
namelen = (int)strlen(name) + 1;
|
|
header = ainfo.attrhdr;
|
|
entry = ainfo.attr_entry;
|
|
|
|
/*
|
|
* See if this attribute exists.
|
|
*/
|
|
for (i = 0; i < header->num_attrs && ATTR_VALID(entry, ainfo); i++) {
|
|
if (strncmp((const char *)entry->name, name, namelen) == 0) {
|
|
found = 1;
|
|
if ((i + 1) == header->num_attrs) {
|
|
lastone = 1;
|
|
}
|
|
break;
|
|
}
|
|
entry = ATTR_NEXT(entry);
|
|
}
|
|
if (!found) {
|
|
error = ENOATTR;
|
|
goto out;
|
|
}
|
|
/* On removal of last attribute the ._ file is removed. */
|
|
if (--attrcount == 0) {
|
|
goto out;
|
|
}
|
|
|
|
datalen = entry->length;
|
|
dataoff = entry->offset;
|
|
entrylen = ATTR_ENTRY_LENGTH(namelen);
|
|
if ((header->data_start + header->data_length) > ATTR_MAX_HDR_SIZE) {
|
|
splitdata = 1;
|
|
} else {
|
|
splitdata = 0;
|
|
}
|
|
|
|
/* Remove the attribute entry. */
|
|
if (!lastone) {
|
|
bcopy((u_int8_t *)entry + entrylen, (u_int8_t *)entry,
|
|
((size_t)header + header->data_start) - ((size_t)entry + entrylen));
|
|
}
|
|
|
|
/* Adjust the attribute data. */
|
|
if (splitdata) {
|
|
shift_data_up(xvp,
|
|
header->data_start,
|
|
dataoff - header->data_start,
|
|
entrylen,
|
|
context);
|
|
if (!lastone) {
|
|
shift_data_up(xvp,
|
|
dataoff + datalen,
|
|
(header->data_start + header->data_length) - (dataoff + datalen),
|
|
datalen + entrylen,
|
|
context);
|
|
}
|
|
/* XXX write zeros to freed space ? */
|
|
ainfo.iosize = ainfo.attrhdr->data_start - entrylen;
|
|
} else {
|
|
bcopy((u_int8_t *)header + header->data_start,
|
|
(u_int8_t *)header + header->data_start - entrylen,
|
|
dataoff - header->data_start);
|
|
if (!lastone) {
|
|
bcopy((u_int8_t *)header + dataoff + datalen,
|
|
(u_int8_t *)header + dataoff - entrylen,
|
|
(header->data_start + header->data_length) - (dataoff + datalen));
|
|
}
|
|
bzero(((u_int8_t *)header + header->data_start + header->data_length) - (datalen + entrylen), (datalen + entrylen));
|
|
ainfo.iosize = ainfo.attrhdr->data_start + ainfo.attrhdr->data_length;
|
|
}
|
|
|
|
/* Adjust the header values and entry offsets. */
|
|
header->num_attrs--;
|
|
header->data_start -= entrylen;
|
|
header->data_length -= datalen;
|
|
|
|
oldslot = entry;
|
|
entry = ainfo.attr_entry;
|
|
for (i = 0; i < header->num_attrs && ATTR_VALID(entry, ainfo); i++) {
|
|
entry->offset -= entrylen;
|
|
if (entry >= oldslot) {
|
|
entry->offset -= datalen;
|
|
}
|
|
entry = ATTR_NEXT(entry);
|
|
}
|
|
error = write_xattrinfo(&ainfo);
|
|
if (error) {
|
|
printf("removexattr: write_xattrinfo error %d\n", error);
|
|
}
|
|
out:
|
|
rel_xattrinfo(&ainfo);
|
|
|
|
/* When there are no more attributes remove the ._ file. */
|
|
if (attrcount == 0) {
|
|
if (fileflags & O_EXLOCK) {
|
|
(void) unlock_xattrfile(xvp, context);
|
|
}
|
|
VNOP_CLOSE(xvp, fileflags, context);
|
|
vnode_rele(xvp);
|
|
error = remove_xattrfile(xvp, context);
|
|
vnode_put(xvp);
|
|
} else {
|
|
close_xattrfile(xvp, fileflags, context);
|
|
}
|
|
/* Touch the change time if we changed an attribute. */
|
|
if (error == 0) {
|
|
struct vnode_attr va;
|
|
|
|
/* Re-write the mtime to cause a ctime change. */
|
|
VATTR_INIT(&va);
|
|
VATTR_WANTED(&va, va_modify_time);
|
|
if (vnode_getattr(vp, &va, context) == 0) {
|
|
VATTR_INIT(&va);
|
|
VATTR_SET(&va, va_modify_time, va.va_modify_time);
|
|
(void) vnode_setattr(vp, &va, context);
|
|
}
|
|
}
|
|
|
|
post_event_if_success(vp, error, NOTE_ATTRIB);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
/*
|
|
* Retrieve the list of extended attribute names.
|
|
*/
|
|
static int
|
|
default_listxattr(vnode_t vp, uio_t uio, size_t *size, __unused int options, vfs_context_t context)
|
|
{
|
|
vnode_t xvp = NULL;
|
|
attr_info_t ainfo;
|
|
attr_entry_t *entry;
|
|
int i, count;
|
|
int error;
|
|
|
|
/*
|
|
* We do not zero "*size" here as we don't want to stomp a size set when
|
|
* VNOP_LISTXATTR processed any native EAs. That size is initially zeroed by the
|
|
* system call layer, up in listxattr or flistxattr.
|
|
*/
|
|
|
|
if ((error = open_xattrfile(vp, FREAD, &xvp, context))) {
|
|
if (error == ENOATTR) {
|
|
error = 0;
|
|
}
|
|
return error;
|
|
}
|
|
if ((error = get_xattrinfo(xvp, 0, &ainfo, context))) {
|
|
if (error == ENOATTR) {
|
|
error = 0;
|
|
}
|
|
close_xattrfile(xvp, FREAD, context);
|
|
return error;
|
|
}
|
|
|
|
/* Check for Finder Info. */
|
|
if (ainfo.finderinfo && !ainfo.emptyfinderinfo) {
|
|
if (uio == NULL) {
|
|
*size += sizeof(XATTR_FINDERINFO_NAME);
|
|
} else if (uio_resid(uio) < (user_ssize_t)sizeof(XATTR_FINDERINFO_NAME)) {
|
|
error = ERANGE;
|
|
goto out;
|
|
} else {
|
|
error = uiomove(XATTR_FINDERINFO_NAME,
|
|
sizeof(XATTR_FINDERINFO_NAME), uio);
|
|
if (error) {
|
|
error = ERANGE;
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Check for Resource Fork. */
|
|
if (vnode_isreg(vp) && ainfo.rsrcfork) {
|
|
if (uio == NULL) {
|
|
*size += sizeof(XATTR_RESOURCEFORK_NAME);
|
|
} else if (uio_resid(uio) < (user_ssize_t)sizeof(XATTR_RESOURCEFORK_NAME)) {
|
|
error = ERANGE;
|
|
goto out;
|
|
} else {
|
|
error = uiomove(XATTR_RESOURCEFORK_NAME,
|
|
sizeof(XATTR_RESOURCEFORK_NAME), uio);
|
|
if (error) {
|
|
error = ERANGE;
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Check for attributes. */
|
|
if (ainfo.attrhdr) {
|
|
count = ainfo.attrhdr->num_attrs;
|
|
for (i = 0, entry = ainfo.attr_entry; i < count && ATTR_VALID(entry, ainfo); i++) {
|
|
if (xattr_protected((const char *)entry->name) ||
|
|
((entry->namelen < XATTR_MAXNAMELEN) &&
|
|
(entry->name[entry->namelen] == '\0') &&
|
|
(xattr_validatename((const char *)entry->name) != 0))) {
|
|
entry = ATTR_NEXT(entry);
|
|
continue;
|
|
}
|
|
if (uio == NULL) {
|
|
*size += entry->namelen;
|
|
entry = ATTR_NEXT(entry);
|
|
continue;
|
|
}
|
|
if (uio_resid(uio) < entry->namelen) {
|
|
error = ERANGE;
|
|
break;
|
|
}
|
|
error = uiomove((caddr_t) entry->name, entry->namelen, uio);
|
|
if (error) {
|
|
if (error != EFAULT) {
|
|
error = ERANGE;
|
|
}
|
|
break;
|
|
}
|
|
entry = ATTR_NEXT(entry);
|
|
}
|
|
}
|
|
out:
|
|
rel_xattrinfo(&ainfo);
|
|
close_xattrfile(xvp, FREAD, context);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int
|
|
open_xattrfile(vnode_t vp, int fileflags, vnode_t *xvpp, vfs_context_t context)
|
|
{
|
|
vnode_t xvp = NULLVP;
|
|
vnode_t dvp = NULLVP;
|
|
struct vnode_attr *va = NULL;
|
|
struct nameidata *nd = NULL;
|
|
char smallname[64];
|
|
char *filename = NULL;
|
|
const char *basename = NULL;
|
|
size_t alloc_len;
|
|
size_t copy_len;
|
|
errno_t error;
|
|
int opened = 0;
|
|
int referenced = 0;
|
|
|
|
if (vnode_isvroot(vp) && vnode_isdir(vp)) {
|
|
/*
|
|
* For the root directory use "._." to hold the attributes.
|
|
*/
|
|
filename = &smallname[0];
|
|
snprintf(filename, sizeof(smallname), "%s%s", ATTR_FILE_PREFIX, ".");
|
|
dvp = vp; /* the "._." file resides in the root dir */
|
|
goto lookup;
|
|
}
|
|
if ((dvp = vnode_getparent(vp)) == NULLVP) {
|
|
error = ENOATTR;
|
|
goto out;
|
|
}
|
|
if ((basename = vnode_getname(vp)) == NULL) {
|
|
error = ENOATTR;
|
|
goto out;
|
|
}
|
|
|
|
/* "._" Attribute files cannot have attributes */
|
|
if (vp->v_type == VREG && strlen(basename) > 2 &&
|
|
basename[0] == '.' && basename[1] == '_') {
|
|
error = EPERM;
|
|
goto out;
|
|
}
|
|
filename = &smallname[0];
|
|
alloc_len = snprintf(filename, sizeof(smallname), "%s%s", ATTR_FILE_PREFIX, basename);
|
|
if (alloc_len >= sizeof(smallname)) {
|
|
alloc_len++; /* snprintf result doesn't include '\0' */
|
|
filename = kheap_alloc(KHEAP_TEMP, alloc_len, Z_WAITOK);
|
|
copy_len = snprintf(filename, alloc_len, "%s%s", ATTR_FILE_PREFIX, basename);
|
|
}
|
|
/*
|
|
* Note that the lookup here does not authorize. Since we are looking
|
|
* up in the same directory that we already have the file vnode in,
|
|
* we must have been given the file vnode legitimately. Read/write
|
|
* access has already been authorized in layers above for calls from
|
|
* userspace, and the authorization code using this path to read
|
|
* file security from the EA must always get access
|
|
*/
|
|
lookup:
|
|
nd = kheap_alloc(KHEAP_TEMP, sizeof(struct nameidata), Z_WAITOK);
|
|
NDINIT(nd, LOOKUP, OP_OPEN, LOCKLEAF | NOFOLLOW | USEDVP | DONOTAUTH,
|
|
UIO_SYSSPACE, CAST_USER_ADDR_T(filename), context);
|
|
nd->ni_dvp = dvp;
|
|
|
|
va = kheap_alloc(KHEAP_TEMP, sizeof(struct vnode_attr), Z_WAITOK);
|
|
|
|
if (fileflags & O_CREAT) {
|
|
nd->ni_cnd.cn_nameiop = CREATE;
|
|
#if CONFIG_TRIGGERS
|
|
nd->ni_op = OP_LINK;
|
|
#endif
|
|
if (dvp != vp) {
|
|
nd->ni_cnd.cn_flags |= LOCKPARENT;
|
|
}
|
|
if ((error = namei(nd))) {
|
|
nd->ni_dvp = NULLVP;
|
|
error = ENOATTR;
|
|
goto out;
|
|
}
|
|
if ((xvp = nd->ni_vp) == NULLVP) {
|
|
uid_t uid;
|
|
gid_t gid;
|
|
mode_t umode;
|
|
|
|
/*
|
|
* Pick up uid/gid/mode from target file.
|
|
*/
|
|
VATTR_INIT(va);
|
|
VATTR_WANTED(va, va_uid);
|
|
VATTR_WANTED(va, va_gid);
|
|
VATTR_WANTED(va, va_mode);
|
|
if (VNOP_GETATTR(vp, va, context) == 0 &&
|
|
VATTR_IS_SUPPORTED(va, va_uid) &&
|
|
VATTR_IS_SUPPORTED(va, va_gid) &&
|
|
VATTR_IS_SUPPORTED(va, va_mode)) {
|
|
uid = va->va_uid;
|
|
gid = va->va_gid;
|
|
umode = va->va_mode & (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
|
|
} else { /* fallback values */
|
|
uid = KAUTH_UID_NONE;
|
|
gid = KAUTH_GID_NONE;
|
|
umode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
|
|
}
|
|
|
|
VATTR_INIT(va);
|
|
VATTR_SET(va, va_type, VREG);
|
|
VATTR_SET(va, va_mode, umode);
|
|
if (uid != KAUTH_UID_NONE) {
|
|
VATTR_SET(va, va_uid, uid);
|
|
}
|
|
if (gid != KAUTH_GID_NONE) {
|
|
VATTR_SET(va, va_gid, gid);
|
|
}
|
|
|
|
error = vn_create(dvp, &nd->ni_vp, nd, va,
|
|
VN_CREATE_NOAUTH | VN_CREATE_NOINHERIT | VN_CREATE_NOLABEL,
|
|
0, NULL,
|
|
context);
|
|
if (error) {
|
|
error = ENOATTR;
|
|
} else {
|
|
xvp = nd->ni_vp;
|
|
}
|
|
}
|
|
nameidone(nd);
|
|
if (dvp != vp) {
|
|
vnode_put(dvp); /* drop iocount from LOCKPARENT request above */
|
|
}
|
|
if (error) {
|
|
goto out;
|
|
}
|
|
} else {
|
|
if ((error = namei(nd))) {
|
|
nd->ni_dvp = NULLVP;
|
|
error = ENOATTR;
|
|
goto out;
|
|
}
|
|
xvp = nd->ni_vp;
|
|
nameidone(nd);
|
|
}
|
|
nd->ni_dvp = NULLVP;
|
|
|
|
if (xvp->v_type != VREG) {
|
|
error = ENOATTR;
|
|
goto out;
|
|
}
|
|
/*
|
|
* Owners must match.
|
|
*/
|
|
VATTR_INIT(va);
|
|
VATTR_WANTED(va, va_uid);
|
|
if (VNOP_GETATTR(vp, va, context) == 0 && VATTR_IS_SUPPORTED(va, va_uid)) {
|
|
uid_t owner = va->va_uid;
|
|
|
|
VATTR_INIT(va);
|
|
VATTR_WANTED(va, va_uid);
|
|
if (VNOP_GETATTR(xvp, va, context) == 0 && (owner != va->va_uid)) {
|
|
error = ENOATTR; /* don't use this "._" file */
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if ((error = VNOP_OPEN(xvp, fileflags & ~(O_EXLOCK | O_SHLOCK), context))) {
|
|
error = ENOATTR;
|
|
goto out;
|
|
}
|
|
opened = 1;
|
|
|
|
if ((error = vnode_ref(xvp))) {
|
|
goto out;
|
|
}
|
|
referenced = 1;
|
|
|
|
/* If create was requested, make sure file header exists. */
|
|
if (fileflags & O_CREAT) {
|
|
VATTR_INIT(va);
|
|
VATTR_WANTED(va, va_data_size);
|
|
VATTR_WANTED(va, va_fileid);
|
|
VATTR_WANTED(va, va_nlink);
|
|
if ((error = vnode_getattr(xvp, va, context)) != 0) {
|
|
error = EPERM;
|
|
goto out;
|
|
}
|
|
|
|
/* If the file is empty then add a default header. */
|
|
if (va->va_data_size == 0) {
|
|
/* Don't adopt hard-linked "._" files. */
|
|
if (VATTR_IS_SUPPORTED(va, va_nlink) && va->va_nlink > 1) {
|
|
error = EPERM;
|
|
goto out;
|
|
}
|
|
if ((error = create_xattrfile(xvp, (u_int32_t)va->va_fileid, context))) {
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
/* Apply file locking if requested. */
|
|
if (fileflags & (O_EXLOCK | O_SHLOCK)) {
|
|
short locktype;
|
|
|
|
locktype = (fileflags & O_EXLOCK) ? F_WRLCK : F_RDLCK;
|
|
error = lock_xattrfile(xvp, locktype, context);
|
|
if (error) {
|
|
error = ENOATTR;
|
|
}
|
|
}
|
|
out:
|
|
if (error) {
|
|
if (xvp != NULLVP) {
|
|
if (opened) {
|
|
(void) VNOP_CLOSE(xvp, fileflags, context);
|
|
}
|
|
|
|
if (fileflags & O_CREAT) {
|
|
/* Delete the xattr file if we encountered any errors */
|
|
(void) remove_xattrfile(xvp, context);
|
|
}
|
|
|
|
if (referenced) {
|
|
(void) vnode_rele(xvp);
|
|
}
|
|
(void) vnode_put(xvp);
|
|
xvp = NULLVP;
|
|
}
|
|
if ((error == ENOATTR) && (fileflags & O_CREAT)) {
|
|
error = EPERM;
|
|
}
|
|
}
|
|
/* Release resources after error-handling */
|
|
kheap_free(KHEAP_TEMP, nd, sizeof(struct nameidata));
|
|
kheap_free(KHEAP_TEMP, va, sizeof(struct vnode_attr));
|
|
if (dvp && (dvp != vp)) {
|
|
vnode_put(dvp);
|
|
}
|
|
if (basename) {
|
|
vnode_putname(basename);
|
|
}
|
|
if (filename && filename != &smallname[0]) {
|
|
kheap_free(KHEAP_TEMP, filename, alloc_len);
|
|
}
|
|
|
|
*xvpp = xvp; /* return a referenced vnode */
|
|
return error;
|
|
}
|
|
|
|
static void
|
|
close_xattrfile(vnode_t xvp, int fileflags, vfs_context_t context)
|
|
{
|
|
// if (fileflags & FWRITE)
|
|
// (void) VNOP_FSYNC(xvp, MNT_WAIT, context);
|
|
|
|
if (fileflags & (O_EXLOCK | O_SHLOCK)) {
|
|
(void) unlock_xattrfile(xvp, context);
|
|
}
|
|
|
|
(void) VNOP_CLOSE(xvp, fileflags, context);
|
|
(void) vnode_rele(xvp);
|
|
(void) vnode_put(xvp);
|
|
}
|
|
|
|
static int
|
|
remove_xattrfile(vnode_t xvp, vfs_context_t context)
|
|
{
|
|
vnode_t dvp;
|
|
struct nameidata nd;
|
|
char *path = NULL;
|
|
int pathlen;
|
|
int error = 0;
|
|
|
|
path = zalloc(ZV_NAMEI);
|
|
pathlen = MAXPATHLEN;
|
|
error = vn_getpath(xvp, path, &pathlen);
|
|
if (error) {
|
|
zfree(ZV_NAMEI, path);
|
|
return error;
|
|
}
|
|
|
|
NDINIT(&nd, DELETE, OP_UNLINK, LOCKPARENT | NOFOLLOW | DONOTAUTH,
|
|
UIO_SYSSPACE, CAST_USER_ADDR_T(path), context);
|
|
error = namei(&nd);
|
|
zfree(ZV_NAMEI, path);
|
|
if (error) {
|
|
return error;
|
|
}
|
|
dvp = nd.ni_dvp;
|
|
xvp = nd.ni_vp;
|
|
|
|
error = VNOP_REMOVE(dvp, xvp, &nd.ni_cnd, 0, context);
|
|
nameidone(&nd);
|
|
vnode_put(dvp);
|
|
vnode_put(xvp);
|
|
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Read in and parse the AppleDouble header and entries, and the extended
|
|
* attribute header and entries if any. Populates the fields of ainfop
|
|
* based on the headers and entries found.
|
|
*
|
|
* The basic idea is to:
|
|
* - Read in up to ATTR_MAX_HDR_SIZE bytes of the start of the file. All
|
|
* AppleDouble entries, the extended attribute header, and extended
|
|
* attribute entries must lie within this part of the file; the rest of
|
|
* the AppleDouble handling code assumes this. Plus it allows us to
|
|
* somewhat optimize by doing a smaller number of larger I/Os.
|
|
* - Swap and sanity check the AppleDouble header (including the AppleDouble
|
|
* entries).
|
|
* - Find the Finder Info and Resource Fork entries, if any.
|
|
* - If we're going to be writing, try to make sure the Finder Info entry has
|
|
* room to store the extended attribute header, plus some space for extended
|
|
* attributes.
|
|
* - Swap and sanity check the extended attribute header and entries (if any).
|
|
*/
|
|
static int
|
|
get_xattrinfo(vnode_t xvp, int setting, attr_info_t *ainfop, vfs_context_t context)
|
|
{
|
|
uio_t auio = NULL;
|
|
void * buffer = NULL;
|
|
apple_double_header_t *filehdr;
|
|
struct vnode_attr va;
|
|
size_t iosize = 0;
|
|
int i;
|
|
int error;
|
|
|
|
bzero(ainfop, sizeof(attr_info_t));
|
|
ainfop->filevp = xvp;
|
|
ainfop->context = context;
|
|
VATTR_INIT(&va);
|
|
VATTR_WANTED(&va, va_data_size);
|
|
VATTR_WANTED(&va, va_fileid);
|
|
if ((error = vnode_getattr(xvp, &va, context))) {
|
|
goto bail;
|
|
}
|
|
ainfop->filesize = va.va_data_size;
|
|
|
|
/* When setting attributes, allow room for the header to grow. */
|
|
if (setting) {
|
|
iosize = ATTR_MAX_HDR_SIZE;
|
|
} else {
|
|
iosize = MIN(ATTR_MAX_HDR_SIZE, ainfop->filesize);
|
|
}
|
|
|
|
if (iosize == 0 || iosize < sizeof(apple_double_header_t)) {
|
|
error = ENOATTR;
|
|
goto bail;
|
|
}
|
|
|
|
ainfop->iosize = iosize;
|
|
buffer = kheap_alloc(KHEAP_DATA_BUFFERS, iosize, Z_WAITOK | Z_ZERO);
|
|
if (buffer == NULL) {
|
|
error = ENOMEM;
|
|
goto bail;
|
|
}
|
|
|
|
auio = uio_create(1, 0, UIO_SYSSPACE, UIO_READ);
|
|
uio_addiov(auio, (uintptr_t)buffer, iosize);
|
|
|
|
/* Read the file header. */
|
|
error = VNOP_READ(xvp, auio, 0, context);
|
|
if (error) {
|
|
goto bail;
|
|
}
|
|
ainfop->rawsize = iosize - uio_resid(auio);
|
|
ainfop->rawdata = (u_int8_t *)buffer;
|
|
|
|
filehdr = (apple_double_header_t *)buffer;
|
|
|
|
error = check_and_swap_apple_double_header(ainfop);
|
|
if (error) {
|
|
goto bail;
|
|
}
|
|
|
|
ainfop->filehdr = filehdr; /* valid AppleDouble header */
|
|
|
|
/* rel_xattrinfo is responsible for freeing the header buffer */
|
|
buffer = NULL;
|
|
|
|
/* Find the Finder Info and Resource Fork entries, if any */
|
|
for (i = 0; i < filehdr->numEntries; ++i) {
|
|
if (filehdr->entries[i].type == AD_FINDERINFO &&
|
|
filehdr->entries[i].length >= FINDERINFOSIZE) {
|
|
/* We found the Finder Info entry. */
|
|
ainfop->finderinfo = &filehdr->entries[i];
|
|
|
|
/* At this point check_and_swap_apple_double_header() call above
|
|
* verified that all apple double entires are valid:
|
|
* they point somewhere within the file.
|
|
*
|
|
* Now for finderinfo make sure that the fixed portion
|
|
* is within the buffer we read in.
|
|
*/
|
|
if (((ainfop->finderinfo->offset + FINDERINFOSIZE) > ainfop->finderinfo->offset) &&
|
|
((ainfop->finderinfo->offset + FINDERINFOSIZE) <= ainfop->rawsize)) {
|
|
/*
|
|
* Is the Finder Info "empty" (all zeroes)? If so,
|
|
* we'll pretend like the Finder Info extended attribute
|
|
* does not exist.
|
|
*/
|
|
if (bcmp((u_int8_t*)ainfop->filehdr + ainfop->finderinfo->offset, emptyfinfo, sizeof(emptyfinfo)) == 0) {
|
|
ainfop->emptyfinderinfo = 1;
|
|
}
|
|
} else {
|
|
error = ENOATTR;
|
|
goto bail;
|
|
}
|
|
}
|
|
if (filehdr->entries[i].type == AD_RESOURCE) {
|
|
/*
|
|
* Ignore zero-length resource forks when getting. If setting,
|
|
* we need to remember the resource fork entry so it can be
|
|
* updated once the new content has been written.
|
|
*/
|
|
if (filehdr->entries[i].length == 0 && !setting) {
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Check to see if any "empty" resource fork is ours (i.e. is ignorable).
|
|
*
|
|
* The "empty" resource headers we created have a system data tag of:
|
|
* "This resource fork intentionally left blank "
|
|
*/
|
|
if (filehdr->entries[i].length == sizeof(rsrcfork_header_t) && !setting) {
|
|
uio_t rf_uio;
|
|
u_int8_t systemData[64];
|
|
int rf_err;
|
|
|
|
|
|
/* Read the system data which starts at byte 16 */
|
|
rf_uio = uio_create(1, 0, UIO_SYSSPACE, UIO_READ);
|
|
uio_addiov(rf_uio, (uintptr_t)systemData, sizeof(systemData));
|
|
uio_setoffset(rf_uio, filehdr->entries[i].offset + 16);
|
|
rf_err = VNOP_READ(xvp, rf_uio, 0, context);
|
|
uio_free(rf_uio);
|
|
|
|
if (rf_err != 0 ||
|
|
bcmp(systemData, RF_EMPTY_TAG, sizeof(RF_EMPTY_TAG)) == 0) {
|
|
continue; /* skip this resource fork */
|
|
}
|
|
}
|
|
ainfop->rsrcfork = &filehdr->entries[i];
|
|
if (i != (filehdr->numEntries - 1)) {
|
|
printf("get_xattrinfo: resource fork not last entry\n");
|
|
ainfop->readonly = 1;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* See if this file looks like it is laid out correctly to contain
|
|
* extended attributes. If so, then do the following:
|
|
*
|
|
* - If we're going to be writing, try to make sure the Finder Info
|
|
* entry has room to store the extended attribute header, plus some
|
|
* space for extended attributes.
|
|
*
|
|
* - Swap and sanity check the extended attribute header and entries
|
|
* (if any).
|
|
*/
|
|
if (filehdr->numEntries == 2 &&
|
|
ainfop->finderinfo == &filehdr->entries[0] &&
|
|
ainfop->rsrcfork == &filehdr->entries[1] &&
|
|
ainfop->finderinfo->offset == offsetof(apple_double_header_t, finfo)) {
|
|
attr_header_t *attrhdr;
|
|
attrhdr = (attr_header_t *)filehdr;
|
|
/*
|
|
* If we're going to be writing, try to make sure the Finder
|
|
* Info entry has room to store the extended attribute header,
|
|
* plus some space for extended attributes.
|
|
*/
|
|
if (setting && ainfop->finderinfo->length == FINDERINFOSIZE) {
|
|
size_t delta;
|
|
size_t writesize;
|
|
|
|
delta = ATTR_BUF_SIZE - (filehdr->entries[0].offset + FINDERINFOSIZE);
|
|
if (ainfop->rsrcfork && filehdr->entries[1].length) {
|
|
/* Make some room before existing resource fork. */
|
|
shift_data_down(xvp,
|
|
filehdr->entries[1].offset,
|
|
filehdr->entries[1].length,
|
|
delta, context);
|
|
writesize = sizeof(attr_header_t);
|
|
} else {
|
|
/* Create a new, empty resource fork. */
|
|
rsrcfork_header_t *rsrcforkhdr;
|
|
|
|
vnode_setsize(xvp, filehdr->entries[1].offset + delta, 0, context);
|
|
|
|
/* Steal some space for an empty RF header. */
|
|
delta -= sizeof(rsrcfork_header_t);
|
|
|
|
bzero(&attrhdr->appledouble.pad[0], delta);
|
|
rsrcforkhdr = (rsrcfork_header_t *)((char *)filehdr + filehdr->entries[1].offset + delta);
|
|
|
|
/* Fill in Empty Resource Fork Header. */
|
|
init_empty_resource_fork(rsrcforkhdr);
|
|
|
|
filehdr->entries[1].length = sizeof(rsrcfork_header_t);
|
|
writesize = ATTR_BUF_SIZE;
|
|
}
|
|
filehdr->entries[0].length += delta;
|
|
filehdr->entries[1].offset += delta;
|
|
|
|
/* Fill in Attribute Header. */
|
|
attrhdr->magic = ATTR_HDR_MAGIC;
|
|
attrhdr->debug_tag = (u_int32_t)va.va_fileid;
|
|
attrhdr->total_size = filehdr->entries[1].offset;
|
|
attrhdr->data_start = sizeof(attr_header_t);
|
|
attrhdr->data_length = 0;
|
|
attrhdr->reserved[0] = 0;
|
|
attrhdr->reserved[1] = 0;
|
|
attrhdr->reserved[2] = 0;
|
|
attrhdr->flags = 0;
|
|
attrhdr->num_attrs = 0;
|
|
|
|
/* Push out new header */
|
|
uio_reset(auio, 0, UIO_SYSSPACE, UIO_WRITE);
|
|
uio_addiov(auio, (uintptr_t)filehdr, writesize);
|
|
|
|
swap_adhdr(filehdr); /* to big endian */
|
|
swap_attrhdr(attrhdr, ainfop); /* to big endian */
|
|
error = VNOP_WRITE(xvp, auio, 0, context);
|
|
swap_adhdr(filehdr); /* back to native */
|
|
/* The attribute header gets swapped below. */
|
|
}
|
|
}
|
|
/*
|
|
* Swap and sanity check the extended attribute header and
|
|
* entries (if any). The Finder Info content must be big enough
|
|
* to include the extended attribute header; if not, we just
|
|
* ignore it.
|
|
*
|
|
* Note that we're passing the offset + length (i.e. the end)
|
|
* of the Finder Info instead of rawsize to validate_attrhdr.
|
|
* This ensures that all extended attributes lie within the
|
|
* Finder Info content according to the AppleDouble entry.
|
|
*
|
|
* Sets ainfop->attrhdr and ainfop->attr_entry if a valid
|
|
* header was found.
|
|
*/
|
|
if (ainfop->finderinfo &&
|
|
ainfop->finderinfo == &filehdr->entries[0] &&
|
|
ainfop->finderinfo->length >= (sizeof(attr_header_t) - sizeof(apple_double_header_t))) {
|
|
attr_header_t *attrhdr = (attr_header_t*)filehdr;
|
|
|
|
if (ainfop->finderinfo->offset != offsetof(apple_double_header_t, finfo)) {
|
|
error = ENOATTR;
|
|
goto bail;
|
|
}
|
|
|
|
if ((error = check_and_swap_attrhdr(attrhdr, ainfop)) == 0) {
|
|
ainfop->attrhdr = attrhdr; /* valid attribute header */
|
|
/* First attr_entry starts immediately following attribute header */
|
|
ainfop->attr_entry = (attr_entry_t *)&attrhdr[1];
|
|
}
|
|
}
|
|
|
|
error = 0;
|
|
bail:
|
|
if (auio != NULL) {
|
|
uio_free(auio);
|
|
}
|
|
kheap_free(KHEAP_DATA_BUFFERS, buffer, iosize);
|
|
return error;
|
|
}
|
|
|
|
|
|
static int
|
|
create_xattrfile(vnode_t xvp, u_int32_t fileid, vfs_context_t context)
|
|
{
|
|
attr_header_t *xah;
|
|
rsrcfork_header_t *rsrcforkhdr;
|
|
void * buffer;
|
|
uio_t auio;
|
|
int rsrcforksize;
|
|
int error;
|
|
|
|
buffer = kheap_alloc(KHEAP_TEMP, ATTR_BUF_SIZE, Z_WAITOK);
|
|
bzero(buffer, ATTR_BUF_SIZE);
|
|
|
|
xah = (attr_header_t *)buffer;
|
|
auio = uio_create(1, 0, UIO_SYSSPACE, UIO_WRITE);
|
|
uio_addiov(auio, (uintptr_t)buffer, ATTR_BUF_SIZE);
|
|
rsrcforksize = sizeof(rsrcfork_header_t);
|
|
rsrcforkhdr = (rsrcfork_header_t *) ((char *)buffer + ATTR_BUF_SIZE - rsrcforksize);
|
|
|
|
/* Fill in Apple Double Header. */
|
|
xah->appledouble.magic = SWAP32(ADH_MAGIC);
|
|
xah->appledouble.version = SWAP32(ADH_VERSION);
|
|
xah->appledouble.numEntries = SWAP16(2);
|
|
xah->appledouble.entries[0].type = SWAP32(AD_FINDERINFO);
|
|
xah->appledouble.entries[0].offset = SWAP32(offsetof(apple_double_header_t, finfo));
|
|
xah->appledouble.entries[0].length = SWAP32(ATTR_BUF_SIZE - offsetof(apple_double_header_t, finfo) - rsrcforksize);
|
|
xah->appledouble.entries[1].type = SWAP32(AD_RESOURCE);
|
|
xah->appledouble.entries[1].offset = SWAP32(ATTR_BUF_SIZE - rsrcforksize);
|
|
xah->appledouble.entries[1].length = SWAP32(rsrcforksize);
|
|
bcopy(ADH_MACOSX, xah->appledouble.filler, sizeof(xah->appledouble.filler));
|
|
|
|
/* Fill in Attribute Header. */
|
|
xah->magic = SWAP32(ATTR_HDR_MAGIC);
|
|
xah->debug_tag = SWAP32(fileid);
|
|
xah->total_size = SWAP32(ATTR_BUF_SIZE - rsrcforksize);
|
|
xah->data_start = SWAP32(sizeof(attr_header_t));
|
|
|
|
/* Fill in Empty Resource Fork Header. */
|
|
init_empty_resource_fork(rsrcforkhdr);
|
|
|
|
/* Push it out. */
|
|
error = VNOP_WRITE(xvp, auio, IO_UNIT, context);
|
|
|
|
/* Did we write out the full uio? */
|
|
if (uio_resid(auio) > 0) {
|
|
error = ENOSPC;
|
|
}
|
|
|
|
uio_free(auio);
|
|
kheap_free(KHEAP_TEMP, buffer, ATTR_BUF_SIZE);
|
|
|
|
return error;
|
|
}
|
|
|
|
static void
|
|
init_empty_resource_fork(rsrcfork_header_t * rsrcforkhdr)
|
|
{
|
|
bzero(rsrcforkhdr, sizeof(rsrcfork_header_t));
|
|
rsrcforkhdr->fh_DataOffset = SWAP32(RF_FIRST_RESOURCE);
|
|
rsrcforkhdr->fh_MapOffset = SWAP32(RF_FIRST_RESOURCE);
|
|
rsrcforkhdr->fh_MapLength = SWAP32(RF_NULL_MAP_LENGTH);
|
|
rsrcforkhdr->mh_DataOffset = SWAP32(RF_FIRST_RESOURCE);
|
|
rsrcforkhdr->mh_MapOffset = SWAP32(RF_FIRST_RESOURCE);
|
|
rsrcforkhdr->mh_MapLength = SWAP32(RF_NULL_MAP_LENGTH);
|
|
rsrcforkhdr->mh_Types = SWAP16(RF_NULL_MAP_LENGTH - 2 );
|
|
rsrcforkhdr->mh_Names = SWAP16(RF_NULL_MAP_LENGTH);
|
|
rsrcforkhdr->typeCount = SWAP16(-1);
|
|
bcopy(RF_EMPTY_TAG, rsrcforkhdr->systemData, sizeof(RF_EMPTY_TAG));
|
|
}
|
|
|
|
static void
|
|
rel_xattrinfo(attr_info_t *ainfop)
|
|
{
|
|
kheap_free_addr(KHEAP_DATA_BUFFERS, ainfop->filehdr);
|
|
bzero(ainfop, sizeof(attr_info_t));
|
|
}
|
|
|
|
static int
|
|
write_xattrinfo(attr_info_t *ainfop)
|
|
{
|
|
uio_t auio;
|
|
int error;
|
|
|
|
auio = uio_create(1, 0, UIO_SYSSPACE, UIO_WRITE);
|
|
uio_addiov(auio, (uintptr_t)ainfop->filehdr, ainfop->iosize);
|
|
|
|
swap_adhdr(ainfop->filehdr);
|
|
if (ainfop->attrhdr != NULL) {
|
|
swap_attrhdr(ainfop->attrhdr, ainfop);
|
|
}
|
|
|
|
error = VNOP_WRITE(ainfop->filevp, auio, 0, ainfop->context);
|
|
|
|
swap_adhdr(ainfop->filehdr);
|
|
if (ainfop->attrhdr != NULL) {
|
|
swap_attrhdr(ainfop->attrhdr, ainfop);
|
|
}
|
|
uio_free(auio);
|
|
|
|
return error;
|
|
}
|
|
|
|
#if BYTE_ORDER == LITTLE_ENDIAN
|
|
/*
|
|
* Endian swap apple double header
|
|
*/
|
|
static void
|
|
swap_adhdr(apple_double_header_t *adh)
|
|
{
|
|
int count;
|
|
int i;
|
|
|
|
count = (adh->magic == ADH_MAGIC) ? adh->numEntries : SWAP16(adh->numEntries);
|
|
|
|
adh->magic = SWAP32(adh->magic);
|
|
adh->version = SWAP32(adh->version);
|
|
adh->numEntries = SWAP16(adh->numEntries);
|
|
|
|
for (i = 0; i < count; i++) {
|
|
adh->entries[i].type = SWAP32(adh->entries[i].type);
|
|
adh->entries[i].offset = SWAP32(adh->entries[i].offset);
|
|
adh->entries[i].length = SWAP32(adh->entries[i].length);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Endian swap extended attributes header
|
|
*/
|
|
static void
|
|
swap_attrhdr(attr_header_t *ah, attr_info_t* info)
|
|
{
|
|
attr_entry_t *ae;
|
|
int count;
|
|
int i;
|
|
|
|
count = (ah->magic == ATTR_HDR_MAGIC) ? ah->num_attrs : SWAP16(ah->num_attrs);
|
|
|
|
ah->magic = SWAP32(ah->magic);
|
|
ah->debug_tag = SWAP32(ah->debug_tag);
|
|
ah->total_size = SWAP32(ah->total_size);
|
|
ah->data_start = SWAP32(ah->data_start);
|
|
ah->data_length = SWAP32(ah->data_length);
|
|
ah->flags = SWAP16(ah->flags);
|
|
ah->num_attrs = SWAP16(ah->num_attrs);
|
|
|
|
ae = (attr_entry_t *)(&ah[1]);
|
|
for (i = 0; i < count && ATTR_VALID(ae, *info); i++, ae = ATTR_NEXT(ae)) {
|
|
ae->offset = SWAP32(ae->offset);
|
|
ae->length = SWAP32(ae->length);
|
|
ae->flags = SWAP16(ae->flags);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Validate and swap the attributes header contents, and each attribute's
|
|
* attr_entry_t.
|
|
*
|
|
* Note: Assumes the caller has verified that the Finder Info content is large
|
|
* enough to contain the attr_header structure itself. Therefore, we can
|
|
* swap the header fields before sanity checking them.
|
|
*/
|
|
static int
|
|
check_and_swap_attrhdr(attr_header_t *ah, attr_info_t *ainfop)
|
|
{
|
|
attr_entry_t *ae;
|
|
u_int8_t *buf_end;
|
|
u_int32_t end;
|
|
int count;
|
|
int i;
|
|
uint32_t total_header_size;
|
|
|
|
if (ah == NULL) {
|
|
return EINVAL;
|
|
}
|
|
|
|
if (SWAP32(ah->magic) != ATTR_HDR_MAGIC) {
|
|
return EINVAL;
|
|
}
|
|
|
|
/* Swap the basic header fields */
|
|
ah->magic = SWAP32(ah->magic);
|
|
ah->debug_tag = SWAP32(ah->debug_tag);
|
|
ah->total_size = SWAP32(ah->total_size);
|
|
ah->data_start = SWAP32(ah->data_start);
|
|
ah->data_length = SWAP32(ah->data_length);
|
|
ah->flags = SWAP16(ah->flags);
|
|
ah->num_attrs = SWAP16(ah->num_attrs);
|
|
|
|
/*
|
|
* Make sure the total_size fits within the Finder Info area, and the
|
|
* extended attribute data area fits within total_size.
|
|
*/
|
|
end = ah->data_start + ah->data_length;
|
|
if (ah->total_size > ainfop->finderinfo->offset + ainfop->finderinfo->length ||
|
|
ah->data_start < sizeof(attr_header_t) ||
|
|
end < ah->data_start ||
|
|
end > ah->total_size) {
|
|
return EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Make sure each of the attr_entry_t's fits within total_size.
|
|
*/
|
|
buf_end = ainfop->rawdata + ah->data_start;
|
|
if (buf_end > ainfop->rawdata + ainfop->rawsize) {
|
|
return EINVAL;
|
|
}
|
|
count = ah->num_attrs;
|
|
if (count > 256) {
|
|
return EINVAL;
|
|
}
|
|
ae = (attr_entry_t *)(&ah[1]);
|
|
|
|
total_header_size = sizeof(attr_header_t);
|
|
for (i = 0; i < count; i++) {
|
|
/* Make sure the fixed-size part of this attr_entry_t fits. */
|
|
if ((u_int8_t *) &ae[1] > buf_end) {
|
|
return EINVAL;
|
|
}
|
|
|
|
/* Make sure the variable-length name fits (+1 is for NUL terminator) */
|
|
if (&ae->name[ae->namelen + 1] > buf_end) {
|
|
return EINVAL;
|
|
}
|
|
|
|
/* Make sure that namelen is matching name's real length, namelen included NUL */
|
|
if (strnlen((const char *)ae->name, ae->namelen) != ae->namelen - 1) {
|
|
return EINVAL;
|
|
}
|
|
|
|
/* Swap the attribute entry fields */
|
|
ae->offset = SWAP32(ae->offset);
|
|
ae->length = SWAP32(ae->length);
|
|
ae->flags = SWAP16(ae->flags);
|
|
|
|
/* Make sure the attribute content fits and points to the data part */
|
|
end = ae->offset + ae->length;
|
|
if (end < ae->offset || end > ah->total_size) {
|
|
return EINVAL;
|
|
}
|
|
|
|
/* Make sure entry points to data section and not header */
|
|
if (ae->offset < ah->data_start) {
|
|
return EINVAL;
|
|
}
|
|
|
|
/* We verified namelen is ok above, so add this entry's size to a total */
|
|
total_header_size += ATTR_ENTRY_LENGTH(ae->namelen);
|
|
|
|
ae = ATTR_NEXT(ae);
|
|
}
|
|
|
|
|
|
/* make sure data_start is actually after all the xattr key entries */
|
|
if (ah->data_start < total_header_size) {
|
|
return EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// "start" & "end" are byte offsets in the file.
|
|
// "to" is the byte offset we want to move the
|
|
// data to. "to" should be > "start".
|
|
//
|
|
// we do the copy backwards to avoid problems if
|
|
// there's an overlap.
|
|
//
|
|
static int
|
|
shift_data_down(vnode_t xvp, off_t start, size_t len, off_t delta, vfs_context_t context)
|
|
{
|
|
int ret, iolen;
|
|
size_t chunk, orig_chunk;
|
|
char *buff;
|
|
off_t pos;
|
|
kauth_cred_t ucred = vfs_context_ucred(context);
|
|
proc_t p = vfs_context_proc(context);
|
|
|
|
if (delta == 0 || len == 0) {
|
|
return 0;
|
|
}
|
|
|
|
chunk = 4096;
|
|
if (len < chunk) {
|
|
chunk = len;
|
|
}
|
|
orig_chunk = chunk;
|
|
|
|
if (kmem_alloc(kernel_map, (vm_offset_t *)&buff, chunk, VM_KERN_MEMORY_FILE)) {
|
|
return ENOMEM;
|
|
}
|
|
|
|
for (pos = start + len - chunk; pos >= start; pos -= chunk) {
|
|
ret = vn_rdwr(UIO_READ, xvp, buff, (int)chunk, pos, UIO_SYSSPACE, IO_NODELOCKED | IO_NOAUTH, ucred, &iolen, p);
|
|
if (iolen != 0) {
|
|
printf("xattr:shift_data: error reading data @ %lld (read %d of %lu) (%d)\n",
|
|
pos, ret, chunk, ret);
|
|
break;
|
|
}
|
|
|
|
ret = vn_rdwr(UIO_WRITE, xvp, buff, (int)chunk, pos + delta, UIO_SYSSPACE, IO_NODELOCKED | IO_NOAUTH, ucred, &iolen, p);
|
|
if (iolen != 0) {
|
|
printf("xattr:shift_data: error writing data @ %lld (wrote %d of %lu) (%d)\n",
|
|
pos + delta, ret, chunk, ret);
|
|
break;
|
|
}
|
|
|
|
if ((pos - (off_t)chunk) < start) {
|
|
chunk = pos - start;
|
|
|
|
if (chunk == 0) { // we're all done
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
kmem_free(kernel_map, (vm_offset_t)buff, orig_chunk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
shift_data_up(vnode_t xvp, off_t start, size_t len, off_t delta, vfs_context_t context)
|
|
{
|
|
int ret, iolen;
|
|
size_t chunk, orig_chunk;
|
|
char *buff;
|
|
off_t pos;
|
|
off_t end;
|
|
kauth_cred_t ucred = vfs_context_ucred(context);
|
|
proc_t p = vfs_context_proc(context);
|
|
|
|
if (delta == 0 || len == 0) {
|
|
return 0;
|
|
}
|
|
|
|
chunk = 4096;
|
|
if (len < chunk) {
|
|
chunk = len;
|
|
}
|
|
orig_chunk = chunk;
|
|
end = start + len;
|
|
|
|
if (kmem_alloc(kernel_map, (vm_offset_t *)&buff, chunk, VM_KERN_MEMORY_FILE)) {
|
|
return ENOMEM;
|
|
}
|
|
|
|
for (pos = start; pos < end; pos += chunk) {
|
|
ret = vn_rdwr(UIO_READ, xvp, buff, (int)chunk, pos, UIO_SYSSPACE, IO_NODELOCKED | IO_NOAUTH, ucred, &iolen, p);
|
|
if (iolen != 0) {
|
|
printf("xattr:shift_data: error reading data @ %lld (read %d of %lu) (%d)\n",
|
|
pos, ret, chunk, ret);
|
|
break;
|
|
}
|
|
|
|
ret = vn_rdwr(UIO_WRITE, xvp, buff, (int)chunk, pos - delta, UIO_SYSSPACE, IO_NODELOCKED | IO_NOAUTH, ucred, &iolen, p);
|
|
if (iolen != 0) {
|
|
printf("xattr:shift_data: error writing data @ %lld (wrote %d of %lu) (%d)\n",
|
|
pos + delta, ret, chunk, ret);
|
|
break;
|
|
}
|
|
|
|
if ((pos + (off_t)chunk) > end) {
|
|
chunk = end - pos;
|
|
|
|
if (chunk == 0) { // we're all done
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
kmem_free(kernel_map, (vm_offset_t)buff, orig_chunk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
lock_xattrfile(vnode_t xvp, short locktype, vfs_context_t context)
|
|
{
|
|
struct flock lf;
|
|
int error;
|
|
|
|
lf.l_whence = SEEK_SET;
|
|
lf.l_start = 0;
|
|
lf.l_len = 0;
|
|
lf.l_type = locktype; /* F_WRLCK or F_RDLCK */
|
|
/* Note: id is just a kernel address that's not a proc */
|
|
error = VNOP_ADVLOCK(xvp, (caddr_t)xvp, F_SETLK, &lf, F_FLOCK | F_WAIT, context, NULL);
|
|
return error == ENOTSUP ? 0 : error;
|
|
}
|
|
|
|
int
|
|
unlock_xattrfile(vnode_t xvp, vfs_context_t context)
|
|
{
|
|
struct flock lf;
|
|
int error;
|
|
|
|
lf.l_whence = SEEK_SET;
|
|
lf.l_start = 0;
|
|
lf.l_len = 0;
|
|
lf.l_type = F_UNLCK;
|
|
/* Note: id is just a kernel address that's not a proc */
|
|
error = VNOP_ADVLOCK(xvp, (caddr_t)xvp, F_UNLCK, &lf, F_FLOCK, context, NULL);
|
|
return error == ENOTSUP ? 0 : error;
|
|
}
|
|
|
|
#else /* CONFIG_APPLEDOUBLE */
|
|
|
|
|
|
static int
|
|
default_getxattr(__unused vnode_t vp, __unused const char *name,
|
|
__unused uio_t uio, __unused size_t *size, __unused int options,
|
|
__unused vfs_context_t context)
|
|
{
|
|
return ENOTSUP;
|
|
}
|
|
|
|
static int
|
|
default_setxattr(__unused vnode_t vp, __unused const char *name,
|
|
__unused uio_t uio, __unused int options, __unused vfs_context_t context)
|
|
{
|
|
return ENOTSUP;
|
|
}
|
|
|
|
static int
|
|
default_listxattr(__unused vnode_t vp,
|
|
__unused uio_t uio, __unused size_t *size, __unused int options,
|
|
__unused vfs_context_t context)
|
|
{
|
|
return ENOTSUP;
|
|
}
|
|
|
|
static int
|
|
default_removexattr(__unused vnode_t vp, __unused const char *name,
|
|
__unused int options, __unused vfs_context_t context)
|
|
{
|
|
return ENOTSUP;
|
|
}
|
|
|
|
#endif /* CONFIG_APPLEDOUBLE */
|