/* * Copyright (c) 2000-2004 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * The contents of this file constitute Original Code as defined in and * are subject to the Apple Public Source License Version 1.1 (the * "License"). You may not use this file except in compliance with the * License. Please obtain a copy of the License at * http://www.apple.com/publicsource and read it before using this file. * * This Original Code and all software distributed under the License are * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the * License for the specific language governing rights and limitations * under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * Copyright (c) 1998-1999 Apple Computer, Inc. All Rights Reserved. * * Modification History: * * 02-Feb-2000 Clark Warner Added copyfile to table * 17-Aug-1999 Pat Dirks New today. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "synthfs.h" #define RWSUPPORT 0 #if RWSUPPORT #error NOT PORTED FOR UBC #include #endif static int synthfs_remove_internal(struct vnode *dvp, struct vnode *vp, struct componentname *cnp, vfs_context_t context); #define VOPFUNC int (*)(void *) /* Global vfs data structures for synthfs. */ int (**synthfs_vnodeop_p) (void *); struct vnodeopv_entry_desc synthfs_vnodeop_entries[] = { {&vnop_default_desc, (VOPFUNC)vn_default_error}, {&vnop_strategy_desc, (VOPFUNC)err_strategy}, /* strategy - not supported */ {&vnop_bwrite_desc, (VOPFUNC)err_bwrite}, /* bwrite - not supported */ {&vnop_lookup_desc, (VOPFUNC)synthfs_cached_lookup}, /* cached lookup */ {&vnop_create_desc, (VOPFUNC)synthfs_create}, /* create - DEBUGGER */ {&vnop_whiteout_desc, (VOPFUNC)err_whiteout}, /* whiteout - not supported */ {&vnop_mknod_desc, (VOPFUNC)err_mknod}, /* mknod - not supported */ {&vnop_open_desc, (VOPFUNC)synthfs_open}, /* open - DEBUGGER */ {&vnop_close_desc, (VOPFUNC)nop_close}, /* close - NOP */ {&vnop_getattr_desc, (VOPFUNC)synthfs_getattr}, /* getattr */ {&vnop_setattr_desc, (VOPFUNC)synthfs_setattr}, /* setattr */ {&vnop_getattrlist_desc, (VOPFUNC)err_getattrlist}, /* getattrlist - not supported */ {&vnop_setattrlist_desc, (VOPFUNC)err_setattrlist}, /* setattrlist - not supported */ {&vnop_read_desc, (VOPFUNC)err_read}, /* read - not supported */ {&vnop_write_desc, (VOPFUNC)err_write}, /* write - not supported */ {&vnop_ioctl_desc, (VOPFUNC)err_ioctl}, /* ioctl - not supported */ {&vnop_select_desc, (VOPFUNC)synthfs_select}, /* select */ {&vnop_exchange_desc, (VOPFUNC)err_exchange}, /* exchange - not supported */ {&vnop_revoke_desc, (VOPFUNC)nop_revoke}, /* revoke - NOP */ {&vnop_mmap_desc, (VOPFUNC)synthfs_mmap}, /* mmap - DEBUGGER */ {&vnop_fsync_desc, (VOPFUNC)nop_fsync}, /* fsync - NOP */ {&vnop_remove_desc, (VOPFUNC)synthfs_remove}, /* remove */ {&vnop_link_desc, (VOPFUNC)err_link}, /* link - not supported */ {&vnop_rename_desc, (VOPFUNC)synthfs_rename}, /* rename */ {&vnop_mkdir_desc, (VOPFUNC)synthfs_mkdir}, /* mkdir */ {&vnop_rmdir_desc, (VOPFUNC)synthfs_rmdir}, /* rmdir */ {&vnop_symlink_desc, (VOPFUNC)synthfs_symlink}, /* symlink */ {&vnop_readdir_desc, (VOPFUNC)synthfs_readdir}, /* readdir */ {&vnop_readdirattr_desc, (VOPFUNC)err_readdirattr}, /* readdirattr - not supported */ {&vnop_readlink_desc, (VOPFUNC)synthfs_readlink}, /* readlink */ {&vnop_inactive_desc, (VOPFUNC)synthfs_inactive}, /* inactive */ {&vnop_reclaim_desc, (VOPFUNC)synthfs_reclaim}, /* reclaim */ {&vnop_pathconf_desc, (VOPFUNC)synthfs_pathconf}, /* pathconf */ {&vnop_advlock_desc, (VOPFUNC)err_advlock}, /* advlock - not supported */ {&vnop_allocate_desc, (VOPFUNC)err_allocate}, /* allocate - not supported */ {&vnop_pagein_desc, (VOPFUNC)err_pagein}, /* pagein - not supported */ {&vnop_pageout_desc, (VOPFUNC)err_pageout}, /* pageout - not supported */ {&vnop_devblocksize_desc, (VOPFUNC)err_devblocksize}, /* devblocksize - not supported */ {&vnop_searchfs_desc, (VOPFUNC)err_searchfs}, /* searchfs - not supported */ {&vnop_copyfile_desc, (VOPFUNC)err_copyfile}, /* copyfile - not supported */ { &vnop_blktooff_desc, (VOPFUNC)err_blktooff }, /* blktooff not supported */ { &vnop_offtoblk_desc, (VOPFUNC)err_offtoblk }, /* offtoblk not supported */ { &vnop_blockmap_desc, (VOPFUNC)err_blockmap }, /* blockmap not supported */ {(struct vnodeop_desc *) NULL, (int (*) ()) NULL} }; /* * Oh what a tangled web we weave. This structure will be used by * bsd/vfs/vfs_conf.c to actually do the initialization of synthfs_vnodeop_p */ struct vnodeopv_desc synthfs_vnodeop_opv_desc = {&synthfs_vnodeop_p, synthfs_vnodeop_entries}; /* * Create a regular file #% create dvp L U U #% create vpp - L - # vnop_create { IN WILLRELE struct vnode *dvp; OUT struct vnode **vpp; IN struct componentname *cnp; IN struct vnode_attr *vap; We are responsible for freeing the namei buffer, it is done in hfs_makenode(), unless there is a previous error. */ int synthfs_create(ap) struct vnop_create_args /* { struct vnode *a_dvp; struct vnode **a_vpp; struct componentname *a_cnp; struct vnode_attr *a_vap; vfs_context_t a_context; } */ *ap; { #if DEBUG struct vnode *dvp = ap->a_dvp; char debugmsg[255]; sprintf(debugmsg, "synthfs_create: attempt to create '%s' in '%s' ?!", ap->a_cnp->cn_nameptr, VTOS(dvp)->s_name); Debugger(debugmsg); #endif return err_create(ap); } /* * Open called. #% open vp L L L # vnop_open { IN struct vnode *vp; IN int mode; IN vfs_context_t a_context; */ int synthfs_open(ap) struct vnop_open_args /* { struct vnode *a_vp; int a_mode; vfs_context_t a_context; } */ *ap; { struct vnode *vp = ap->a_vp; if (vp->v_type == VDIR) { return 0; } else { #if DEBUG struct synthfsnode *sp = VTOS(vp); char debugmsg[255]; sprintf(debugmsg, "synthfs_open: attempt to open '/%s' ?!", sp->s_name); Debugger(debugmsg); #endif }; return 0; } /* * Mmap a file * * NB Currently unsupported. # XXX - not used # vnop_mmap { IN struct vnode *vp; IN int fflags; IN kauth_cred_t cred; IN struct proc *p; */ /* ARGSUSED */ int synthfs_mmap(__unused struct vnop_mmap_args *ap) { return EINVAL; } /* #% getattr vp = = = # vnop_getattr { IN struct vnode *vp; IN struct vnode_attr *vap; IN vfs_context_t context; */ int synthfs_getattr(ap) struct vnop_getattr_args /* { struct vnode *a_vp; struct vnode_attr *a_vap; vfs_context_t a_context; } */ *ap; { struct vnode *vp = ap->a_vp; struct vnode_attr *vap = ap->a_vap; struct synthfsnode *sp = VTOS(vp); VATTR_RETURN(vap, va_type, vp->v_type); VATTR_RETURN(vap, va_mode, sp->s_mode); VATTR_RETURN(vap, va_nlink, sp->s_linkcount); VATTR_RETURN(vap, va_uid, sp->s_uid); VATTR_RETURN(vap, va_gid, sp->s_gid); VATTR_RETURN(vap, va_fsid, VTOVFS(vp)->mnt_vfsstat.f_fsid.val[0]); VATTR_RETURN(vap, va_fileid, sp->s_nodeid); switch (vp->v_type) { case VDIR: VATTR_RETURN(vap, va_data_size, (sp->s_u.d.d_entrycount + 2) * sizeof(struct dirent)); break; case VREG: VATTR_RETURN(vap, va_data_size, sp->s_u.f.f_size); break; case VLNK: VATTR_RETURN(vap, va_data_size, sp->s_u.s.s_length); break; default: VATTR_RETURN(vap, va_data_size, 0); }; VATTR_RETURN(vap, va_iosize, 512); vap->va_access_time.tv_sec = sp->s_accesstime.tv_sec; vap->va_access_time.tv_nsec = sp->s_accesstime.tv_usec * 1000; VATTR_SET_SUPPORTED(vap, va_access_time); vap->va_modify_time.tv_sec = sp->s_modificationtime.tv_sec; vap->va_modify_time.tv_nsec = sp->s_modificationtime.tv_usec * 1000; VATTR_SET_SUPPORTED(vap, va_modify_time); vap->va_change_time.tv_sec = sp->s_changetime.tv_sec; vap->va_change_time.tv_nsec = sp->s_changetime.tv_usec * 1000; VATTR_SET_SUPPORTED(vap, va_change_time); VATTR_RETURN(vap, va_gen, sp->s_generation); VATTR_RETURN(vap, va_flags, sp->s_flags); VATTR_RETURN(vap, va_rdev, sp->s_rdev); VATTR_RETURN(vap, va_filerev, 0); VATTR_RETURN(vap, va_acl, NULL); return (0); } /* * Change the mode on a file or directory. * vnode vp must be locked on entry. */ int synthfs_chmod(struct vnode *vp, int mode, kauth_cred_t cred, struct proc *p) { struct synthfsnode *sp = VTOS(vp); int result; sp->s_mode &= ~ALLPERMS; sp->s_mode |= (mode & ALLPERMS); sp->s_nodeflags |= IN_CHANGE; #if RWSUPPORT if ((vp->v_flag & VTEXT) && (sp->s_mode & S_ISTXT) == 0) (void) vnode_uncache(vp); #endif return 0; } /* * Change the flags on a file or directory. * vnode vp must be locked on entry. */ int synthfs_chflags(struct vnode *vp, u_long flags, kauth_cred_t cred, struct proc *p) { struct synthfsnode *sp = VTOS(vp); sp->s_flags = flags; sp->s_nodeflags |= IN_CHANGE; return 0; } /* * Perform chown operation on vnode vp; * vnode vp must be locked on entry. */ int synthfs_chown(struct vnode *vp, uid_t uid, gid_t gid, kauth_cred_t cred, struct proc *p) { struct synthfsnode *sp = VTOS(vp); uid_t ouid; gid_t ogid; int result = 0; int is_member; if (uid == (uid_t)VNOVAL) uid = sp->s_uid; if (gid == (gid_t)VNOVAL) gid = sp->s_gid; ogid = sp->s_gid; ouid = sp->s_uid; sp->s_gid = gid; sp->s_uid = uid; if (ouid != uid || ogid != gid) sp->s_nodeflags |= IN_CHANGE; if (ouid != uid && suser(cred, NULL)) sp->s_mode &= ~S_ISUID; if (ogid != gid && suser(cred, NULL)) sp->s_mode &= ~S_ISGID; return 0; } /* * Set attribute vnode op. called from several syscalls #% setattr vp L L L # vnop_setattr { IN struct vnode *vp; IN struct vnode_attr *vap; IN vfs_context_t context; */ int synthfs_setattr(ap) struct vnop_setattr_args /* { struct vnode *a_vp; struct vnode_attr *a_vap; vfs_context_t a_context; } */ *ap; { struct vnode *vp = ap->a_vp; struct synthfsnode *sp = VTOS(vp); struct vnode_attr *vap = ap->a_vap; kauth_cred_t cred = vfs_context_ucred(ap->a_context); struct proc *p = vfs_context_proc(ap->a_context); struct timeval atimeval, mtimeval; uid_t nuid; gid_t ngid; int result; result = 0; if (VATTR_IS_ACTIVE(vap, va_flags)) { if ((result = synthfs_chflags(vp, vap->va_flags, cred, p))) { goto Err_Exit; } } VATTR_SET_SUPPORTED(vap, va_flags); nuid = (uid_t)ngid = (gid_t)VNOVAL; if (VATTR_IS_ACTIVE(vap, va_uid)) nuid = vap->va_uid; if (VATTR_IS_ACTIVE(vap, va_gid)) ngid = vap->va_gid; if (nuid != (uid_t)VNOVAL || ngid != (gid_t)VNOVAL) { if ((result = synthfs_chown(vp, nuid, ngid, cred, p))) { goto Err_Exit; } } VATTR_SET_SUPPORTED(vap, va_uid); VATTR_SET_SUPPORTED(vap, va_gid); if (VATTR_IS_ACTIVE(vap, va_data_size)) { #if RWSUPPORT if ((result = vnode_setsize(vp, vap->va_data_size, 0, ap->a_context))) { goto Err_Exit; }; VATTR_SET_SUPPORTED(vap, va_data_size); #else result = EINVAL; goto Err_Exit; #endif } sp = VTOS(vp); if (VATTR_IS_ACTIVE(vap, va_access_time) || VATTR_IS_ACTIVE(vap, va_modify_time)) { if (VATTR_IS_ACTIVE(vap, va_access_time)) { sp->s_nodeflags |= IN_ACCESS; atimeval.tv_sec = vap->va_access_time.tv_sec; atimeval.tv_usec = vap->va_access_time.tv_nsec / 1000; } if (VATTR_IS_ACTIVE(vap, va_modify_time)) { sp->s_nodeflags |= IN_CHANGE | IN_UPDATE; mtimeval.tv_sec = vap->va_modify_time.tv_sec; mtimeval.tv_usec = vap->va_modify_time.tv_nsec / 1000; } if ((result = synthfs_update(vp, &atimeval, &mtimeval, 1))) { goto Err_Exit; } } VATTR_SET_SUPPORTED(vap, va_access_time); VATTR_SET_SUPPORTED(vap, va_modify_time); if (VATTR_IS_ACTIVE(vap, va_mode)) result = synthfs_chmod(vp, (int)vap->va_mode, cred, p); VATTR_SET_SUPPORTED(vap, va_mode); Err_Exit: DBG_VOP(("synthfs_setattr: returning %d...\n", result)); return (result); } /* #% rename sourcePar_vp U U U #% rename source_vp U U U #% rename targetPar_vp L U U #% rename target_vp X U U # vnop_rename { IN WILLRELE struct vnode *sourcePar_vp; IN WILLRELE struct vnode *source_vp; IN struct componentname *source_cnp; IN WILLRELE struct vnode *targetPar_vp; IN WILLRELE struct vnode *target_vp; IN struct componentname *target_cnp; */ /* * On entry: * source's parent directory is unlocked * source file or directory is unlocked * destination's parent directory is locked * destination file or directory is locked if it exists * * On exit: * all denodes should be released * */ int synthfs_rename(ap) struct vnop_rename_args /* { struct vnode *a_fdvp; struct vnode *a_fvp; struct componentname *a_fcnp; struct vnode *a_tdvp; struct vnode *a_tvp; struct componentname *a_tcnp; vfs_context_t a_context; } */ *ap; { struct vnode *target_vp = ap->a_tvp; struct vnode *targetPar_vp = ap->a_tdvp; struct vnode *source_vp = ap->a_fvp; struct vnode *sourcePar_vp = ap->a_fdvp; struct componentname *target_cnp = ap->a_tcnp; struct componentname *source_cnp = ap->a_fcnp; struct synthfsnode *target_sp, *targetPar_sp, *source_sp, *sourcePar_sp; u_short doingdirectory = 0, oldparent = 0, newparent = 0; int retval = 0; struct timeval tv; #if SYNTHFS_DIAGNOSTIC if ((target_cnp->cn_flags & HASBUF) == 0 || (source_cnp->cn_flags & HASBUF) == 0) panic("synthfs_rename: no name"); #endif DBG_ASSERT((ap->a_fdvp->v_type == VDIR) && (ap->a_tdvp->v_type == VDIR)); target_sp = targetPar_sp = source_sp = sourcePar_sp = NULL; sourcePar_sp = VTOS(sourcePar_vp); source_sp = VTOS(source_vp); oldparent = sourcePar_sp->s_nodeid; /* * Be sure we are not renaming ".", "..", or an alias of ".". This * leads to a crippled directory tree. It's pretty tough to do a * "ls" or "pwd" with the "." directory entry missing, and "cd .." * doesn't work if the ".." entry is missing. */ if (source_sp->s_type == SYNTHFS_DIRECTORY) { if ((source_cnp->cn_namelen == 1 && source_cnp->cn_nameptr[0] == '.') || sourcePar_sp == source_sp || (source_cnp->cn_flags & ISDOTDOT) || (source_sp->s_nodeflags & IN_RENAME)) { retval = EINVAL; goto abortit; } source_sp->s_nodeflags |= IN_RENAME; doingdirectory = TRUE; } /* Transit between abort and bad */ targetPar_sp = VTOS(targetPar_vp); target_sp = target_vp ? VTOS(target_vp) : NULL; newparent = targetPar_sp->s_nodeid; /* * If the destination exists, then be sure its type (file or dir) * matches that of the source. And, if it is a directory make sure * it is empty. Then delete the destination. */ if (target_vp) { #if RWSUPPORT if (target_vp->v_type == VREG) { (void) vnode_uncache(target_vp); }; #endif cache_purge(target_vp); retval = synthfs_remove_internal(targetPar_vp, target_vp, target_cnp, ap->a_context); target_vp = NULL; target_sp = NULL; if (retval) goto bad; }; /* remove the existing entry from the namei cache: */ if (source_vp->v_type == VREG) cache_purge(source_vp); retval = synthfs_move_rename_entry( source_vp, targetPar_vp, target_cnp->cn_nameptr); if (retval) goto bad; source_sp->s_nodeflags &= ~IN_RENAME; /* * Timestamp both parent directories. * Note that if this is a rename within the same directory, * (where targetPar_hp == sourcePar_hp) * the code below is still safe and correct. */ targetPar_sp->s_nodeflags |= IN_UPDATE; sourcePar_sp->s_nodeflags |= IN_UPDATE; microtime(&tv); SYNTHFSTIMES(targetPar_sp, &tv, &tv); SYNTHFSTIMES(sourcePar_sp, &tv, &tv); return (retval); bad:; if (retval && doingdirectory) source_sp->s_nodeflags &= ~IN_RENAME; return (retval); abortit:; return (retval); } /* * Mkdir system call #% mkdir dvp L U U #% mkdir vpp - L - # vnop_mkdir { IN WILLRELE struct vnode *dvp; OUT struct vnode **vpp; IN struct componentname *cnp; IN struct vnode_attr *vap; IN vfs_context_t context; We are responsible for freeing the namei buffer, it is done in synthfs_makenode(), unless there is a previous error. */ int synthfs_mkdir(ap) struct vnop_mkdir_args /* { struct vnode *a_dvp; struct vnode **a_vpp; struct componentname *a_cnp; struct vnode_attr *a_vap; vfs_context_t a_context; } */ *ap; { int retval; struct vnode *dvp = ap->a_dvp; struct componentname *cnp = ap->a_cnp; int mode = MAKEIMODE(ap->a_vap->va_type, ap->a_vap->va_mode); struct vnode *vp = NULL; *ap->a_vpp = NULL; retval = synthfs_new_directory(VTOVFS(dvp), dvp, cnp->cn_nameptr, VTOSFS(dvp)->synthfs_nextid++, mode, vfs_context_proc(cnp->cn_context), &vp); if (retval) goto Error_Exit; *ap->a_vpp = vp; retval = vnode_setattr(vp, ap->a_vap, ap->a_context); if (retval != 0) goto Error_Exit; Error_Exit:; if (retval != 0) { if (vp) synthfs_remove_directory(vp); } return retval; } /* #% remove dvp L U U #% remove vp L U U # vnop_remove { IN WILLRELE struct vnode *dvp; IN WILLRELE struct vnode *vp; IN struct componentname *cnp; IN vfs_context_t context; */ int synthfs_remove(ap) struct vnop_remove_args /* { struct vnode *a_dvp; struct vnode *a_vp; struct componentname *a_cnp; vfs_context_t a_context; } */ *ap; { return synthfs_remove_internal(ap->a_dvp, ap->a_vp, ap->a_cnp, ap->a_context); } static int synthfs_remove_internal(struct vnode *dvp, struct vnode *vp, __unused struct componentname *cnp, __unused vfs_context_t context) { struct synthfsnode *sp = VTOS(vp); struct timeval tv; int retval = 0; /* This is sort of silly right now but someday it may make sense... */ if (sp->s_nodeflags & IN_MODIFIED) { microtime(&tv); synthfs_update(vp, &tv, &tv, 0); }; /* remove the entry from the namei cache: */ cache_purge(vp); /* remove entry from tree and reclaim any resources consumed: */ switch (sp->s_type) { case SYNTHFS_DIRECTORY: synthfs_remove_directory(vp); break; case SYNTHFS_SYMLINK: synthfs_remove_symlink(vp); break; case SYNTHFS_FILE: /* Fall through to default case */ default: synthfs_remove_entry(vp); }; out: if (! retval) VTOS(dvp)->s_nodeflags |= IN_CHANGE | IN_UPDATE; return (retval); } /* #% rmdir dvp L U U #% rmdir vp L U U # vnop_rmdir { IN WILLRELE struct vnode *dvp; IN WILLRELE struct vnode *vp; IN struct componentname *cnp; IN vfs_context_t context; */ int synthfs_rmdir(ap) struct vnop_rmdir_args /* { struct vnode *a_dvp; struct vnode *a_vp; struct componentname *a_cnp; vfs_context_t a_context; } */ *ap; { return synthfs_remove((struct vnop_remove_args *)ap); } /* * synthfs_select - just say OK. Only possible op is readdir * * Locking policy: ignore */ int synthfs_select(__unused struct vnop_select_args /* { struct vnode *a_vp; int a_which; int a_fflags; kauth_cred_t a_cred; void *a_wql; struct proc *a_p; } */ *ap) { DBG_VOP(("synthfs_select called\n")); return (1); } /* # #% symlink dvp L U U #% symlink vpp - U - # # XXX - note that the return vnode has already been vnode_put'ed # by the filesystem layer. To use it you must use vnode_get, # possibly with a further namei. # vnop_symlink { IN WILLRELE struct vnode *dvp; OUT WILLRELE struct vnode **vpp; IN struct componentname *cnp; IN struct vnode_attr *vap; IN char *target; We are responsible for freeing the namei buffer, it is done in synthfs_makenode(), unless there is a previous error. */ int synthfs_symlink(ap) struct vnop_symlink_args /* { struct vnode *a_dvp; struct vnode **a_vpp; struct componentname *a_cnp; struct vnode_attr *a_vap; char *a_target; vfs_context_t a_context; } */ *ap; { struct vnode *dvp = ap->a_dvp; struct vnode **vpp = ap->a_vpp; struct componentname *cnp = ap->a_cnp; int retval; *vpp = NULL; retval = synthfs_new_symlink(VTOVFS(dvp), dvp, cnp->cn_nameptr, VTOSFS(dvp)->synthfs_nextid++, ap->a_target, vfs_context_proc(cnp->cn_context), vpp); return (retval); } /* # #% readlink vp L L L # vnop_readlink { IN struct vnode *vp; INOUT struct uio *uio; IN kauth_cred_t cred; */ int synthfs_readlink(ap) struct vnop_readlink_args /* { struct vnode *a_vp; struct uio *a_uio; vfs_context_t a_context; } */ *ap; { struct vnode *vp = ap->a_vp; struct synthfsnode *sp = VTOS(vp); struct uio *uio = ap->a_uio; int retval; unsigned long count; if (ap->a_uio->uio_offset > sp->s_u.s.s_length) { return 0; }; // LP64todo - fix this! if (uio->uio_offset + uio_resid(uio) <= sp->s_u.s.s_length) { count = uio_resid(uio); } else { count = sp->s_u.s.s_length - uio->uio_offset; }; retval = uiomove((void *)((unsigned char *)sp->s_u.s.s_symlinktarget + uio->uio_offset), count, uio); return (retval); } /* * Read directory entries. */ int synthfs_readdir(ap) struct vnop_readdir_args /* { struct vnode *a_vp; struct uio *a_uio; int a_flags; int *a_eofflag; int *a_numdirent; vfs_context_t a_context; } */ *ap; { struct synthfsnode *sp = VTOS(ap->a_vp); register struct uio *uio = ap->a_uio; off_t diroffset; /* Offset into simulated directory file */ struct synthfsnode *entry; DBG_VOP(("\tuio_offset = %d, uio_resid = %lld\n", (int) uio->uio_offset, uio_resid(uio))); if (ap->a_flags & (VNODE_READDIR_EXTENDED | VNODE_READDIR_REQSEEKOFF)) return (EINVAL); /* We assume it's all one big buffer... */ if (uio->uio_iovcnt > 1) { DBG_VOP(("\tuio->uio_iovcnt = %d?\n", uio->uio_iovcnt)); return EINVAL; }; diroffset = 0; /* * We must synthesize . and .. */ DBG_VOP(("\tstarting ... uio_offset = %d, uio_resid = %lld\n", (int) uio->uio_offset, uio_resid(uio))); if (uio->uio_offset == diroffset) { DBG_VOP(("\tAdding .\n")); diroffset += synthfs_adddirentry(sp->s_nodeid, DT_DIR, ".", uio); DBG_VOP(("\t after adding ., uio_offset = %d, uio_resid = %lld\n", (int) uio->uio_offset, uio_resid(uio))); } if ((uio_resid(uio) > 0) && (diroffset > uio->uio_offset)) { /* Oops - we skipped over a partial entry: at best, diroffset should've just matched uio->uio_offset */ return EINVAL; }; if (uio->uio_offset == diroffset) { DBG_VOP(("\tAdding ..\n")); if (sp->s_parent != NULL) { diroffset += synthfs_adddirentry(sp->s_parent->s_nodeid, DT_DIR, "..", uio); } else { diroffset += synthfs_adddirentry(sp->s_nodeid, DT_DIR, "..", uio); } DBG_VOP(("\t after adding .., uio_offset = %d, uio_resid = %lld\n", (int) uio->uio_offset, uio_resid(uio))); } if ((uio_resid(uio) > 0) && (diroffset > uio->uio_offset)) { /* Oops - we skipped over a partial entry: at best, diroffset should've just matched uio->uio_offset */ return EINVAL; }; /* OK, so much for the fakes. Now for the "real thing": */ TAILQ_FOREACH(entry, &sp->s_u.d.d_subnodes, s_sibling) { if (diroffset == uio->uio_offset) { /* Return this entry */ diroffset += synthfs_adddirentry(entry->s_nodeid, VTTOIF(STOV(entry)->v_type), entry->s_name, uio); }; if ((uio_resid(uio) > 0) && (diroffset > uio->uio_offset)) { /* Oops - we skipped over a partial entry: at best, diroffset should've just matched uio->uio_offset */ return EINVAL; }; }; if (ap->a_eofflag) *ap->a_eofflag = (entry == NULL); /* If we ran all the way through the list, there is no more */ return 0; } /* #% lookup dvp L ? ? #% lookup vpp - L - */ int synthfs_cached_lookup(ap) struct vnop_lookup_args /* { struct vnode *a_dvp; struct vnode **a_vpp; struct componentname *a_cnp; } */ *ap; { struct vnode *dp = ap->a_dvp; struct componentname *cnp = ap->a_cnp; u_long nameiop = cnp->cn_nameiop; u_long flags = cnp->cn_flags; struct vnode **vpp = ap->a_vpp; int result = 0; DBG_VOP(("synthfs_cached_lookup called, name = %s, namelen = %ld\n", ap->a_cnp->cn_nameptr, ap->a_cnp->cn_namelen)); #if DEBUG if (flags & ISLASTCN) DBG_VOP(("\tISLASTCN is set\n")); #endif *vpp = NULL; /* * Look up an entry in the namei cache */ result = cache_lookup(dp, vpp, cnp); if (result == 0) { /* There was no entry in the cache for this parent vnode/name pair: do the full-blown pathname lookup */ return synthfs_lookup(ap); }; if (result == ENOENT) return result; /* An entry matching the parent vnode/name was found in the cache: */ return (0); Err_Exit:; return result; } int synthfs_lookup(ap) struct vnop_lookup_args /* { struct vnode *a_dvp; struct vnode **a_vpp; struct componentname *a_cnp; vfs_context_t a_context; } */ *ap; { struct vnode *dp = ap->a_dvp; struct synthfsnode *dsp = VTOS(dp); struct componentname *cnp = ap->a_cnp; u_long nameiop = cnp->cn_nameiop; // char *nameptr = cnp->cn_nameptr; u_long flags = cnp->cn_flags; long namelen = cnp->cn_namelen; // struct proc *p = cnp->cn_proc; vfs_context_t ctx = cnp->cn_context; kauth_cred_t cred = vfs_context_ucred(ctx); struct synthfsnode *entry; struct vnode *target_vp = NULL; int result = 0; boolean_t found = FALSE; boolean_t isDot = FALSE; boolean_t isDotDot = FALSE; struct vnode *starting_parent = dp; DBG_VOP(("synthfs_lookup called, name = %s, namelen = %ld\n", ap->a_cnp->cn_nameptr, ap->a_cnp->cn_namelen)); #if DEBUG if (flags & LOCKPARENT) DBG_VOP(("\tLOCKPARENT is set\n")); if (flags & ISLASTCN) DBG_VOP(("\tISLASTCN is set\n")); #endif *ap->a_vpp = NULL; /* first check for "." and ".." */ if (cnp->cn_nameptr[0] == '.') { if (namelen == 1) { /* "." requested */ isDot = TRUE; found = TRUE; target_vp = dp; vnode_get(target_vp); result = 0; goto Std_Exit; } else if ((namelen == 2) && (cnp->cn_nameptr[1] == '.')) { /* ".." requested */ isDotDot = TRUE; found = TRUE; if ((dsp->s_parent != NULL) && (dsp->s_parent != VTOS(dp))) { target_vp = STOV(dsp->s_parent); /* * Special case for ".." to prevent deadlock: * always release the parent vnode BEFORE trying to acquire * ITS parent. This avoids deadlocking with another lookup * starting from the target_vp trying to vnode_get() this directory. */ result = vnode_get(target_vp); } else { target_vp = dp; /* dp is alread locked and ref'ed */ result = 0; } goto Std_Exit; } } /* finally, just look for entries by name (making sure the entry's length matches the cnp's namelen... */ TAILQ_FOREACH(entry, &dsp->s_u.d.d_subnodes, s_sibling) { if ((bcmp(cnp->cn_nameptr, entry->s_name, (unsigned)namelen) == 0) && (*(entry->s_name + namelen) == (char)0)) { found = TRUE; target_vp = STOV(entry); result = vnode_getwithref(target_vp); /* refcount is always > 0 for any vnode in this list... */ if (result != 0) { goto Err_Exit; }; /* The specified entry was found and successfully acquired: */ goto Std_Exit; }; }; found = FALSE; Std_Exit:; if (found) { if ((nameiop == DELETE) && (flags & ISLASTCN)) { /* * If the parent directory is "sticky" then the user must own * the directory, or the file in it, in order to be allowed to * delete it (unless the user is root). This implements * append-only directories */ if ((dsp->s_mode & S_ISVTX) && suser(cred, NULL) && (kauth_cred_getuid(cred) != dsp->s_uid) && (target_vp != NULL) && (target_vp->v_type != VLNK) && (VTOS(target_vp)->s_uid != kauth_cred_getuid(cred))) { vnode_put(target_vp); result = EPERM; goto Err_Exit; }; }; if ((nameiop == RENAME) && (flags & WANTPARENT) && (flags * ISLASTCN)) { if (isDot) { vnode_put(target_vp); result = EISDIR; goto Err_Exit; }; }; } else { /* The specified entry wasn't found: */ result = ENOENT; if ((flags & ISLASTCN) && ((nameiop == CREATE) || (nameiop == RENAME) || ((nameiop == DELETE) && (flags & DOWHITEOUT) && (flags & ISWHITEOUT)))) { /* create a new entry */ result = EJUSTRETURN; } }; *ap->a_vpp = target_vp; Err_Exit:; DBG_VOP(("synthfs_lookup: result = %d.\n", result)); if (found) { if (target_vp) { DBG_VOP(("synthfs_lookup: target_vp = 0x%08X \n", (u_long)target_vp)); } else { DBG_VOP(("synthfs_lookup: found = true but target_vp = NULL?\n")); }; } else { DBG_VOP(("synthf_lookup: target not found.\n")); }; DBG_VOP(("synthfs_lookup: dp = %08X; starting_parent = 0x%08X .\n", (u_long)dp, (u_long)starting_parent)); return result; } /* #% pathconf vp L L L # vnop_pathconf { IN struct vnode *vp; IN int name; OUT register_t *retval; */ int synthfs_pathconf(ap) struct vnop_pathconf_args /* { struct vnode *a_vp; int a_name; int *a_retval; vfs_context_t a_context; } */ *ap; { DBG_VOP(("synthfs_pathconf called\n")); switch (ap->a_name) { case _PC_LINK_MAX: *ap->a_retval = LINK_MAX; return (0); case _PC_NAME_MAX: *ap->a_retval = NAME_MAX; return (0); case _PC_PATH_MAX: *ap->a_retval = PATH_MAX; return (0); case _PC_PIPE_BUF: *ap->a_retval = PIPE_BUF; return (0); case _PC_CHOWN_RESTRICTED: *ap->a_retval = 1; return (0); case _PC_NO_TRUNC: *ap->a_retval = 1; return (0); default: return (EINVAL); } /* NOTREACHED */ } /* * Update the access, modified, and node change times as specified by the * IACCESS, IUPDATE, and ICHANGE flags respectively. The IMODIFIED flag is * used to specify that the node needs to be updated but that the times have * already been set. The access and modified times are taken from the second * and third parameters; the node change time is always taken from the current * time. If waitfor is set, then wait for the disk write of the node to * complete. */ int synthfs_update(struct vnode *vp, struct timeval *access, struct timeval *modify, __unused int waitfor) { struct synthfsnode *sp = VTOS(vp); struct timeval tv; DBG_ASSERT(sp != NULL); if (((sp->s_nodeflags & (IN_ACCESS | IN_CHANGE | IN_MODIFIED | IN_UPDATE)) != 0) && !(VTOVFS(vp)->mnt_flag & MNT_RDONLY)) { if (sp->s_nodeflags & IN_ACCESS) sp->s_accesstime = *access; if (sp->s_nodeflags & IN_UPDATE) sp->s_modificationtime = *modify; if (sp->s_nodeflags & IN_CHANGE) { microtime(&tv); sp->s_changetime = tv; } }; /* After the updates are finished, clear the flags */ sp->s_nodeflags &= ~(IN_ACCESS | IN_CHANGE | IN_MODIFIED | IN_UPDATE); return 0; } /******************************************************************************************* Utility/housekeeping vnode operations: ******************************************************************************************/ /* # #% inactive vp L U U # vnop_inactive { IN struct vnode *vp; IN struct proc *p; */ int synthfs_inactive(ap) struct vnop_inactive_args /* { struct vnode *a_vp; vfs_context_t a_context; } */ *ap; { struct vnode *vp = ap->a_vp; struct synthfsnode *sp = VTOS(vp); struct timeval tv; #if DEBUG if (vp->v_usecount != 0) DBG_VOP(("synthfs_inactive: bad usecount = %d\n", vp->v_usecount )); #endif /* * Ignore nodes related to stale file handles. */ if (vp->v_type == VNON) goto out; /* This is sort of silly but might make sense in the future: */ if (sp->s_nodeflags & (IN_ACCESS | IN_CHANGE | IN_MODIFIED | IN_UPDATE)) { microtime(&tv); synthfs_update(vp, &tv, &tv, 0); } out: /* * If we are done with the inode, reclaim it * so that it can be reused immediately. */ if (vp->v_type == VNON) { vnode_recycle(vp); }; return 0; } /* * synthfs_reclaim - Reclaim a vnode so that it can be used for other purposes. * * Locking policy: ignored */ int synthfs_reclaim(ap) struct vnop_reclaim_args /* { struct vnode *a_vp; struct proc *a_p; } */ *ap; { struct vnode *vp = ap->a_vp; struct synthfsnode *sp = VTOS(vp); void *name = sp->s_name; sp->s_name = NULL; FREE(name, M_TEMP); vp->v_data = NULL; FREE((void *)sp, M_SYNTHFS); return (0); }