/*      $NetBSD: hfs_vnops.c,v 1.40 2022/08/06 18:26:42 andvar Exp $    */

/*-
* Copyright (c) 2005, 2007 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Yevgeny Binder and Dieter Baron.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/

/*
* Copyright (c) 1992, 1993
*      The Regents of the University of California.  All rights reserved.
*
* This code is derived from software donated to Berkeley by
* Jan-Simon Pendry.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
*    may be used to endorse or promote products derived from this software
*    without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/

/*
* Copyright (c) 1982, 1986, 1989, 1993, 1995
*      The Regents of the University of California.  All rights reserved.
* (c) UNIX System Laboratories, Inc.
* All or some portions of this file are derived from material licensed
* to the University of California by American Telephone and Telegraph
* Co. or Unix System Laboratories, Inc. and are reproduced herein with
* the permission of UNIX System Laboratories, Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
*    may be used to endorse or promote products derived from this software
*    without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/


/*
* Apple HFS+ filesystem
*/

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: hfs_vnops.c,v 1.40 2022/08/06 18:26:42 andvar Exp $");

#ifdef _KERNEL_OPT
#include "opt_ipsec.h"
#endif

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/vmmeter.h>
#include <sys/time.h>
#include <sys/proc.h>
#include <sys/vnode.h>
#include <sys/malloc.h>
#include <sys/pool.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <sys/namei.h>
#include <sys/buf.h>
#include <sys/dirent.h>
#include <sys/msgbuf.h>

#include <miscfs/fifofs/fifo.h>
#include <miscfs/specfs/specdev.h>

#include <fs/hfs/hfs.h>
#include <fs/hfs/unicode.h>

#include <miscfs/genfs/genfs.h>

int     hfs_vop_parsepath(void *);
int     hfs_vop_lookup(void *);
int     hfs_vop_open(void *);
int     hfs_vop_close(void *);
int     hfs_vop_access(void *);
int     hfs_vop_getattr(void *);
int     hfs_vop_setattr(void *);
int     hfs_vop_bmap(void *);
int     hfs_vop_read(void *);
int     hfs_vop_readdir(void *);
int     hfs_vop_readlink(void *);
int     hfs_vop_reclaim(void *);
int     hfs_vop_print(void *);

#ifdef HFS_DEBUG
#define DPRINTF(a) printf a
#else
#define DPRINTF(a)
#endif


int (**hfs_vnodeop_p) (void *);
const struct vnodeopv_entry_desc hfs_vnodeop_entries[] = {
       { &vop_default_desc, vn_default_error },
       { &vop_parsepath_desc, genfs_parsepath },       /* parsepath */
       { &vop_lookup_desc, hfs_vop_lookup },           /* lookup */
       { &vop_create_desc, genfs_eopnotsupp },         /* create */
       { &vop_whiteout_desc, genfs_eopnotsupp },       /* whiteout */
       { &vop_mknod_desc, genfs_eopnotsupp },          /* mknod */
       { &vop_open_desc, hfs_vop_open },               /* open */
       { &vop_close_desc, hfs_vop_close },             /* close */
       { &vop_access_desc, hfs_vop_access },           /* access */
       { &vop_accessx_desc, genfs_accessx },           /* accessx */
       { &vop_getattr_desc, hfs_vop_getattr },         /* getattr */
       { &vop_setattr_desc, hfs_vop_setattr },         /* setattr */
       { &vop_read_desc, hfs_vop_read },               /* read */
       { &vop_write_desc, genfs_eopnotsupp },          /* write */
       { &vop_fallocate_desc, genfs_eopnotsupp },      /* fallocate */
       { &vop_fdiscard_desc, genfs_eopnotsupp },       /* fdiscard */
       { &vop_ioctl_desc, genfs_eopnotsupp },          /* ioctl */
       { &vop_fcntl_desc, genfs_fcntl },               /* fcntl */
       { &vop_poll_desc, genfs_eopnotsupp },           /* poll */
       { &vop_kqfilter_desc, genfs_kqfilter },         /* kqfilter */
       { &vop_revoke_desc, genfs_eopnotsupp },         /* revoke */
       { &vop_mmap_desc, genfs_mmap },                 /* mmap */
       { &vop_fsync_desc, genfs_nullop },              /* fsync */
       { &vop_seek_desc, genfs_seek },                 /* seek */
       { &vop_remove_desc, genfs_eopnotsupp },         /* remove */
       { &vop_link_desc, genfs_eopnotsupp },           /* link */
       { &vop_rename_desc, genfs_eopnotsupp },         /* rename */
       { &vop_mkdir_desc, genfs_eopnotsupp },          /* mkdir */
       { &vop_rmdir_desc, genfs_eopnotsupp },          /* rmdir */
       { &vop_symlink_desc, genfs_eopnotsupp },        /* symlink */
       { &vop_readdir_desc, hfs_vop_readdir },         /* readdir */
       { &vop_readlink_desc, hfs_vop_readlink },       /* readlink */
       { &vop_abortop_desc, genfs_abortop },           /* abortop */
       { &vop_inactive_desc, genfs_eopnotsupp },       /* inactive */
       { &vop_reclaim_desc, hfs_vop_reclaim },         /* reclaim */
       { &vop_lock_desc, genfs_lock },                 /* lock */
       { &vop_unlock_desc, genfs_unlock },             /* unlock */
       { &vop_bmap_desc, hfs_vop_bmap },               /* bmap */
       { &vop_strategy_desc, genfs_eopnotsupp },       /* strategy */
       { &vop_print_desc, hfs_vop_print },             /* print */
       { &vop_islocked_desc, genfs_islocked },         /* islocked */
       { &vop_pathconf_desc, genfs_eopnotsupp },       /* pathconf */
       { &vop_advlock_desc, genfs_eopnotsupp },        /* advlock */
       { &vop_bwrite_desc, genfs_eopnotsupp },         /* bwrite */
       { &vop_getpages_desc, genfs_getpages },         /* getpages */
       { &vop_putpages_desc, genfs_putpages },         /* putpages */
       { &vop_openextattr_desc, genfs_eopnotsupp },    /* openextattr */
       { &vop_closeextattr_desc, genfs_eopnotsupp },   /* closeextattr */
       { &vop_getextattr_desc, genfs_eopnotsupp },     /* getextattr */
       { &vop_setextattr_desc, genfs_eopnotsupp },     /* setextattr */
       { &vop_listextattr_desc, genfs_eopnotsupp },    /* listextattr */
       { &vop_deleteextattr_desc, genfs_eopnotsupp },  /* deleteextattr */
       { NULL, NULL }
};
const struct vnodeopv_desc hfs_vnodeop_opv_desc =
       { &hfs_vnodeop_p, hfs_vnodeop_entries };

int (**hfs_specop_p) (void *);
const struct vnodeopv_entry_desc hfs_specop_entries[] = {
       { &vop_default_desc, vn_default_error },
       GENFS_SPECOP_ENTRIES,
       { &vop_close_desc, spec_close },                /* close */
       { &vop_access_desc, hfs_vop_access },           /* access */
       { &vop_accessx_desc, genfs_accessx },           /* accessx */
       { &vop_getattr_desc, hfs_vop_getattr },         /* getattr */
       { &vop_setattr_desc, hfs_vop_setattr },         /* setattr */
       { &vop_read_desc, spec_read },                  /* read */
       { &vop_write_desc, spec_write },                /* write */
       { &vop_fcntl_desc, genfs_fcntl },               /* fcntl */
       { &vop_fsync_desc, spec_fsync },                /* fsync */
       { &vop_inactive_desc, genfs_eopnotsupp },       /* inactive */
       { &vop_reclaim_desc, hfs_vop_reclaim },         /* reclaim */
       { &vop_lock_desc, genfs_lock },                 /* lock */
       { &vop_unlock_desc, genfs_unlock },             /* unlock */
       { &vop_print_desc, hfs_vop_print },             /* print */
       { &vop_islocked_desc, genfs_islocked },         /* islocked */
       { &vop_bwrite_desc, vn_bwrite },                /* bwrite */
#if 0
       { &vop_openextattr_desc, hfs_openextattr },     /* openextattr */
       { &vop_closeextattr_desc, hfs_closeextattr },   /* closeextattr */
       { &vop_getextattr_desc, hfs_getextattr },       /* getextattr */
       { &vop_setextattr_desc, hfs_setextattr },       /* setextattr */
       { &vop_listextattr_desc, hfs_listextattr },     /* listextattr */
       { &vop_deleteextattr_desc, hfs_deleteextattr }, /* deleteextattr */
#endif
       { NULL, NULL }
};
const struct vnodeopv_desc hfs_specop_opv_desc =
       { &hfs_specop_p, hfs_specop_entries };

int (**hfs_fifoop_p) (void *);
const struct vnodeopv_entry_desc hfs_fifoop_entries[] = {
       { &vop_default_desc, vn_default_error },
       GENFS_FIFOOP_ENTRIES,
       { &vop_close_desc, vn_fifo_bypass },            /* close */
       { &vop_access_desc, hfs_vop_access },           /* access */
       { &vop_accessx_desc, genfs_accessx },           /* accessx */
       { &vop_getattr_desc, hfs_vop_getattr },         /* getattr */
       { &vop_setattr_desc, hfs_vop_setattr },         /* setattr */
       { &vop_read_desc, vn_fifo_bypass },             /* read */
       { &vop_write_desc, vn_fifo_bypass },            /* write */
       { &vop_fcntl_desc, genfs_fcntl },               /* fcntl */
       { &vop_fsync_desc, vn_fifo_bypass },            /* fsync */
       { &vop_inactive_desc, genfs_eopnotsupp },       /* inactive */
       { &vop_reclaim_desc, hfs_vop_reclaim },         /* reclaim */
       { &vop_lock_desc, genfs_lock },                 /* lock */
       { &vop_unlock_desc, genfs_unlock },             /* unlock */
       { &vop_strategy_desc, vn_fifo_bypass },         /* strategy */
       { &vop_print_desc, hfs_vop_print },             /* print */
       { &vop_islocked_desc, genfs_islocked },         /* islocked */
       { &vop_bwrite_desc, vn_bwrite },                /* bwrite */
#if 0
       { &vop_openextattr_desc, hfs_openextattr },     /* openextattr */
       { &vop_closeextattr_desc, hfs_closeextattr },   /* closeextattr */
       { &vop_getextattr_desc, hfs_getextattr },       /* getextattr */
       { &vop_setextattr_desc, hfs_setextattr },       /* setextattr */
       { &vop_listextattr_desc, hfs_listextattr },     /* listextattr */
       { &vop_deleteextattr_desc, hfs_deleteextattr }, /* deleteextattr */
#endif
       { NULL, NULL }
};
const struct vnodeopv_desc hfs_fifoop_opv_desc =
       { &hfs_fifoop_p, hfs_fifoop_entries };

int
hfs_vop_parsepath(void *v)
{
       struct vop_parsepath_args /* {
               struct vnode *a_dvp;
               const char *a_name;
               size_t *a_retval;
       } */ *ap = v;
       size_t len;
       int error;

       error = genfs_parsepath(v);
       if (error) {
               return error;
       }

       len = *ap->a_retval;
       if (!strcmp(ap->a_name + len, "/rsrc")) {
               *ap->a_retval += 5;
       }
       return 0;
}

int
hfs_vop_lookup(void *v)
{
       struct vop_lookup_v2_args /* {
               struct vnode * a_dvp;
               struct vnode ** a_vpp;
               struct componentname * a_cnp;
       } */ *ap = v;
       struct componentname *cnp;
       struct hfsnode *dp;     /* hfsnode for directory being searched */
       kauth_cred_t cred;
       struct vnode **vpp;             /* resultant vnode */
       struct vnode *tdp;              /* returned by VFS_VGET */
       struct vnode *vdp;              /* vnode for directory being searched */
       hfs_catalog_key_t key;  /* hfs+ catalog search key for requested child */
       hfs_catalog_keyed_record_t rec; /* catalog record of requested child */
       size_t namelen;
       int use_resource_fork = 0;
       unichar_t* unicn;               /* name of component, in Unicode */
       const char *pname;
       int error;
       int flags;
       int result;                     /* result of libhfs operations */

       DPRINTF(("VOP = hfs_vop_lookup()\n"));

       cnp = ap->a_cnp;
       cred = cnp->cn_cred;
       vdp = ap->a_dvp;
       dp = VTOH(vdp);
       error = 0;
       pname = cnp->cn_nameptr;
       result = 0;
       unicn = NULL;
       vpp = ap->a_vpp;
       *vpp = NULL;

       flags = cnp->cn_flags;


       /*
        * Check accessibility of directory.
        */
       if ((error = VOP_ACCESS(vdp, VEXEC, cred)) != 0)
               return error;

       if ((flags & ISLASTCN) && (vdp->v_mount->mnt_flag & MNT_RDONLY) &&
           (cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME))
               return EROFS;

       /*
        * We now have a segment name to search for, and a directory to search.
        *
        * Before tediously performing a linear scan of the directory,
        * check the name cache to see if the directory/name pair
        * we are looking for is known already.
        */
/* XXX Cache disabled until we can make sure it works. */
#if 0
       if ((error = cache_lookup(vdp, vpp, cnp)) >= 0)
               return error;
#endif


#if 0
       if (cnp->cn_namelen == 1 && *pname == '.') {
               *vpp = vdp;
               vref(vdp);
               return 0;
       }
#endif

       if (flags & ISDOTDOT) {
               DPRINTF(("DOTDOT "));
               error = hfs_vget_internal(vdp->v_mount, dp->h_parent,
                   HFS_RSRCFORK, &tdp);
               if (error != 0)
                       goto error;
               *vpp = tdp;
#if 0
       } else if (dp->h_rec.u.cnid == rec.file.u.cnid) {
#endif
       } else if (cnp->cn_namelen == 1 && pname[0] == '.') {
               DPRINTF(("DOT "));
               vref(vdp);      /* we want ourself, ie "." */
               *vpp = vdp;
       } else {
               hfs_callback_args cbargs;
               uint8_t len, ni;

               hfslib_init_cbargs(&cbargs);

               namelen = cnp->cn_namelen;
               if (namelen > 5 &&
                   !strcmp(cnp->cn_nameptr + namelen - 5, "/rsrc")) {
                       namelen -= 5;
                       use_resource_fork = 1;
               }

               /* XXX: when decomposing, string could grow
                  and we have to handle overflow */
               unicn = malloc(namelen * sizeof(unicn[0]), M_TEMP, M_WAITOK);
               len = utf8_to_utf16(unicn, namelen,
                   cnp->cn_nameptr, namelen, 0, NULL);
               for (ni = 0; ni < len; ni++)
                       if (unicn[ni] == (unichar_t)':')
                               unicn[ni] = (unichar_t)'/';
               /* XXX: check conversion errors? */
               if (hfslib_make_catalog_key(VTOH(vdp)->h_rec.u.cnid, len, unicn,
                   &key) == 0) {
                       DPRINTF(("ERROR in hfslib_make_catalog_key\n"));
                       error = EINVAL;
                       goto error;
               }

               result = hfslib_find_catalog_record_with_key(&dp->h_hmp->hm_vol,
                   &key, &rec, &cbargs);
               if (result > 0) {
                       error = EINVAL;
                       goto error;
               }
               if (result < 0) {
                       if (cnp->cn_nameiop == CREATE)
                               error = EROFS;
                       else
                               error = ENOENT;
                       goto error;
               }

               if (rec.file.user_info.file_type == HFS_HARD_LINK_FILE_TYPE
                   && rec.file.user_info.file_creator == HFS_HFSLUS_CREATOR) {
                       if (hfslib_get_hardlink(&dp->h_hmp->hm_vol,
                           rec.file.bsd.special.inode_num,
                           &rec, &cbargs) != 0) {
                               error = EINVAL;
                               goto error;
                       }
               }

               if (rec.type == HFS_REC_FILE
                   && use_resource_fork
                   && rec.file.rsrc_fork.logical_size > 0) {
                   /* advance namei next pointer to end of string */
                       error = hfs_vget_internal(vdp->v_mount, rec.file.cnid,
                           HFS_RSRCFORK, &tdp);
               } else
                       error = hfs_vget_internal(vdp->v_mount, rec.file.cnid,
                           HFS_DATAFORK, &tdp);
               if (error != 0)
                       goto error;
               *vpp = tdp;
       }
       DPRINTF(("\n"));
       /*
        * Insert name into cache if appropriate.
        */
/* XXX Cache disabled until we can make sure it works. */
#if 0
       cache_enter(vdp, *vpp, cnp);
#endif

       error = 0;

       /* FALLTHROUGH */
error:
       if (unicn != NULL)
               free(unicn, M_TEMP);

       return error;
}

int
hfs_vop_open(void *v)
{
#if 0
       struct vop_open_args /* {
               struct vnode *a_vp;
               int a_mode;
               kauth_cred_t a_cred;
       } */ *ap = v;
       struct hfsnode *hn = VTOH(ap->a_vp);
#endif
       DPRINTF(("VOP = hfs_vop_open()\n"));

       /*
        * XXX This is a good place to read and cache the file's extents to
        * XXX avoid doing it upon every read/write. Must however keep the
        * XXX cache in sync when the file grows/shrinks. (So would that go
        * XXX in vop_truncate?)
        */

       return 0;
}

int
hfs_vop_close(void *v)
{
#if 0
       struct vop_close_args /* {
               struct vnode *a_vp;
               int a_fflag;
               kauth_cred_t a_cred;
       } */ *ap = v;
       struct hfsnode *hn = VTOH(ap->a_vp);
#endif
       DPRINTF(("VOP = hfs_vop_close()\n"));

       /* Release extents cache here. */

       return 0;
}

static int
hfs_check_possible(struct vnode *vp, accmode_t accmode)
{

       /*
        * Disallow writes on files, directories, and symlinks
        * since we have no write support yet.
        */

       if (accmode & VWRITE) {
               switch (vp->v_type) {
               case VDIR:
               case VLNK:
               case VREG:
                       return EROFS;
               default:
                       break;
               }
       }

       return 0;
}

static int
hfs_check_permitted(vnode_t *vp, struct vattr *va, accmode_t accmode,
   kauth_cred_t cred)
{

       return kauth_authorize_vnode(cred, KAUTH_ACCESS_ACTION(accmode,
           va->va_type, va->va_mode), vp, NULL,  genfs_can_access(vp, cred,
           va->va_uid, va->va_gid, va->va_mode, NULL, accmode));
}

int
hfs_vop_access(void *v)
{
       struct vop_access_args /* {
               struct vnode *a_vp;
               int a_accmode;
               kauth_cred_t a_cred;
       } */ *ap = v;
       struct vattr va;
       int error;

       DPRINTF(("VOP = hfs_vop_access()\n"));

       error = hfs_check_possible(ap->a_vp, ap->a_accmode);
       if (error)
               return error;

       if ((error = VOP_GETATTR(ap->a_vp, &va, ap->a_cred)) != 0)
               return error;

       error = hfs_check_permitted(ap->a_vp, &va, ap->a_accmode, ap->a_cred);

       return error;
}

int
hfs_vop_getattr(void *v)
{
       struct vop_getattr_args /* {
               struct vnode    *a_vp;
               struct vattr    *a_vap;
               struct ucred    *a_cred;
       } */ *ap = v;
       struct vnode    *vp;
       struct hfsnode  *hp;
       struct vattr    *vap;
       hfs_bsd_data_t *bsd;
       hfs_fork_t     *fork;

       DPRINTF(("VOP = hfs_vop_getattr()\n"));

       vp = ap->a_vp;
       hp = VTOH(vp);
       vap = ap->a_vap;

       vattr_null(vap);

       /*
        * XXX Cannot trust permissions/modes/flags stored in an HFS+ catalog
        * XXX record those values are not set on files created under Mac OS 9.
        */
       vap->va_type = ap->a_vp->v_type;
       if (hp->h_rec.u.rec_type == HFS_REC_FILE) {
               hfs_file_record_t *f = &hp->h_rec.file;
               if (hp->h_fork == HFS_RSRCFORK)
                       fork = &f->rsrc_fork;
               else
                       fork = &f->data_fork;
               vap->va_fileid = f->cnid;
               bsd = &f->bsd;
               vap->va_bytes = fork->total_blocks * HFS_BLOCKSIZE(vp);
               vap->va_size = fork->logical_size;
               hfs_time_to_timespec(f->date_created, &vap->va_ctime);
               hfs_time_to_timespec(f->date_content_mod, &vap->va_mtime);
               hfs_time_to_timespec(f->date_accessed, &vap->va_atime);
               vap->va_nlink = 1;
       } else if (hp->h_rec.u.rec_type == HFS_REC_FLDR) {
               hfs_folder_record_t *f = &hp->h_rec.folder;
               vap->va_fileid = hp->h_rec.folder.cnid;
               bsd = &f->bsd;
               vap->va_size = 512; /* XXX Temporary */
               vap->va_bytes = 512; /* XXX Temporary */
               hfs_time_to_timespec(f->date_created, &vap->va_ctime);
               hfs_time_to_timespec(f->date_content_mod,&vap->va_mtime);
               hfs_time_to_timespec(f->date_accessed, &vap->va_atime);
               vap->va_nlink = 2; /* XXX */
       } else {
               DPRINTF(("hfs+: hfs_vop_getattr(): invalid record type %i",
                   hp->h_rec.u.rec_type));
               return EINVAL;
       }

       if ((bsd->file_mode & S_IFMT) == 0) {
               /* no bsd permissions recorded, use default values */
               if (hp->h_rec.u.rec_type == HFS_REC_FILE)
                       vap->va_mode = (S_IFREG | HFS_DEFAULT_FILE_MODE);
               else
                       vap->va_mode = (S_IFDIR | HFS_DEFAULT_DIR_MODE);
               vap->va_uid = HFS_DEFAULT_UID;
               vap->va_gid = HFS_DEFAULT_GID;
       } else {
               vap->va_mode = bsd->file_mode;
               vap->va_uid = bsd->owner_id;
               vap->va_gid = bsd->group_id;
               if ((vap->va_mode & S_IFMT) == S_IFCHR
                   || (vap->va_mode & S_IFMT) == S_IFBLK) {
                       vap->va_rdev
                           = HFS_CONVERT_RDEV(bsd->special.raw_device);
               }
               else if (bsd->special.link_count != 0) {
                   /* XXX: only if in metadata directory */
                   vap->va_nlink = bsd->special.link_count;
               }
       }

       vap->va_fsid = hp->h_dev;
       vap->va_blocksize = hp->h_hmp->hm_vol.vh.block_size;
       vap->va_gen = 1;
       vap->va_flags = 0;

       return 0;
}

int
hfs_vop_setattr(void *v)
{
       struct vop_setattr_args /* {
               struct vnode    *a_vp;
               struct vattr    *a_vap;
               kauth_cred_t    a_cred;
       } */ *ap = v;
       struct vattr    *vap;
       struct vnode    *vp;

       vap = ap->a_vap;
       vp = ap->a_vp;

       /*
        * Check for unsettable attributes.
        */
       if ((vap->va_type != VNON) || (vap->va_nlink != VNOVAL) ||
           (vap->va_fsid != VNOVAL) || (vap->va_fileid != VNOVAL) ||
           (vap->va_blocksize != VNOVAL) || (vap->va_rdev != VNOVAL) ||
           ((int)vap->va_bytes != VNOVAL) || (vap->va_gen != VNOVAL)) {
               return EINVAL;
       }

       /* XXX: needs revisiting for write support */
       if (vap->va_flags != VNOVAL
           || vap->va_uid != (uid_t)VNOVAL || vap->va_gid != (gid_t)VNOVAL
           || vap->va_atime.tv_sec != VNOVAL || vap->va_mtime.tv_sec != VNOVAL
           || vap->va_birthtime.tv_sec != VNOVAL) {
               return EROFS;
       }

       if (vap->va_size != VNOVAL) {
               /*
                * Disallow write attempts on read-only file systems;
                * unless the file is a socket, fifo, or a block or
                * character device resident on the file system.
                */
               switch (vp->v_type) {
               case VDIR:
                       return EISDIR;
               case VCHR:
               case VBLK:
               case VFIFO:
                       break;
               case VREG:
                       return EROFS;
               default:
                       return EOPNOTSUPP;
               }
       }

       return 0;
}

int
hfs_vop_bmap(void *v)
{
       struct vop_bmap_args /* {
               struct vnode *a_vp;
               daddr_t  a_bn;
               struct vnode **a_vpp;
               daddr_t *a_bnp;
               int *a_runp;
       } */ *ap = v;
       struct vnode *vp;
       struct hfsnode *hp;
       daddr_t lblkno;
       hfs_callback_args cbargs;
       hfs_libcb_argsread argsread;
       hfs_extent_descriptor_t *extents;
       uint16_t numextents, i;
       int bshift;

       vp = ap->a_vp;
       hp = VTOH(vp);
       lblkno = ap->a_bn;
       bshift = vp->v_mount->mnt_fs_bshift;

       /*
        * Check for underlying vnode requests and ensure that logical
        * to physical mapping is requested.
        */
       if (ap->a_vpp != NULL)
               *ap->a_vpp = hp->h_devvp;
       if (ap->a_bnp == NULL)
               return 0;

       hfslib_init_cbargs(&cbargs);
       argsread.cred = NULL;
       argsread.l = NULL;
       cbargs.read = &argsread;

       numextents = hfslib_get_file_extents(&hp->h_hmp->hm_vol,
           hp->h_rec.u.cnid, hp->h_fork, &extents, &cbargs);

       /* XXX: is this correct for 0-length files? */
       if (numextents == 0)
               return EBADF;

       for (i = 0; i < numextents; i++) {
               if (lblkno < extents[i].block_count)
                       break;
               lblkno -= extents[i].block_count;
       }

       if (i == numextents) {
               /* XXX: block number past EOF */
               i--;
               lblkno += extents[i].block_count;
       }

       *ap->a_bnp = ((extents[i].start_block + lblkno) << (bshift-DEV_BSHIFT))
           + (hp->h_hmp->hm_vol.offset >> DEV_BSHIFT);

       if (ap->a_runp) {
               int nblk;

               nblk = extents[i].block_count - lblkno - 1;
               if (nblk <= 0)
                       *ap->a_runp = 0;
               else if (nblk > MAXBSIZE >> bshift)
                       *ap->a_runp = (MAXBSIZE >> bshift) - 1;
               else
                       *ap->a_runp = nblk;
       }

       free(extents, M_TEMP);

       return 0;
}

int
hfs_vop_read(void *v)
{
       struct vop_read_args /* {
               struct vnode *a_vp;
               struct uio *a_uio;
               int a_ioflag;
               kauth_cred_t a_cred;
       } */ *ap = v;
       struct vnode *vp;
       struct hfsnode *hp;
       struct uio *uio;
       uint64_t fsize; /* logical size of file */
       int advice;
       int error;

       vp = ap->a_vp;
       hp = VTOH(vp);
       uio = ap->a_uio;
       if (hp->h_fork == HFS_RSRCFORK)
               fsize = hp->h_rec.file.rsrc_fork.logical_size;
       else
               fsize = hp->h_rec.file.data_fork.logical_size;
       error = 0;
       advice = IO_ADV_DECODE(ap->a_ioflag);

       if (uio->uio_offset < 0)
               return EINVAL;

       if (uio->uio_resid == 0 || uio->uio_offset >= fsize)
               return 0;

       if (vp->v_type != VREG && vp->v_type != VLNK)
               return EINVAL;

       error = 0;
       while (uio->uio_resid > 0 && error == 0) {
               vsize_t len;

               len = MIN(uio->uio_resid, fsize - uio->uio_offset);
               if (len == 0)
                       break;

               error = ubc_uiomove(&vp->v_uobj, uio, len, advice,
                   UBC_READ | UBC_PARTIALOK | UBC_VNODE_FLAGS(vp));
       }

       return error;
}

int
hfs_vop_readdir(void *v)
{
       struct vop_readdir_args /* {
               struct vnode *a_vp;
               struct uio *a_uio;
               kauth_cred_t a_cred;
               int *a_eofflag;
               off_t **a_cookies;
               int a_*ncookies;
       } */ *ap = v;

       DPRINTF(("VOP = hfs_vop_readdir()\n"));

       struct dirent curent; /* the dirent entry we are constructing */
       struct hfsnode *hp;
       hfs_catalog_keyed_record_t *children;
       hfs_unistr255_t *childnames;
       hfs_callback_args cbargs;
       hfs_libcb_argsread argsread;
       struct uio *uio;
       off_t bufoff; /* offset in buffer relative to start of dirents */
       uint32_t numchildren;
       uint32_t curchild; /* index of child we are stuffing into dirent */
       size_t namlen, ni;
       int error;
       int i; /* dummy variable */

       bufoff = 0;
       children = NULL;
       error = 0;
       numchildren = 0;
       hp = VTOH(ap->a_vp);
       uio = ap->a_uio;

       if (uio->uio_offset < 0)
               return EINVAL;
       if (ap->a_eofflag != NULL)
               *ap->a_eofflag = 0;

/* XXX Inform that we don't support NFS, for now. */
#if 0
       if(ap->a_eofflag != NULL || ap->a_cookies != NULL ||
           ap->a_ncookies != NULL)
               return EOPNOTSUPP;
#endif
       DPRINTF(("READDIR uio: offset=%td, resid=%zu\n",
           uio->uio_offset, uio->uio_resid));
       hfslib_init_cbargs(&cbargs);
       argsread.cred = ap->a_cred;
       argsread.l = NULL;
       cbargs.read = &argsread;

       /* XXX Should we cache this? */
       if (hfslib_get_directory_contents(&hp->h_hmp->hm_vol, hp->h_rec.u.cnid,
           &children, &childnames, &numchildren, &cbargs) != 0) {
               DPRINTF(("ENOENT\n"));
               error = ENOENT;
               goto error;
       }

       DPRINTF(("numchildren = %u\n", numchildren));
       for (curchild = 0; curchild < numchildren && uio->uio_resid > 0;
           curchild++) {
               namlen = utf16_to_utf8(curent.d_name, NAME_MAX,
                   childnames[curchild].unicode, childnames[curchild].length,
                   0, NULL);
               /* XXX: check conversion errors? */
               if (namlen > NAME_MAX) {
                       /* XXX: how to handle name too long? */
                       continue;
               }
               for (ni = 0; ni < namlen; ni++)
                       if (curent.d_name[ni] == '/')
                               curent.d_name[ni] = ':';
               curent.d_namlen = namlen;
               curent.d_reclen = _DIRENT_SIZE(&curent);

               /* Skip to desired dirent. */
               bufoff += curent.d_reclen;
               if (bufoff - curent.d_reclen < uio->uio_offset)
                       continue;

               /* Make sure we don't return partial entries. */
               if (uio->uio_resid < curent.d_reclen) {
                       DPRINTF(("PARTIAL ENTRY\n"));
                       if (ap->a_eofflag != NULL)
                               *ap->a_eofflag = 1;
                       break;
               }

               curent.d_fileno = children[curchild].file.cnid;
               switch (hfs_catalog_keyed_record_vtype(children+curchild)) {
               case VREG:
                       curent.d_type = DT_REG;
                       break;
               case VDIR:
                       curent.d_type = DT_DIR;
                       break;
               case VBLK:
                       curent.d_type = DT_BLK;
                       break;
               case VCHR:
                       curent.d_type = DT_CHR;
                       break;
               case VLNK:
                       curent.d_type = DT_LNK;
                       break;
               case VSOCK:
                       curent.d_type = DT_SOCK;
                       break;
               case VFIFO:
                       curent.d_type = DT_FIFO;
                       break;
               default:
                       curent.d_type = DT_UNKNOWN;
                       break;
               }
               DPRINTF(("curchildname = %s\t\t", curchildname));
               /* pad curent.d_name to aligned byte boundary */
               for (i = curent.d_namlen;
                   i < curent.d_reclen - _DIRENT_NAMEOFF(&curent); i++)
                       curent.d_name[i] = 0;

               DPRINTF(("curent.d_name = %s\n", curent.d_name));

               if ((error = uiomove(&curent, curent.d_reclen, uio)) != 0)
                       goto error;
       }

       /* FALLTHROUGH */

error:
       if (numchildren > 0) {
               if (children != NULL)
                       free(children, M_TEMP);
               if (childnames != NULL)
                       free(childnames, M_TEMP);
       }

       if (error) {
               DPRINTF(("ERROR = %i\n", error));
       }

       return error;
}

int
hfs_vop_readlink(void *v) {
       struct vop_readlink_args /* {
               struct vnode *a_vp;
               struct uio *a_uio;
               kauth_cred_t a_cred;
       } */ *ap = v;

       return VOP_READ(ap->a_vp, ap->a_uio, 0, ap->a_cred);
}

int
hfs_vop_reclaim(void *v)
{
       struct vop_reclaim_v2_args /* {
               struct vnode *a_vp;
       } */ *ap = v;
       struct vnode *vp;
       struct hfsnode *hp;

       VOP_UNLOCK(ap->a_vp);

       DPRINTF(("VOP = hfs_vop_reclaim()\n"));

       vp = ap->a_vp;
       hp = VTOH(vp);

       /* Decrement the reference count to the volume's device. */
       if (hp->h_devvp) {
               vrele(hp->h_devvp);
               hp->h_devvp = 0;
       }

       genfs_node_destroy(vp);
       pool_put(&hfs_node_pool, hp);
       vp->v_data = NULL;

       return 0;
}

int
hfs_vop_print(void *v)
{
       struct vop_print_args /* {
               struct vnode    *a_vp;
       } */ *ap = v;
       struct vnode    *vp;
       struct hfsnode  *hp;

       DPRINTF(("VOP = hfs_vop_print()\n"));

       vp = ap->a_vp;
       hp = VTOH(vp);

       printf("dummy = %X\n", (unsigned)hp->dummy);
       printf("\n");

       return 0;
}