#include <sys/types.h>
#include <sys/param.h>
#include <sys/sysmacros.h>
#include <sys/systm.h>
#include <sys/kmem.h>
#include <sys/time.h>
#include <sys/pathname.h>
#include <sys/vfs.h>
#include <sys/vfs_opreg.h>
#include <sys/vnode.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <sys/errno.h>
#include <sys/cmn_err.h>
#include <sys/cred.h>
#include <sys/statvfs.h>
#include <sys/mount.h>
#include <sys/debug.h>
#include <sys/modctl.h>
#include <fs/fs_subr.h>
#include <sys/fs/dv_node.h>
#include <sys/fs/snode.h>
#include <sys/sunndi.h>
#include <sys/policy.h>
#include <sys/sunmdi.h>
static int devfs_mount(struct vfs *, struct vnode *, struct mounta *,
struct cred *);
static int devfs_unmount(struct vfs *, int, struct cred *);
static int devfs_root(struct vfs *, struct vnode **);
static int devfs_statvfs(struct vfs *, struct statvfs64 *);
static int devfs_mountroot(struct vfs *, enum whymountroot);
static int devfsinit(int, char *);
static vfsdef_t devfs_vfssw = {
VFSDEF_VERSION,
"devfs",
devfsinit,
0,
NULL
};
static kmutex_t devfs_lock;
static int devfstype;
static dev_t devfsdev;
static struct devfs_data *devfs_mntinfo;
static struct modlfs modlfs = {
&mod_fsops, "devices filesystem", &devfs_vfssw
};
static struct modlinkage modlinkage = {
MODREV_1, (void *)&modlfs, NULL
};
int
_init(void)
{
int e;
mutex_init(&devfs_lock, "devfs lock", MUTEX_DEFAULT, NULL);
dv_node_cache_init();
if ((e = mod_install(&modlinkage)) != 0) {
dv_node_cache_fini();
mutex_destroy(&devfs_lock);
return (e);
}
dcmn_err(("devfs loaded\n"));
return (0);
}
int
_fini(void)
{
return (EBUSY);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
static int
devfsinit(int fstype, char *name)
{
static const fs_operation_def_t devfs_vfsops_template[] = {
VFSNAME_MOUNT, { .vfs_mount = devfs_mount },
VFSNAME_UNMOUNT, { .vfs_unmount = devfs_unmount },
VFSNAME_ROOT, { .vfs_root = devfs_root },
VFSNAME_STATVFS, { .vfs_statvfs = devfs_statvfs },
VFSNAME_SYNC, { .vfs_sync = fs_sync },
VFSNAME_MOUNTROOT, { .vfs_mountroot = devfs_mountroot },
NULL, NULL
};
int error;
int dev;
extern major_t getudev(void);
devfstype = fstype;
error = vfs_setfsops(fstype, devfs_vfsops_template, NULL);
if (error != 0) {
cmn_err(CE_WARN, "devfsinit: bad vfs ops template");
return (error);
}
error = vn_make_ops("dev fs", dv_vnodeops_template, &dv_vnodeops);
if (error != 0) {
(void) vfs_freevfsops_by_type(fstype);
cmn_err(CE_WARN, "devfsinit: bad vnode ops template");
return (error);
}
if ((dev = getudev()) == DDI_MAJOR_T_NONE) {
cmn_err(CE_NOTE, "%s: can't get unique dev", devfs_vfssw.name);
dev = 0;
}
devfsdev = makedevice(dev, 0);
return (0);
}
static int
devfs_mount(struct vfs *vfsp, struct vnode *mvp, struct mounta *uap,
struct cred *cr)
{
struct devfs_data *devfs_data;
struct vnode *avp;
struct dv_node *dv;
struct vattr va;
dcmn_err(("devfs_mount\n"));
if (secpolicy_fs_mount(cr, mvp, vfsp) != 0)
return (EPERM);
if (mvp->v_type != VDIR)
return (ENOTDIR);
ASSERT(uap->flags & MS_SYSSPACE);
avp = mvp;
mutex_enter(&devfs_lock);
ASSERT(devfs_mntinfo == NULL);
dv = dv_mkroot(vfsp, devfsdev);
dv->dv_attrvp = avp;
ASSERT(dv == dv->dv_dotdot);
devfs_data = kmem_zalloc(sizeof (struct devfs_data), KM_SLEEP);
devfs_data->devfs_vfsp = vfsp;
devfs_data->devfs_root = dv;
vfsp->vfs_data = (caddr_t)devfs_data;
vfsp->vfs_fstype = devfstype;
vfsp->vfs_dev = devfsdev;
vfsp->vfs_bsize = DEV_BSIZE;
vfsp->vfs_mtime = ddi_get_time();
vfs_make_fsid(&vfsp->vfs_fsid, vfsp->vfs_dev, devfstype);
devfs_mntinfo = devfs_data;
mutex_exit(&devfs_lock);
va.va_mask = AT_ATIME|AT_MTIME;
gethrestime(&va.va_atime);
gethrestime(&va.va_mtime);
(void) VOP_SETATTR(DVTOV(dv), &va, 0, cr, NULL);
return (0);
}
static int
devfs_unmount(struct vfs *vfsp, int flag, struct cred *cr)
{
return (EBUSY);
}
static int
devfs_root(struct vfs *vfsp, struct vnode **vpp)
{
dcmn_err(("devfs_root\n"));
*vpp = DVTOV(VFSTODVFS(vfsp)->devfs_root);
VN_HOLD(*vpp);
return (0);
}
static int
devfs_statvfs(struct vfs *vfsp, struct statvfs64 *sbp)
{
extern kmem_cache_t *dv_node_cache;
dev32_t d32;
dcmn_err(("devfs_statvfs\n"));
bzero(sbp, sizeof (*sbp));
sbp->f_frsize = sbp->f_bsize = vfsp->vfs_bsize;
sbp->f_files = kmem_cache_stat(dv_node_cache, "alloc");
sbp->f_ffree = 0;
sbp->f_favail = 0;
sbp->f_bfree = 0;
sbp->f_bavail = 0;
sbp->f_blocks = 0;
(void) cmpldev(&d32, vfsp->vfs_dev);
sbp->f_fsid = d32;
(void) strcpy(sbp->f_basetype, vfssw[devfstype].vsw_name);
sbp->f_flag = vf_to_stf(vfsp->vfs_flag);
sbp->f_namemax = MAXNAMELEN - 1;
(void) strcpy(sbp->f_fstr, "devices");
return (0);
}
static int
devfs_mountroot(struct vfs *vfsp, enum whymountroot why)
{
dcmn_err(("devfs_mountroot\n"));
return (EINVAL);
}
struct dv_node *
devfs_dip_to_dvnode(dev_info_t *dip)
{
char *dirpath;
struct vnode *dirvp;
ASSERT(dip != NULL);
if (devfs_mntinfo == NULL)
return (NULL);
dirpath = kmem_alloc(MAXPATHLEN, KM_SLEEP);
(void) ddi_pathname(dip, dirpath);
if (devfs_lookupname(dirpath, NULLVPP, &dirvp)) {
dcmn_err(("directory %s not found\n", dirpath));
kmem_free(dirpath, MAXPATHLEN);
return (NULL);
}
kmem_free(dirpath, MAXPATHLEN);
return (VTODV(dirvp));
}
static int
devfs_clean_vhci(dev_info_t *dip, void *args)
{
struct dv_node *dvp;
uint_t flags = (uint_t)(uintptr_t)args;
(void) tsd_set(devfs_clean_key, (void *)1);
dvp = devfs_dip_to_dvnode(dip);
if (dvp) {
(void) dv_cleandir(dvp, NULL, flags);
VN_RELE(DVTOV(dvp));
}
(void) tsd_set(devfs_clean_key, NULL);
return (DDI_WALK_CONTINUE);
}
int
devfs_clean(dev_info_t *dip, char *devnm, uint_t flags)
{
struct dv_node *dvp;
dcmn_err(("devfs_unconfigure: dip = 0x%p, flags = 0x%x",
(void *)dip, flags));
(void) tsd_set(devfs_clean_key, (void *)1);
dvp = devfs_dip_to_dvnode(dip);
if (dvp == NULL) {
(void) tsd_set(devfs_clean_key, NULL);
return (0);
}
(void) dv_cleandir(dvp, devnm, flags);
(void) tsd_set(devfs_clean_key, NULL);
VN_RELE(DVTOV(dvp));
if ((flags & DV_CLEAN_FORCE) && (dip != ddi_root_node()) &&
(mdi_component_is_vhci(dip, NULL) != MDI_SUCCESS)) {
mdi_walk_vhcis(devfs_clean_vhci, (void *)(uintptr_t)flags);
}
return (0);
}
int
devfs_lookupname(
char *pathname,
vnode_t **dirvpp,
vnode_t **compvpp)
{
struct pathname pn;
int error;
ASSERT(devicesdir);
ASSERT(pathname);
if (error = pn_get(pathname, UIO_SYSSPACE, &pn))
return (error);
pn_skipslash(&pn);
if (pn_pathleft(&pn) == 0) {
if (dirvpp)
*dirvpp = NULL;
if (compvpp) {
VN_HOLD(devicesdir);
*compvpp = devicesdir;
}
} else {
VN_HOLD(devicesdir);
VN_HOLD(devicesdir);
error = lookuppnvp(&pn, NULL, FOLLOW, dirvpp, compvpp,
devicesdir, devicesdir, kcred);
}
pn_free(&pn);
return (error);
}
int
devfs_walk(
char *path,
void (*callback)(struct dv_node *, void *),
void *arg)
{
char *dirpath, *devnm;
struct vnode *dirvp;
ASSERT(path && callback);
if (*path != '/' || devfs_mntinfo == NULL)
return (ENXIO);
dcmn_err(("devfs_walk: path = %s", path));
dirpath = kmem_alloc(MAXPATHLEN, KM_SLEEP);
(void) snprintf(dirpath, MAXPATHLEN, "/devices%s", path);
devnm = strrchr(dirpath, '/');
ASSERT(devnm);
*devnm++ = '\0';
if (lookupname(dirpath, UIO_SYSSPACE, 0, NULL, &dirvp)) {
dcmn_err(("directory %s not found\n", dirpath));
kmem_free(dirpath, MAXPATHLEN);
return (ENXIO);
}
if (*devnm == '\0') {
callback(VTODV(dirvp), arg);
devnm = NULL;
}
dv_walk(VTODV(dirvp), devnm, callback, arg);
VN_RELE(dirvp);
kmem_free(dirpath, MAXPATHLEN);
return (0);
}
int
devfs_devpolicy(vnode_t *vp, devplcy_t **dpp)
{
struct vnode *rvp;
struct dv_node *dvp;
int rval = -1;
if (devfs_mntinfo == NULL)
return (rval);
if (VOP_REALVP(vp, &rvp, NULL) == 0 && vn_matchops(rvp, dv_vnodeops)) {
dvp = VTODV(rvp);
rw_enter(&dvp->dv_contents, RW_READER);
if (dvp->dv_priv) {
dphold(dvp->dv_priv);
*dpp = dvp->dv_priv;
rval = 0;
}
rw_exit(&dvp->dv_contents);
}
return (rval);
}