1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
  23  * Use is subject to license terms.
  24  */
  25 
  26 /*
  27  * vnode ops for the /dev/ipnet directory
  28  *      The lookup is based on the ipnetif nodes held
  29  *      in the ipnet module. We also override readdir
  30  *      in order to delete ipnet nodes no longer in use.
  31  */
  32 
  33 #include <sys/types.h>
  34 #include <sys/param.h>
  35 #include <sys/sysmacros.h>
  36 #include <sys/sunndi.h>
  37 #include <fs/fs_subr.h>
  38 #include <sys/fs/dv_node.h>
  39 #include <sys/fs/sdev_impl.h>
  40 #include <sys/policy.h>
  41 #include <inet/ipnet.h>
  42 #include <sys/zone.h>
  43 
  44 struct vnodeops         *devipnet_vnodeops;
  45 
  46 static void
  47 devipnet_fill_vattr(struct vattr *vap, dev_t dev)
  48 {
  49         timestruc_t now;
  50 
  51         *vap = sdev_vattr_chr;
  52         vap->va_rdev = dev;
  53         vap->va_mode |= 0666;
  54 
  55         gethrestime(&now);
  56         vap->va_atime = now;
  57         vap->va_mtime = now;
  58         vap->va_ctime = now;
  59 }
  60 
  61 /*
  62  * Check if an ipnet sdev_node is still valid.
  63  */
  64 int
  65 devipnet_validate(struct sdev_node *dv)
  66 {
  67         dev_t   dev;
  68 
  69         dev = ipnet_if_getdev(dv->sdev_name, getzoneid());
  70         if (dev == (dev_t)-1)
  71                 return (SDEV_VTOR_INVALID);
  72         if (getminor(SDEVTOV(dv)->v_rdev) != getminor(dev))
  73                 return (SDEV_VTOR_STALE);
  74         return (SDEV_VTOR_VALID);
  75 }
  76 
  77 /*
  78  * This callback is invoked from devname_lookup_func() to create
  79  * an ipnet entry when the node is not found in the cache.
  80  */
  81 /*ARGSUSED*/
  82 static int
  83 devipnet_create_rvp(struct sdev_node *ddv, char *nm,
  84     void **arg, cred_t *cred, void *whatever, char *whichever)
  85 {
  86         dev_t           dev;
  87         struct vattr    *vap = (struct vattr *)arg;
  88         int             err = 0;
  89 
  90         if ((dev = ipnet_if_getdev(nm, getzoneid())) == (dev_t)-1)
  91                 err = ENOENT;
  92         else
  93                 devipnet_fill_vattr(vap, dev);
  94 
  95         return (err);
  96 }
  97 
  98 /*
  99  * Lookup for /dev/ipnet directory
 100  *      If the entry does not exist, the devipnet_create_rvp() callback
 101  *      is invoked to create it. Nodes do not persist across reboot.
 102  */
 103 /*ARGSUSED3*/
 104 static int
 105 devipnet_lookup(struct vnode *dvp, char *nm, struct vnode **vpp,
 106     struct pathname *pnp, int flags, struct vnode *rdir, struct cred *cred,
 107     caller_context_t *ct, int *direntflags, pathname_t *realpnp)
 108 {
 109         struct sdev_node *sdvp = VTOSDEV(dvp);
 110         struct sdev_node *dv;
 111         struct vnode *rvp = NULL;
 112         int error;
 113 
 114         error = devname_lookup_func(sdvp, nm, vpp, cred, devipnet_create_rvp,
 115             SDEV_VATTR);
 116 
 117         if (error == 0) {
 118                 switch ((*vpp)->v_type) {
 119                 case VCHR:
 120                         dv = VTOSDEV(VTOS(*vpp)->s_realvp);
 121                         ASSERT(VOP_REALVP(SDEVTOV(dv), &rvp, NULL) == ENOSYS);
 122                         break;
 123                 case VDIR:
 124                         dv = VTOSDEV(*vpp);
 125                         break;
 126                 default:
 127                         cmn_err(CE_PANIC, "devipnet_lookup: Unsupported node "
 128                             "type: %p: %d", (void *)(*vpp), (*vpp)->v_type);
 129                         break;
 130                 }
 131                 ASSERT(SDEV_HELD(dv));
 132         }
 133 
 134         return (error);
 135 }
 136 
 137 static void
 138 devipnet_filldir_entry(const char *name, void *arg, dev_t dev)
 139 {
 140         struct sdev_node *ddv = arg;
 141         struct vattr vattr;
 142         struct sdev_node *dv;
 143 
 144         ASSERT(RW_WRITE_HELD(&ddv->sdev_contents));
 145 
 146         if ((dv = sdev_cache_lookup(ddv, (char *)name)) == NULL) {
 147                 devipnet_fill_vattr(&vattr, dev);
 148                 if (sdev_mknode(ddv, (char *)name, &dv, &vattr, NULL, NULL,
 149                     kcred, SDEV_READY) != 0)
 150                         return;
 151         }
 152         SDEV_SIMPLE_RELE(dv);
 153 }
 154 
 155 static void
 156 devipnet_filldir(struct sdev_node *ddv)
 157 {
 158         sdev_node_t     *dv, *next;
 159 
 160         ASSERT(RW_READ_HELD(&ddv->sdev_contents));
 161         if (rw_tryupgrade(&ddv->sdev_contents) == NULL) {
 162                 rw_exit(&ddv->sdev_contents);
 163                 rw_enter(&ddv->sdev_contents, RW_WRITER);
 164                 /*
 165                  * We've been made a zombie while we weren't looking. We'll bail
 166                  * if that's the case.
 167                  */
 168                 if (ddv->sdev_state == SDEV_ZOMBIE) {
 169                         rw_exit(&ddv->sdev_contents);
 170                         return;
 171                 }
 172         }
 173 
 174         for (dv = SDEV_FIRST_ENTRY(ddv); dv; dv = next) {
 175                 next = SDEV_NEXT_ENTRY(ddv, dv);
 176 
 177                 /* validate and prune only ready nodes */
 178                 if (dv->sdev_state != SDEV_READY)
 179                         continue;
 180                 switch (devipnet_validate(dv)) {
 181                 case SDEV_VTOR_VALID:
 182                 case SDEV_VTOR_SKIP:
 183                         continue;
 184                 case SDEV_VTOR_INVALID:
 185                 case SDEV_VTOR_STALE:
 186                         sdcmn_err12(("devipnet_filldir: destroy invalid "
 187                             "node: %s(%p)\n", dv->sdev_name, (void *)dv));
 188                         break;
 189                 }
 190 
 191                 if (SDEVTOV(dv)->v_count > 0)
 192                         continue;
 193                 SDEV_HOLD(dv);
 194                 /* remove the cache node */
 195                 (void) sdev_cache_update(ddv, &dv, dv->sdev_name,
 196                     SDEV_CACHE_DELETE);
 197                 SDEV_RELE(dv);
 198         }
 199 
 200         ipnet_walk_if(devipnet_filldir_entry, ddv, getzoneid());
 201 
 202         rw_downgrade(&ddv->sdev_contents);
 203 }
 204 
 205 /*
 206  * Display all instantiated ipnet device nodes.
 207  */
 208 /* ARGSUSED */
 209 static int
 210 devipnet_readdir(struct vnode *dvp, struct uio *uiop, struct cred *cred,
 211     int *eofp, caller_context_t *ct, int flags)
 212 {
 213         struct sdev_node *sdvp = VTOSDEV(dvp);
 214 
 215         if (uiop->uio_offset == 0)
 216                 devipnet_filldir(sdvp);
 217 
 218         return (devname_readdir_func(dvp, uiop, cred, eofp, 0));
 219 }
 220 
 221 /*
 222  * We override lookup and readdir to build entries based on the
 223  * in kernel ipnet table.
 224  */
 225 const fs_operation_def_t devipnet_vnodeops_tbl[] = {
 226         { VOPNAME_READDIR,      { .vop_readdir = devipnet_readdir } },
 227         { VOPNAME_LOOKUP,       { .vop_lookup = devipnet_lookup } },
 228         { VOPNAME_CREATE,       { .error = fs_nosys } },
 229         { VOPNAME_REMOVE,       { .error = fs_nosys } },
 230         { VOPNAME_MKDIR,        { .error = fs_nosys } },
 231         { VOPNAME_RMDIR,        { .error = fs_nosys } },
 232         { VOPNAME_SYMLINK,      { .error = fs_nosys } },
 233         { VOPNAME_SETSECATTR,   { .error = fs_nosys } },
 234         { NULL,                 { NULL } }
 235 };