#include <sys/types.h>
#include <sys/param.h>
#include <sys/sysmacros.h>
#include <sys/sunndi.h>
#include <fs/fs_subr.h>
#include <sys/fs/dv_node.h>
#include <sys/fs/sdev_impl.h>
#include <sys/policy.h>
#include <sys/zone.h>
#include <sys/dls.h>
struct vnodeops *devnet_vnodeops;
int
devnet_validate(struct sdev_node *dv)
{
datalink_id_t linkid;
zoneid_t zoneid;
ASSERT(dv->sdev_state == SDEV_READY);
if (dls_mgmt_get_linkid(dv->sdev_name, &linkid) != 0)
return (SDEV_VTOR_INVALID);
if (SDEV_IS_GLOBAL(dv))
return (SDEV_VTOR_VALID);
zoneid = getzoneid();
return (zone_check_datalink(&zoneid, linkid) == 0 ?
SDEV_VTOR_VALID : SDEV_VTOR_INVALID);
}
static int
devnet_create_rvp(const char *nm, struct vattr *vap, dls_dl_handle_t *ddhp)
{
timestruc_t now;
dev_t dev;
int error;
if ((error = dls_devnet_open(nm, ddhp, &dev)) != 0) {
sdcmn_err12(("devnet_create_rvp: not a valid vanity name "
"network node: %s\n", nm));
return (error);
}
*vap = sdev_vattr_chr;
vap->va_mode |= 0666;
vap->va_rdev = dev;
gethrestime(&now);
vap->va_atime = now;
vap->va_mtime = now;
vap->va_ctime = now;
return (0);
}
static int
devnet_lookup(struct vnode *dvp, char *nm, struct vnode **vpp,
struct pathname *pnp, int flags, struct vnode *rdir, struct cred *cred,
caller_context_t *ct, int *direntflags, pathname_t *realpnp)
{
struct sdev_node *ddv = VTOSDEV(dvp);
struct sdev_node *dv = NULL;
dls_dl_handle_t ddh = NULL;
struct vattr vattr;
int nmlen;
int error = ENOENT;
if (SDEVTOV(ddv)->v_type != VDIR)
return (ENOTDIR);
nmlen = strlen(nm);
if ((nmlen == 0) || ((nmlen == 1) && (nm[0] == '.'))) {
*vpp = SDEVTOV(ddv);
VN_HOLD(*vpp);
return (0);
}
if ((nmlen == 2) && (strcmp(nm, "..") == 0)) {
*vpp = SDEVTOV(ddv->sdev_dotdot);
VN_HOLD(*vpp);
return (0);
}
rw_enter(&ddv->sdev_contents, RW_WRITER);
if ((dv = sdev_cache_lookup(ddv, nm)) != NULL) {
ASSERT(dv->sdev_state == SDEV_READY);
if (!(dv->sdev_flags & SDEV_ATTR_INVALID))
goto found;
}
if (ddv->sdev_state == SDEV_ZOMBIE)
goto failed;
error = devnet_create_rvp(nm, &vattr, &ddh);
if (error != 0)
goto failed;
error = sdev_mknode(ddv, nm, &dv, &vattr, NULL, NULL, cred, SDEV_READY);
if (error != 0) {
dls_devnet_close(ddh);
goto failed;
}
ASSERT(dv != NULL);
rw_enter(&dv->sdev_contents, RW_WRITER);
if (dv->sdev_flags & SDEV_ATTR_INVALID) {
SDEVTOV(dv)->v_rdev = vattr.va_rdev;
ASSERT(dv->sdev_attr != NULL);
dv->sdev_attr->va_rdev = vattr.va_rdev;
dv->sdev_flags &= ~SDEV_ATTR_INVALID;
}
ASSERT(dv->sdev_private == NULL);
dv->sdev_private = ddh;
rw_exit(&dv->sdev_contents);
found:
ASSERT(SDEV_HELD(dv));
rw_exit(&ddv->sdev_contents);
return (sdev_to_vp(dv, vpp));
failed:
rw_exit(&ddv->sdev_contents);
if (dv != NULL)
SDEV_RELE(dv);
*vpp = NULL;
return (error);
}
static int
devnet_filldir_datalink(datalink_id_t linkid, void *arg)
{
struct sdev_node *ddv = arg;
struct vattr vattr;
struct sdev_node *dv;
dls_dl_handle_t ddh = NULL;
char link[MAXLINKNAMELEN];
ASSERT(RW_WRITE_HELD(&ddv->sdev_contents));
if (dls_mgmt_get_linkinfo(linkid, link, NULL, NULL, NULL) != 0)
return (0);
if ((dv = sdev_cache_lookup(ddv, (char *)link)) != NULL)
goto found;
if (devnet_create_rvp(link, &vattr, &ddh) != 0)
return (0);
ASSERT(ddh != NULL);
dls_devnet_close(ddh);
if (sdev_mknode(ddv, (char *)link, &dv, &vattr, NULL, NULL, kcred,
SDEV_READY) != 0) {
return (0);
}
rw_enter(&dv->sdev_contents, RW_WRITER);
dv->sdev_flags |= SDEV_ATTR_INVALID;
rw_exit(&dv->sdev_contents);
found:
SDEV_SIMPLE_RELE(dv);
return (0);
}
static void
devnet_filldir(struct sdev_node *ddv)
{
sdev_node_t *dv, *next;
datalink_id_t linkid;
ASSERT(RW_READ_HELD(&ddv->sdev_contents));
if (rw_tryupgrade(&ddv->sdev_contents) == 0) {
rw_exit(&ddv->sdev_contents);
rw_enter(&ddv->sdev_contents, RW_WRITER);
}
for (dv = SDEV_FIRST_ENTRY(ddv); dv; dv = next) {
next = SDEV_NEXT_ENTRY(ddv, dv);
if (dv->sdev_state != SDEV_READY)
continue;
switch (devnet_validate(dv)) {
case SDEV_VTOR_VALID:
case SDEV_VTOR_SKIP:
continue;
case SDEV_VTOR_INVALID:
case SDEV_VTOR_STALE:
sdcmn_err12(("devnet_filldir: destroy invalid "
"node: %s(%p)\n", dv->sdev_name, (void *)dv));
break;
}
if (SDEVTOV(dv)->v_count > 0)
continue;
SDEV_HOLD(dv);
(void) sdev_cache_update(ddv, &dv, dv->sdev_name,
SDEV_CACHE_DELETE);
SDEV_RELE(dv);
}
if (((ddv->sdev_flags & SDEV_BUILD) == 0) && !dls_devnet_rebuild())
goto done;
if (SDEV_IS_GLOBAL(ddv)) {
linkid = DATALINK_INVALID_LINKID;
do {
linkid = dls_mgmt_get_next(linkid, DATALINK_CLASS_ALL,
DATALINK_ANY_MEDIATYPE, DLMGMT_ACTIVE);
if (linkid != DATALINK_INVALID_LINKID)
(void) devnet_filldir_datalink(linkid, ddv);
} while (linkid != DATALINK_INVALID_LINKID);
} else {
(void) zone_datalink_walk(getzoneid(),
devnet_filldir_datalink, ddv);
}
ddv->sdev_flags &= ~SDEV_BUILD;
done:
rw_downgrade(&ddv->sdev_contents);
}
static int
devnet_readdir(struct vnode *dvp, struct uio *uiop, struct cred *cred,
int *eofp, caller_context_t *ct, int flags)
{
struct sdev_node *sdvp = VTOSDEV(dvp);
ASSERT(sdvp);
if (uiop->uio_offset == 0)
devnet_filldir(sdvp);
return (devname_readdir_func(dvp, uiop, cred, eofp, 0));
}
static void
devnet_inactive_callback(struct vnode *dvp)
{
struct sdev_node *sdvp = VTOSDEV(dvp);
dls_dl_handle_t ddh;
if (dvp->v_type == VDIR)
return;
ASSERT(dvp->v_type == VCHR);
rw_enter(&sdvp->sdev_contents, RW_WRITER);
ddh = sdvp->sdev_private;
sdvp->sdev_private = NULL;
sdvp->sdev_flags |= SDEV_ATTR_INVALID;
rw_exit(&sdvp->sdev_contents);
if (ddh != NULL)
dls_devnet_close(ddh);
}
static void
devnet_inactive(struct vnode *dvp, struct cred *cred, caller_context_t *ct)
{
devname_inactive_func(dvp, cred, devnet_inactive_callback);
}
const fs_operation_def_t devnet_vnodeops_tbl[] = {
VOPNAME_READDIR, { .vop_readdir = devnet_readdir },
VOPNAME_LOOKUP, { .vop_lookup = devnet_lookup },
VOPNAME_INACTIVE, { .vop_inactive = devnet_inactive },
VOPNAME_CREATE, { .error = fs_nosys },
VOPNAME_REMOVE, { .error = fs_nosys },
VOPNAME_MKDIR, { .error = fs_nosys },
VOPNAME_RMDIR, { .error = fs_nosys },
VOPNAME_SYMLINK, { .error = fs_nosys },
VOPNAME_SETSECATTR, { .error = fs_nosys },
NULL, NULL
};