/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include ldi_ident_t zut_li = NULL; dev_info_t *zut_dip; static int zut_open_dir(char *path, vnode_t *startvp, cred_t *cr, int flags, pathname_t *realpn, vnode_t **dvn) { pathname_t pn; vnode_t *vp; vnode_t *rootvp; proc_t *p = curproc; int error; pn_alloc(&pn); (void) strlcpy(pn.pn_buf, path, MAXPATHLEN); pn.pn_pathlen = strlen(path); mutex_enter(&p->p_lock); /* for u_rdir and u_cdir */ if ((rootvp = PTOU(p)->u_rdir) == NULL) rootvp = rootdir; else if (rootvp != rootdir) /* no need to VN_HOLD rootdir */ VN_HOLD(rootvp); if (pn.pn_path[0] == '/') { vp = rootvp; } else { vp = (startvp == NULL) ? PTOU(p)->u_cdir : startvp; } VN_HOLD(vp); mutex_exit(&p->p_lock); /* * Skip over leading slashes */ while (pn.pn_path[0] == '/') { pn.pn_path++; pn.pn_pathlen--; } error = lookuppnvp(&pn, realpn, flags | FOLLOW, NULL, dvn, rootvp, vp, cr); /* * If we lack read access to the directory, we should error out. */ if (!error) { if (vfs_has_feature((*dvn)->v_vfsp, VFSFT_ACEMASKONACCESS)) { error = VOP_ACCESS(*dvn, ACE_LIST_DIRECTORY, V_ACE_MASK, cr, NULL); } else { error = VOP_ACCESS(*dvn, VREAD, 0, cr, NULL); } } pn_free(&pn); return (error); } static int zut_readdir(intptr_t arg, cred_t *cr, int iflag, int *rvalp) { zut_readdir_t *zr; struct iovec aiov; struct uio auio; vnode_t *dvn = NULL; vnode_t *fvn = NULL; char *kbuf; int flags = 0; int error, rc; zr = kmem_zalloc(sizeof (zut_readdir_t), KM_SLEEP); error = ddi_copyin((void *)arg, zr, sizeof (zut_readdir_t), iflag); if (error) goto zutr_bail; kbuf = kmem_zalloc(zr->zr_buflen, KM_SLEEP); zr->zr_retcode = zut_open_dir(zr->zr_dir, NULL, cr, flags, NULL, &dvn); if (zr->zr_retcode) goto zutr_done; if (zr->zr_reqflags & ZUT_XATTR) { vattr_t vattr; zr->zr_retcode = VOP_LOOKUP(dvn, zr->zr_file, &fvn, NULL, flags, NULL, cr, NULL, NULL, NULL); VN_RELE(dvn); dvn = NULL; if (zr->zr_retcode) goto zutr_done; /* * In order to access hidden attribute directory the * user must have appropriate read access and be able * to stat() the file */ if (vfs_has_feature(fvn->v_vfsp, VFSFT_ACEMASKONACCESS)) { zr->zr_retcode = VOP_ACCESS(fvn, ACE_READ_NAMED_ATTRS, V_ACE_MASK, cr, NULL); } else { zr->zr_retcode = VOP_ACCESS(fvn, VREAD, 0, cr, NULL); } if (zr->zr_retcode) goto zutr_done; vattr.va_mask = AT_ALL; zr->zr_retcode = VOP_GETATTR(fvn, &vattr, 0, cr, NULL); if (zr->zr_retcode) goto zutr_done; zr->zr_retcode = VOP_LOOKUP(fvn, "", &dvn, NULL, flags | LOOKUP_XATTR, NULL, cr, NULL, NULL, NULL); VN_RELE(fvn); if (zr->zr_retcode) goto zutr_done; } aiov.iov_base = kbuf; aiov.iov_len = zr->zr_buflen; auio.uio_iov = &aiov; auio.uio_iovcnt = 1; auio.uio_loffset = zr->zr_loffset; auio.uio_segflg = UIO_SYSSPACE; auio.uio_resid = zr->zr_buflen; auio.uio_fmode = 0; auio.uio_extflg = UIO_COPY_CACHED; if (zr->zr_reqflags & ZUT_EXTRDDIR) flags |= V_RDDIR_ENTFLAGS; if (zr->zr_reqflags & ZUT_ACCFILTER) flags |= V_RDDIR_ACCFILTER; (void) VOP_RWLOCK(dvn, V_WRITELOCK_FALSE, NULL); zr->zr_retcode = VOP_READDIR(dvn, &auio, cr, &zr->zr_eof, NULL, flags); VOP_RWUNLOCK(dvn, V_WRITELOCK_FALSE, NULL); VN_RELE(dvn); zr->zr_bytes = aiov.iov_base - kbuf; zr->zr_loffset = auio.uio_loffset; error = ddi_copyout(kbuf, (void *)(uintptr_t)zr->zr_buf, zr->zr_buflen, iflag); zutr_done: kmem_free(kbuf, zr->zr_buflen); rc = ddi_copyout(zr, (void *)arg, sizeof (zut_readdir_t), iflag); if (error == 0) error = rc; zutr_bail: kmem_free(zr, sizeof (zut_readdir_t)); if (rvalp) *rvalp = error; return (error); } static int zut_stat64(vnode_t *vp, struct stat64 *sb, uint64_t *xvs, int flag, cred_t *cr) { xoptattr_t *xoap = NULL; xvattr_t xv = { 0 }; int error; xva_init(&xv); XVA_SET_REQ(&xv, XAT_ARCHIVE); XVA_SET_REQ(&xv, XAT_SYSTEM); XVA_SET_REQ(&xv, XAT_READONLY); XVA_SET_REQ(&xv, XAT_HIDDEN); XVA_SET_REQ(&xv, XAT_NOUNLINK); XVA_SET_REQ(&xv, XAT_IMMUTABLE); XVA_SET_REQ(&xv, XAT_APPENDONLY); XVA_SET_REQ(&xv, XAT_NODUMP); XVA_SET_REQ(&xv, XAT_OPAQUE); XVA_SET_REQ(&xv, XAT_AV_QUARANTINED); XVA_SET_REQ(&xv, XAT_AV_MODIFIED); XVA_SET_REQ(&xv, XAT_REPARSE); XVA_SET_REQ(&xv, XAT_OFFLINE); XVA_SET_REQ(&xv, XAT_SPARSE); xv.xva_vattr.va_mask |= AT_STAT | AT_NBLOCKS | AT_BLKSIZE | AT_SIZE; if (error = VOP_GETATTR(vp, &xv.xva_vattr, flag, cr, NULL)) return (error); bzero(sb, sizeof (sb)); sb->st_dev = xv.xva_vattr.va_fsid; sb->st_ino = xv.xva_vattr.va_nodeid; sb->st_mode = VTTOIF(xv.xva_vattr.va_type) | xv.xva_vattr.va_mode; sb->st_nlink = xv.xva_vattr.va_nlink; sb->st_uid = xv.xva_vattr.va_uid; sb->st_gid = xv.xva_vattr.va_gid; sb->st_rdev = xv.xva_vattr.va_rdev; sb->st_size = xv.xva_vattr.va_size; sb->st_atim = xv.xva_vattr.va_atime; sb->st_mtim = xv.xva_vattr.va_mtime; sb->st_ctim = xv.xva_vattr.va_ctime; sb->st_blksize = xv.xva_vattr.va_blksize; sb->st_blocks = xv.xva_vattr.va_nblocks; sb->st_fstype[0] = 0; if ((xoap = xva_getxoptattr(&xv)) == NULL) return (0); if (XVA_ISSET_RTN(&xv, XAT_ARCHIVE) && xoap->xoa_archive) *xvs |= (1 << F_ARCHIVE); if (XVA_ISSET_RTN(&xv, XAT_SYSTEM) && xoap->xoa_system) *xvs |= (1 << F_SYSTEM); if (XVA_ISSET_RTN(&xv, XAT_READONLY) && xoap->xoa_readonly) *xvs |= (1 << F_READONLY); if (XVA_ISSET_RTN(&xv, XAT_HIDDEN) && xoap->xoa_hidden) *xvs |= (1 << F_HIDDEN); if (XVA_ISSET_RTN(&xv, XAT_NOUNLINK) && xoap->xoa_nounlink) *xvs |= (1 << F_NOUNLINK); if (XVA_ISSET_RTN(&xv, XAT_IMMUTABLE) && xoap->xoa_immutable) *xvs |= (1 << F_IMMUTABLE); if (XVA_ISSET_RTN(&xv, XAT_APPENDONLY) && xoap->xoa_appendonly) *xvs |= (1 << F_APPENDONLY); if (XVA_ISSET_RTN(&xv, XAT_NODUMP) && xoap->xoa_nodump) *xvs |= (1 << F_NODUMP); if (XVA_ISSET_RTN(&xv, XAT_OPAQUE) && xoap->xoa_opaque) *xvs |= (1 << F_OPAQUE); if (XVA_ISSET_RTN(&xv, XAT_AV_QUARANTINED) && xoap->xoa_av_quarantined) *xvs |= (1 << F_AV_QUARANTINED); if (XVA_ISSET_RTN(&xv, XAT_AV_MODIFIED) && xoap->xoa_av_modified) *xvs |= (1 << F_AV_MODIFIED); if (XVA_ISSET_RTN(&xv, XAT_REPARSE) && xoap->xoa_reparse) *xvs |= (1 << F_REPARSE); if (XVA_ISSET_RTN(&xv, XAT_OFFLINE) && xoap->xoa_offline) *xvs |= (1 << F_OFFLINE); if (XVA_ISSET_RTN(&xv, XAT_SPARSE) && xoap->xoa_sparse) *xvs |= (1 << F_SPARSE); return (0); } /*ARGSUSED*/ static int zut_lookup(intptr_t arg, cred_t *cr, int iflag, int *rvalp) { zut_lookup_t *zl; pathname_t rpn; vnode_t *dvn = NULL; vnode_t *fvn = NULL; vnode_t *xdvn = NULL; vnode_t *xfvn = NULL; vnode_t *release = NULL; int flags = 0; int error, rc; zl = kmem_zalloc(sizeof (zut_lookup_t), KM_SLEEP); error = ddi_copyin((void *)arg, zl, sizeof (zut_lookup_t), iflag); if (error) goto zutl_bail; pn_alloc(&rpn); bzero(rpn.pn_buf, MAXPATHLEN); zl->zl_retcode = zut_open_dir(zl->zl_dir, NULL, cr, flags, &rpn, &dvn); if (zl->zl_retcode) goto zutl_done; if (zl->zl_reqflags & ZUT_IGNORECASE) flags |= FIGNORECASE; zl->zl_retcode = VOP_LOOKUP(dvn, zl->zl_file, &fvn, NULL, flags, NULL, cr, NULL, &zl->zl_deflags, &rpn); if (zl->zl_retcode) goto zutl_done; release = fvn; if (zl->zl_reqflags & ZUT_XATTR) { vattr_t vattr; /* * In order to access hidden attribute directory the * user must have appropriate read access and be able * to stat() the file */ if (vfs_has_feature(fvn->v_vfsp, VFSFT_ACEMASKONACCESS)) { zl->zl_retcode = VOP_ACCESS(fvn, ACE_READ_NAMED_ATTRS, V_ACE_MASK, cr, NULL); } else { zl->zl_retcode = VOP_ACCESS(fvn, VREAD, 0, cr, NULL); } if (zl->zl_retcode) goto zutl_done; vattr.va_mask = AT_ALL; zl->zl_retcode = VOP_GETATTR(fvn, &vattr, 0, cr, NULL); if (zl->zl_retcode) goto zutl_done; zl->zl_retcode = VOP_LOOKUP(fvn, "", &xdvn, NULL, flags | LOOKUP_XATTR, NULL, cr, NULL, NULL, NULL); if (zl->zl_retcode) goto zutl_done; VN_RELE(fvn); release = xdvn; zl->zl_retcode = VOP_LOOKUP(xdvn, zl->zl_xfile, &xfvn, NULL, flags, NULL, cr, NULL, &zl->zl_deflags, &rpn); if (zl->zl_retcode) goto zutl_done; VN_RELE(xdvn); release = xfvn; } if (zl->zl_reqflags & ZUT_GETSTAT) { zl->zl_retcode = zut_stat64(release, &zl->zl_statbuf, &zl->zl_xvattrs, 0, cr); } zutl_done: (void) strlcpy(zl->zl_real, rpn.pn_path, MAXPATHLEN); rc = ddi_copyout(zl, (void *)arg, sizeof (zut_lookup_t), iflag); if (error == 0) error = rc; if (release) VN_RELE(release); if (dvn) VN_RELE(dvn); pn_free(&rpn); zutl_bail: kmem_free(zl, sizeof (zut_lookup_t)); if (rvalp) *rvalp = error; return (error); } /*ARGSUSED*/ static int zut_ioctl(dev_t dev, int cmd, intptr_t arg, int flag, cred_t *cr, int *rvalp) { int error; if (getminor(dev) != 0) return (ENXIO); if (cmd <= ZUT_IOC_MIN_CMD || cmd >= ZUT_IOC_MAX_CMD) return (EINVAL); switch (cmd) { case ZUT_IOC_LOOKUP: error = zut_lookup(arg, cr, flag, rvalp); break; case ZUT_IOC_READDIR: error = zut_readdir(arg, cr, flag, rvalp); default: break; } return (error); } static int zut_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { if (cmd != DDI_ATTACH) return (DDI_FAILURE); if (ddi_create_minor_node(dip, "zut", S_IFCHR, 0, DDI_PSEUDO, 0) == DDI_FAILURE) return (DDI_FAILURE); zut_dip = dip; ddi_report_dev(dip); return (DDI_SUCCESS); } static int zut_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { if (cmd != DDI_DETACH) return (DDI_FAILURE); zut_dip = NULL; ddi_prop_remove_all(dip); ddi_remove_minor_node(dip, NULL); return (DDI_SUCCESS); } /*ARGSUSED*/ static int zut_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) { switch (infocmd) { case DDI_INFO_DEVT2DEVINFO: *result = zut_dip; return (DDI_SUCCESS); case DDI_INFO_DEVT2INSTANCE: *result = (void *)0; return (DDI_SUCCESS); } return (DDI_FAILURE); } /*ARGSUSED*/ int zut_open(dev_t *devp, int flag, int otyp, cred_t *cr) { minor_t minor = getminor(*devp); if (minor == 0) /* This is the control device */ return (0); return (ENXIO); } /*ARGSUSED*/ int zut_close(dev_t dev, int flag, int otyp, cred_t *cr) { minor_t minor = getminor(dev); if (minor == 0) /* This is the control device */ return (0); return (ENXIO); } /* * /dev/zut is the control node, i.e. minor 0. * * There are no other minor nodes, and /dev/zut basically does nothing * other than serve up ioctls. */ static struct cb_ops zut_cb_ops = { zut_open, /* open */ zut_close, /* close */ nodev, /* strategy */ nodev, /* print */ nodev, /* dump */ nodev, /* read */ nodev, /* write */ zut_ioctl, /* ioctl */ nodev, /* devmap */ nodev, /* mmap */ nodev, /* segmap */ nochpoll, /* poll */ ddi_prop_op, /* prop_op */ NULL, /* streamtab */ D_NEW | D_MP | D_64BIT, /* Driver compatibility flag */ CB_REV, /* version */ nodev, /* async read */ nodev, /* async write */ }; static struct dev_ops zut_dev_ops = { DEVO_REV, /* version */ 0, /* refcnt */ zut_info, /* info */ nulldev, /* identify */ nulldev, /* probe */ zut_attach, /* attach */ zut_detach, /* detach */ nodev, /* reset */ &zut_cb_ops, /* driver operations */ NULL /* no bus operations */ }; static struct modldrv zut_modldrv = { &mod_driverops, "ZFS unit test " ZUT_VERSION_STRING, &zut_dev_ops }; static struct modlinkage modlinkage = { MODREV_1, (void *)&zut_modldrv, NULL }; int _init(void) { int error; if ((error = mod_install(&modlinkage)) != 0) { return (error); } error = ldi_ident_from_mod(&modlinkage, &zut_li); ASSERT(error == 0); return (0); } int _fini(void) { int error; if ((error = mod_remove(&modlinkage)) != 0) return (error); ldi_ident_release(zut_li); zut_li = NULL; return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); }