#include <sys/file.h>
#include <sys/stat.h>
#include <sys/atomic.h>
#include <sys/mntio.h>
#include <sys/mnttab.h>
#include <sys/mount.h>
#include <sys/sunddi.h>
#include <sys/sysmacros.h>
#include <sys/systm.h>
#include <sys/vfs.h>
#include <sys/vfs_opreg.h>
#include <sys/fs/mntdata.h>
#include <fs/fs_subr.h>
#include <sys/vmsystm.h>
#include <vm/seg_vn.h>
#include <sys/time.h>
#include <sys/ksynch.h>
#include <sys/sdt.h>
#define MNTROOTINO 2
static mntnode_t *mntgetnode(vnode_t *);
vnodeops_t *mntvnodeops;
extern void vfs_mnttab_readop(void);
int mntfs_enabledev = 1;
extern void vfs_mono_time(timespec_t *);
enum { MNTFS_FIRST, MNTFS_SECOND, MNTFS_NEITHER };
#define MNTFS_REAL_FIELD(x) (*(x) != '-' || *((x) + 1) != '\t')
static int
mntfs_devsize(struct vfs *vfsp)
{
dev32_t odev;
(void) cmpldev(&odev, vfsp->vfs_dev);
return (snprintf(NULL, 0, "dev=%x", odev));
}
static int
mntfs_devprint(struct vfs *vfsp, char *buf)
{
dev32_t odev;
(void) cmpldev(&odev, vfsp->vfs_dev);
return (snprintf(buf, MAX_MNTOPT_STR, "dev=%x", odev));
}
static int
mntfs_newest(timespec_t *a, timespec_t *b)
{
if (a->tv_sec == b->tv_sec &&
a->tv_nsec == b->tv_nsec) {
return (MNTFS_NEITHER);
} else if (b->tv_sec > a->tv_sec ||
(b->tv_sec == a->tv_sec &&
b->tv_nsec > a->tv_nsec)) {
return (MNTFS_SECOND);
} else {
return (MNTFS_FIRST);
}
}
static int
mntfs_optsize(struct vfs *vfsp)
{
int i, size = 0;
mntopt_t *mop;
for (i = 0; i < vfsp->vfs_mntopts.mo_count; i++) {
mop = &vfsp->vfs_mntopts.mo_list[i];
if (mop->mo_flags & MO_NODISPLAY)
continue;
if (mop->mo_flags & MO_SET) {
if (size)
size++;
size += strlen(mop->mo_name);
if (mop->mo_arg != NULL) {
size += strlen(mop->mo_arg) + 1;
}
}
}
if (vfsp->vfs_zone != NULL && vfsp->vfs_zone != global_zone) {
if (size)
size++;
size += sizeof ("zone=") - 1;
size += strlen(vfsp->vfs_zone->zone_name);
}
if (mntfs_enabledev) {
if (size != 0)
size++;
size += mntfs_devsize(vfsp);
}
if (size == 0)
size = strlen("-");
return (size);
}
static int
mntfs_optprint(struct vfs *vfsp, char *buf)
{
int i, optinbuf = 0;
mntopt_t *mop;
char *origbuf = buf;
for (i = 0; i < vfsp->vfs_mntopts.mo_count; i++) {
mop = &vfsp->vfs_mntopts.mo_list[i];
if (mop->mo_flags & MO_NODISPLAY)
continue;
if (mop->mo_flags & MO_SET) {
if (optinbuf)
*buf++ = ',';
else
optinbuf = 1;
buf += snprintf(buf, MAX_MNTOPT_STR,
"%s", mop->mo_name);
if (mop->mo_arg != NULL) {
buf += snprintf(buf, MAX_MNTOPT_STR, "=%s",
mop->mo_arg);
}
}
}
if (vfsp->vfs_zone != NULL && vfsp->vfs_zone != global_zone) {
if (optinbuf)
*buf++ = ',';
else
optinbuf = 1;
buf += snprintf(buf, MAX_MNTOPT_STR, "zone=%s",
vfsp->vfs_zone->zone_name);
}
if (mntfs_enabledev) {
if (optinbuf++)
*buf++ = ',';
buf += mntfs_devprint(vfsp, buf);
}
if (!optinbuf) {
buf += snprintf(buf, MAX_MNTOPT_STR, "-");
}
return (buf - origbuf);
}
void
mntfs_populate_text(vfs_t *vfsp, zone_t *zonep, mntelem_t *elemp)
{
struct extmnttab *tabp = &elemp->mnte_tab;
const char *resource, *mntpt;
char *cp = elemp->mnte_text;
mntpt = refstr_value(vfsp->vfs_mntpt);
resource = refstr_value(vfsp->vfs_resource);
tabp->mnt_special = 0;
if (resource != NULL && resource[0] != '\0') {
if (resource[0] != '/') {
cp += snprintf(cp, MAXPATHLEN, "%s\t", resource);
} else if (!ZONE_PATH_VISIBLE(resource, zonep)) {
cp += snprintf(cp, MAXPATHLEN, "%s\t",
ZONE_PATH_TRANSLATE(mntpt, zonep));
} else {
cp += snprintf(cp, MAXPATHLEN, "%s\t",
ZONE_PATH_TRANSLATE(resource, zonep));
}
} else {
cp += snprintf(cp, MAXPATHLEN, "-\t");
}
tabp->mnt_mountp = (char *)(cp - elemp->mnte_text);
if (mntpt != NULL && mntpt[0] != '\0') {
cp += snprintf(cp, MAXPATHLEN, "%s\t",
ZONE_PATH_TRANSLATE(mntpt, zonep));
} else {
cp += snprintf(cp, MAXPATHLEN, "-\t");
}
tabp->mnt_fstype = (char *)(cp - elemp->mnte_text);
cp += snprintf(cp, MAXPATHLEN, "%s\t",
vfssw[vfsp->vfs_fstype].vsw_name);
tabp->mnt_mntopts = (char *)(cp - elemp->mnte_text);
cp += mntfs_optprint(vfsp, cp);
*cp++ = '\t';
tabp->mnt_time = (char *)(cp - elemp->mnte_text);
cp += snprintf(cp, MAX_MNTOPT_STR, "%ld", vfsp->vfs_mtime);
*cp++ = '\n';
tabp->mnt_major = getmajor(vfsp->vfs_dev);
tabp->mnt_minor = getminor(vfsp->vfs_dev);
elemp->mnte_text_size = cp - elemp->mnte_text;
elemp->mnte_vfs_ctime = vfsp->vfs_hrctime;
elemp->mnte_hidden = vfsp->vfs_flag & VFS_NOMNTTAB;
}
static size_t
mntfs_text_len(vfs_t *vfsp, zone_t *zone)
{
size_t size = 0;
const char *resource, *mntpt;
size_t mntsize;
mntpt = refstr_value(vfsp->vfs_mntpt);
if (mntpt != NULL && mntpt[0] != '\0') {
mntsize = strlen(ZONE_PATH_TRANSLATE(mntpt, zone)) + 1;
} else {
mntsize = 2;
}
size += mntsize;
resource = refstr_value(vfsp->vfs_resource);
if (resource != NULL && resource[0] != '\0') {
if (resource[0] != '/') {
size += strlen(resource) + 1;
} else if (!ZONE_PATH_VISIBLE(resource, zone)) {
size += mntsize;
} else {
size += strlen(ZONE_PATH_TRANSLATE(resource, zone)) + 1;
}
} else {
size += 2;
}
size += strlen(vfssw[vfsp->vfs_fstype].vsw_name) + 1;
size += mntfs_optsize(vfsp);
size += snprintf(NULL, 0, "\t%ld\n", vfsp->vfs_mtime);
return (size);
}
static void
mntfs_destroy_elem(mntelem_t *elemp)
{
kmem_free(elemp->mnte_text, elemp->mnte_text_size);
kmem_free(elemp, sizeof (mntelem_t));
}
static int
mntfs_elem_in_range(mntsnap_t *snapp, mntelem_t *elemp)
{
timespec_t *stimep = &snapp->mnts_time;
timespec_t *btimep = &elemp->mnte_birth;
timespec_t *dtimep = &elemp->mnte_death;
if (mntfs_newest(btimep, stimep) == MNTFS_SECOND &&
(MNTFS_ELEM_IS_ALIVE(elemp) ||
mntfs_newest(stimep, dtimep) == MNTFS_SECOND))
return (1);
else
return (0);
}
static mntelem_t *
mntfs_get_next_elem(mntsnap_t *snapp, mntelem_t *elemp)
{
int show_hidden = snapp->mnts_flags & MNTS_SHOWHIDDEN;
do {
elemp = elemp->mnte_next;
} while (elemp &&
(!mntfs_elem_in_range(snapp, elemp) ||
(!show_hidden && elemp->mnte_hidden)));
return (elemp);
}
static void
mntfs_freesnap(mntnode_t *mnp, mntsnap_t *snapp)
{
zone_t *zonep = MTOD(mnp)->mnt_zone_ref.zref_zone;
krwlock_t *dblockp = &zonep->zone_mntfs_db_lock;
mntelem_t **elempp = &zonep->zone_mntfs_db;
mntelem_t *elemp;
int show_hidden = snapp->mnts_flags & MNTS_SHOWHIDDEN;
size_t number_decremented = 0;
ASSERT(RW_WRITE_HELD(&mnp->mnt_contents));
if (snapp->mnts_nmnts == 0)
return;
rw_enter(dblockp, RW_WRITER);
while ((elemp = *elempp) != NULL) {
if (mntfs_elem_in_range(snapp, elemp) &&
(!elemp->mnte_hidden || show_hidden) &&
++number_decremented && --elemp->mnte_refcnt == 0) {
if ((*elempp = elemp->mnte_next) != NULL)
(*elempp)->mnte_prev = elemp->mnte_prev;
mntfs_destroy_elem(elemp);
} else {
elempp = &elemp->mnte_next;
}
}
rw_exit(dblockp);
ASSERT(number_decremented == snapp->mnts_nmnts);
bzero(snapp, sizeof (mntsnap_t));
}
static void
mntfs_insert_after(mntelem_t *newp, mntelem_t *prevp)
{
newp->mnte_prev = prevp;
newp->mnte_next = prevp->mnte_next;
prevp->mnte_next = newp;
if (newp->mnte_next != NULL)
newp->mnte_next->mnte_prev = newp;
}
static mntelem_t *
mntfs_copy(mntelem_t *origp)
{
mntelem_t *copyp;
copyp = kmem_zalloc(sizeof (mntelem_t), KM_SLEEP);
copyp->mnte_vfs_ctime = origp->mnte_vfs_ctime;
copyp->mnte_text_size = origp->mnte_text_size;
copyp->mnte_text = kmem_alloc(copyp->mnte_text_size, KM_SLEEP);
bcopy(origp->mnte_text, copyp->mnte_text, copyp->mnte_text_size);
copyp->mnte_tab = origp->mnte_tab;
copyp->mnte_hidden = origp->mnte_hidden;
return (copyp);
}
static int
mntfs_is_same_element(mntelem_t *a, mntelem_t *b)
{
if (a->mnte_hidden == b->mnte_hidden &&
a->mnte_text_size == b->mnte_text_size &&
bcmp(a->mnte_text, b->mnte_text, a->mnte_text_size) == 0 &&
bcmp(&a->mnte_tab, &b->mnte_tab, sizeof (struct extmnttab)) == 0)
return (1);
else
return (0);
}
static void
mntfs_snapshot(mntnode_t *mnp, mntsnap_t *snapp)
{
mntdata_t *mnd = MTOD(mnp);
zone_t *zonep = mnd->mnt_zone_ref.zref_zone;
int is_global_zone = (zonep == global_zone);
int show_hidden = mnp->mnt_flags & MNT_SHOWHIDDEN;
vfs_t *vfsp, *firstvfsp, *lastvfsp;
vfs_t dummyvfs;
vfs_t *dummyvfsp = NULL;
krwlock_t *dblockp = &zonep->zone_mntfs_db_lock;
mntelem_t **headpp = &zonep->zone_mntfs_db;
mntelem_t *elemp;
mntelem_t *prevp = NULL;
int order;
mntelem_t *tempelemp;
mntelem_t *newp;
mntelem_t *firstp = NULL;
size_t nmnts = 0;
size_t total_text_size = 0;
size_t normal_text_size = 0;
int insert_before;
timespec_t last_mtime;
size_t entry_length, new_entry_length;
ASSERT(RW_WRITE_HELD(&mnp->mnt_contents));
vfs_list_read_lock();
vfs_mnttab_modtime(&last_mtime);
if (snapp->mnts_nmnts) {
if (mntfs_newest(&last_mtime, &snapp->mnts_last_mtime) ==
MNTFS_NEITHER) {
snapp->mnts_next = snapp->mnts_first;
snapp->mnts_flags &= ~MNTS_REWIND;
snapp->mnts_foffset = snapp->mnts_ieoffset = 0;
vfs_list_unlock();
return;
} else {
mntfs_freesnap(mnp, snapp);
}
}
tempelemp = kmem_alloc(sizeof (mntelem_t), KM_SLEEP);
entry_length = MNT_LINE_MAX;
tempelemp->mnte_text = kmem_alloc(entry_length, KM_SLEEP);
if (is_global_zone) {
firstvfsp = rootvfs;
lastvfsp = firstvfsp->vfs_prev;
} else {
firstvfsp = zonep->zone_vfslist;
if (firstvfsp == NULL ||
strcmp(refstr_value(firstvfsp->vfs_mntpt),
zonep->zone_rootpath) != 0) {
dummyvfs = *zonep->zone_rootvp->v_vfsp;
dummyvfs.vfs_mntpt = refstr_alloc(zonep->zone_rootpath);
if (strcmp(vfssw[dummyvfs.vfs_fstype].vsw_name, "zfs")
!= 0)
dummyvfs.vfs_resource = dummyvfs.vfs_mntpt;
dummyvfsp = &dummyvfs;
if (firstvfsp == NULL) {
lastvfsp = dummyvfsp;
} else {
lastvfsp = firstvfsp->vfs_zone_prev;
dummyvfsp->vfs_zone_next = firstvfsp;
}
firstvfsp = dummyvfsp;
} else {
lastvfsp = firstvfsp->vfs_zone_prev;
}
}
rw_enter(dblockp, RW_WRITER);
elemp = zonep->zone_mntfs_db;
for (vfsp = firstvfsp;;
vfsp = is_global_zone ? vfsp->vfs_next : vfsp->vfs_zone_next) {
DTRACE_PROBE1(new__vfs, vfs_t *, vfsp);
if ((vfsp->vfs_flag & VFS_NOMNTTAB) == 0 || show_hidden) {
insert_before = 0;
for (; elemp; prevp = elemp, elemp = elemp->mnte_next) {
DTRACE_PROBE1(considering__elem, mntelem_t *,
elemp);
order = mntfs_newest(&elemp->mnte_vfs_ctime,
&vfsp->vfs_hrctime);
if (order == MNTFS_FIRST) {
insert_before = 1;
break;
}
if (MNTFS_ELEM_IS_DEAD(elemp))
continue;
if (order == MNTFS_NEITHER)
break;
if (!elemp->mnte_hidden || show_hidden)
vfs_mono_time(&elemp->mnte_death);
}
DTRACE_PROBE2(possible__match, vfs_t *, vfsp,
mntelem_t *, elemp);
new_entry_length = mntfs_text_len(vfsp, zonep);
if (new_entry_length > entry_length) {
kmem_free(tempelemp->mnte_text, entry_length);
tempelemp->mnte_text =
kmem_alloc(new_entry_length, KM_SLEEP);
entry_length = new_entry_length;
}
mntfs_populate_text(vfsp, zonep, tempelemp);
ASSERT(tempelemp->mnte_text_size == new_entry_length);
if (elemp == NULL) {
newp = mntfs_copy(tempelemp);
vfs_mono_time(&newp->mnte_birth);
if (prevp) {
mntfs_insert_after(newp, prevp);
} else {
newp->mnte_next = NULL;
newp->mnte_prev = NULL;
ASSERT(*headpp == NULL);
*headpp = newp;
}
elemp = newp;
} else if (insert_before) {
newp = mntfs_copy(tempelemp);
vfs_mono_time(&newp->mnte_birth);
if (prevp) {
mntfs_insert_after(newp, prevp);
} else {
newp->mnte_next = elemp;
newp->mnte_prev = NULL;
elemp->mnte_prev = newp;
ASSERT(*headpp == elemp);
*headpp = newp;
}
elemp = newp;
} else if (!mntfs_is_same_element(elemp, tempelemp)) {
vfs_mono_time(&elemp->mnte_death);
newp = mntfs_copy(tempelemp);
vfs_mono_time(&newp->mnte_birth);
mntfs_insert_after(newp, elemp);
elemp = newp;
}
DTRACE_PROBE1(incrementing, mntelem_t *, elemp);
elemp->mnte_refcnt++;
nmnts++;
total_text_size += elemp->mnte_text_size;
if (!elemp->mnte_hidden)
normal_text_size += elemp->mnte_text_size;
if (!firstp)
firstp = elemp;
prevp = elemp;
elemp = elemp->mnte_next;
}
if (vfsp == lastvfsp)
break;
}
for (; elemp; elemp = elemp->mnte_next) {
if (MNTFS_ELEM_IS_ALIVE(elemp) &&
(!elemp->mnte_hidden || show_hidden))
vfs_mono_time(&elemp->mnte_death);
}
vfs_mono_time(&snapp->mnts_time);
snapp->mnts_last_mtime = last_mtime;
snapp->mnts_first = snapp->mnts_next = firstp;
snapp->mnts_flags = show_hidden ? MNTS_SHOWHIDDEN : 0;
snapp->mnts_nmnts = nmnts;
snapp->mnts_text_size = total_text_size;
snapp->mnts_foffset = snapp->mnts_ieoffset = 0;
mnd->mnt_size = normal_text_size;
mnd->mnt_mtime = last_mtime;
if (show_hidden) {
mnd->mnt_hidden_size = total_text_size;
mnd->mnt_hidden_mtime = last_mtime;
}
rw_exit(dblockp);
vfs_list_unlock();
if (dummyvfsp != NULL)
refstr_rele(dummyvfsp->vfs_mntpt);
kmem_free(tempelemp->mnte_text, entry_length);
kmem_free(tempelemp, sizeof (mntelem_t));
}
void
mntfs_getmntopts(struct vfs *vfsp, char **bufp, size_t *lenp)
{
size_t len;
char *buf;
vfs_list_read_lock();
len = mntfs_optsize(vfsp) + 1;
buf = kmem_alloc(len, KM_NOSLEEP);
if (buf == NULL) {
*bufp = NULL;
vfs_list_unlock();
return;
}
buf[len - 1] = '\0';
(void) mntfs_optprint(vfsp, buf);
ASSERT(buf[len - 1] == '\0');
vfs_list_unlock();
*bufp = buf;
*lenp = len;
}
static int
mntopen(vnode_t **vpp, int flag, cred_t *cr, caller_context_t *ct)
{
vnode_t *vp = *vpp;
mntnode_t *nmnp;
if (flag & FWRITE)
return (EPERM);
nmnp = mntgetnode(vp);
*vpp = MTOV(nmnp);
atomic_inc_32(&MTOD(nmnp)->mnt_nopen);
VN_RELE(vp);
return (0);
}
static int
mntclose(vnode_t *vp, int flag, int count, offset_t offset, cred_t *cr,
caller_context_t *ct)
{
mntnode_t *mnp = VTOM(vp);
cleanlocks(vp, ttoproc(curthread)->p_pid, 0);
cleanshares(vp, ttoproc(curthread)->p_pid);
if (count > 1)
return (0);
if (vp->v_count == 1) {
rw_enter(&mnp->mnt_contents, RW_WRITER);
mntfs_freesnap(mnp, &mnp->mnt_read);
mntfs_freesnap(mnp, &mnp->mnt_ioctl);
rw_exit(&mnp->mnt_contents);
atomic_dec_32(&MTOD(mnp)->mnt_nopen);
}
return (0);
}
static int
mntread(vnode_t *vp, uio_t *uio, int ioflag, cred_t *cred, caller_context_t *ct)
{
mntnode_t *mnp = VTOM(vp);
zone_t *zonep = MTOD(mnp)->mnt_zone_ref.zref_zone;
mntsnap_t *snapp = &mnp->mnt_read;
off_t off = uio->uio_offset;
size_t len = uio->uio_resid;
char *bufferp;
size_t available, copylen;
size_t written = 0;
mntelem_t *elemp;
krwlock_t *dblockp = &zonep->zone_mntfs_db_lock;
int error = 0;
off_t ieoffset;
rw_enter(&mnp->mnt_contents, RW_WRITER);
if (snapp->mnts_nmnts == 0 || (off == (off_t)0))
mntfs_snapshot(mnp, snapp);
if ((size_t)(off + len) > snapp->mnts_text_size)
len = snapp->mnts_text_size - off;
if (off < 0 || len > snapp->mnts_text_size) {
rw_exit(&mnp->mnt_contents);
return (EFAULT);
}
if (len == 0) {
rw_exit(&mnp->mnt_contents);
return (0);
}
rw_enter(dblockp, RW_READER);
if (off == 0 || (off == snapp->mnts_foffset)) {
elemp = snapp->mnts_next;
ieoffset = snapp->mnts_ieoffset;
} else {
off_t total_off;
if (off > snapp->mnts_foffset) {
elemp = snapp->mnts_next;
total_off = snapp->mnts_foffset - snapp->mnts_ieoffset;
} else {
elemp = snapp->mnts_first;
total_off = 0;
}
while (off > total_off + elemp->mnte_text_size) {
total_off += elemp->mnte_text_size;
elemp = mntfs_get_next_elem(snapp, elemp);
ASSERT(elemp != NULL);
}
if (off > total_off)
ieoffset = off - total_off;
else
ieoffset = 0;
}
bufferp = kmem_alloc(len, KM_SLEEP);
while (written < len) {
available = elemp->mnte_text_size - ieoffset;
copylen = MIN(len - written, available);
bcopy(elemp->mnte_text + ieoffset, bufferp + written, copylen);
written += copylen;
if (copylen == available) {
elemp = mntfs_get_next_elem(snapp, elemp);
ASSERT(elemp != NULL || written == len);
ieoffset = 0;
} else {
ieoffset += copylen;
}
}
rw_exit(dblockp);
error = uiomove(bufferp, len, UIO_READ, uio);
if (error == 0) {
snapp->mnts_next = elemp;
snapp->mnts_foffset = off + len;
snapp->mnts_ieoffset = ieoffset;
}
vfs_mnttab_readop();
rw_exit(&mnp->mnt_contents);
kmem_free(bufferp, len);
return (error);
}
static int
mntgetattr(vnode_t *vp, vattr_t *vap, int flags, cred_t *cr,
caller_context_t *ct)
{
int mask = vap->va_mask;
int error;
mntnode_t *mnp = VTOM(vp);
timespec_t mtime, old_mtime;
size_t size, old_size;
mntdata_t *mntdata = MTOD(VTOM(vp));
mntsnap_t *rsnapp, *isnapp;
extern timespec_t vfs_mnttab_ctime;
if (mask & AT_MODE|AT_UID|AT_GID) {
if (error = VOP_GETATTR(mnp->mnt_mountvp, vap, flags, cr, ct))
return (error);
}
if (mask & AT_SIZE|AT_NBLOCKS) {
rw_enter(&mnp->mnt_contents, RW_WRITER);
vfs_list_read_lock();
vfs_mnttab_modtime(&mtime);
if (mnp->mnt_flags & MNT_SHOWHIDDEN) {
old_mtime = mntdata->mnt_hidden_mtime;
old_size = mntdata->mnt_hidden_size;
} else {
old_mtime = mntdata->mnt_mtime;
old_size = mntdata->mnt_size;
}
vfs_list_unlock();
rsnapp = &mnp->mnt_read;
isnapp = &mnp->mnt_ioctl;
if (rsnapp->mnts_nmnts || isnapp->mnts_nmnts) {
size = rsnapp->mnts_nmnts ? rsnapp->mnts_text_size :
isnapp->mnts_text_size;
} else if (mntfs_newest(&mtime, &old_mtime) == MNTFS_NEITHER) {
size = old_size;
} else {
mntfs_snapshot(mnp, rsnapp);
size = rsnapp->mnts_text_size;
mtime = rsnapp->mnts_last_mtime;
mntfs_freesnap(mnp, rsnapp);
}
rw_exit(&mnp->mnt_contents);
} else if (mask & AT_ATIME|AT_MTIME) {
vfs_list_read_lock();
vfs_mnttab_modtime(&mtime);
vfs_list_unlock();
}
if (mask & AT_TYPE)
vap->va_type = VREG;
if (mask & AT_MODE)
vap->va_mode &= 07444;
if (mask & AT_FSID)
vap->va_fsid = vp->v_vfsp->vfs_dev;
if (mask & AT_NODEID)
vap->va_nodeid = (ino64_t)MNTROOTINO;
if (mask & AT_NLINK)
vap->va_nlink = mntdata->mnt_nopen + 1;
if (mask & AT_SIZE)
vap->va_size = size;
if (mask & AT_ATIME)
vap->va_atime = mtime;
if (mask & AT_MTIME)
vap->va_mtime = mtime;
if (mask & AT_CTIME)
vap->va_ctime = vfs_mnttab_ctime;
if (mask & AT_RDEV)
vap->va_rdev = 0;
if (mask & AT_BLKSIZE)
vap->va_blksize = DEV_BSIZE;
if (mask & AT_NBLOCKS)
vap->va_nblocks = btod(size);
if (mask & AT_SEQ)
vap->va_seq = 0;
return (0);
}
static int
mntaccess(vnode_t *vp, int mode, int flags, cred_t *cr,
caller_context_t *ct)
{
mntnode_t *mnp = VTOM(vp);
if (mode & (VWRITE|VEXEC))
return (EROFS);
return (VOP_ACCESS(mnp->mnt_mountvp, mode, flags, cr, ct));
}
static mntnode_t *
mntgetnode(vnode_t *dp)
{
mntnode_t *mnp;
vnode_t *vp;
mnp = kmem_zalloc(sizeof (mntnode_t), KM_SLEEP);
mnp->mnt_vnode = vn_alloc(KM_SLEEP);
mnp->mnt_mountvp = VTOM(dp)->mnt_mountvp;
rw_init(&mnp->mnt_contents, NULL, RW_DEFAULT, NULL);
vp = MTOV(mnp);
vp->v_flag = VNOCACHE|VNOMAP|VNOSWAP|VNOMOUNT;
vn_setops(vp, mntvnodeops);
vp->v_vfsp = dp->v_vfsp;
vp->v_type = VREG;
vp->v_data = (caddr_t)mnp;
return (mnp);
}
static void
mntfreenode(mntnode_t *mnp)
{
vnode_t *vp = MTOV(mnp);
rw_destroy(&mnp->mnt_contents);
vn_invalid(vp);
vn_free(vp);
kmem_free(mnp, sizeof (*mnp));
}
static int
mntfsync(vnode_t *vp, int syncflag, cred_t *cr, caller_context_t *ct)
{
return (0);
}
static void
mntinactive(vnode_t *vp, cred_t *cr, caller_context_t *ct)
{
mntnode_t *mnp = VTOM(vp);
mntfreenode(mnp);
}
static int
mntseek(vnode_t *vp, offset_t ooff, offset_t *noffp, caller_context_t *ct)
{
mntnode_t *mnp = VTOM(vp);
if (*noffp == 0) {
rw_enter(&mnp->mnt_contents, RW_WRITER);
mnp->mnt_ioctl.mnts_flags |= MNTS_REWIND;
rw_exit(&mnp->mnt_contents);
}
return (0);
}
static int
mntpoll(vnode_t *vp, short ev, int any, short *revp, pollhead_t **phpp,
caller_context_t *ct)
{
mntnode_t *mnp = VTOM(vp);
mntsnap_t *snapp;
rw_enter(&mnp->mnt_contents, RW_READER);
if (mntfs_newest(&mnp->mnt_ioctl.mnts_last_mtime,
&mnp->mnt_read.mnts_last_mtime) == MNTFS_FIRST)
snapp = &mnp->mnt_ioctl;
else
snapp = &mnp->mnt_read;
*revp = 0;
*phpp = (pollhead_t *)NULL;
if (ev & POLLIN)
*revp |= POLLIN;
if (ev & POLLRDNORM)
*revp |= POLLRDNORM;
if (ev & POLLRDBAND) {
vfs_mnttab_poll(&snapp->mnts_last_mtime, phpp);
if (*phpp == (pollhead_t *)NULL)
*revp |= POLLRDBAND;
}
rw_exit(&mnp->mnt_contents);
if (*revp || *phpp != NULL || any) {
return (0);
}
*revp = POLLERR;
return (0);
}
int
mntfs_same_word(char *worda, char *bufa, size_t sizea, off_t offb, char *bufb,
size_t sizeb)
{
char *wordb = bufb + offb;
int bytes_remaining;
ASSERT(worda != NULL);
bytes_remaining = MIN(((bufa + sizea) - worda),
((bufb + sizeb) - wordb));
while (bytes_remaining && *worda == *wordb) {
worda++;
wordb++;
bytes_remaining--;
}
if (bytes_remaining &&
*worda == '\0' && (*wordb == '\t' || *wordb == '\n'))
return (1);
else
return (0);
}
vtype_t
mntfs_special_info_string(char *path, uint_t *major, uint_t *minor, cred_t *cr)
{
vattr_t vattr;
vnode_t *vp;
vtype_t type;
int error;
if (path == NULL || *path != '/' ||
lookupnameat(path + 1, UIO_SYSSPACE, FOLLOW, NULLVPP, &vp, rootdir))
return (0);
vattr.va_mask = AT_TYPE | AT_RDEV;
error = VOP_GETATTR(vp, &vattr, ATTR_REAL, cr, NULL);
VN_RELE(vp);
if (error == 0 && ((type = vattr.va_type) == VBLK || type == VCHR)) {
if (major && minor) {
*major = getmajor(vattr.va_rdev);
*minor = getminor(vattr.va_rdev);
}
return (type);
} else {
return (0);
}
}
vtype_t
mntfs_special_info_element(mntelem_t *elemp, cred_t *cr)
{
char *newpath;
vtype_t type;
newpath = kmem_alloc(elemp->mnte_text_size, KM_SLEEP);
bcopy(elemp->mnte_text, newpath, (off_t)(elemp->mnte_tab.mnt_mountp));
*(newpath + (off_t)elemp->mnte_tab.mnt_mountp - 1) = '\0';
type = mntfs_special_info_string(newpath, NULL, NULL, cr);
kmem_free(newpath, elemp->mnte_text_size);
return (type);
}
char *
mntfs_import_addr(char *uaddr, char *ubufp, char *kbufp, size_t bufsize)
{
if (uaddr < ubufp || uaddr >= ubufp + bufsize)
return (NULL);
else
return (kbufp + (uaddr - ubufp));
}
#ifdef _SYSCALL32_IMPL
typedef struct extmnttab32 {
uint32_t mnt_special;
uint32_t mnt_mountp;
uint32_t mnt_fstype;
uint32_t mnt_mntopts;
uint32_t mnt_time;
uint_t mnt_major;
uint_t mnt_minor;
} extmnttab32_t;
typedef struct mnttab32 {
uint32_t mnt_special;
uint32_t mnt_mountp;
uint32_t mnt_fstype;
uint32_t mnt_mntopts;
uint32_t mnt_time;
} mnttab32_t;
struct mntentbuf32 {
uint32_t mbuf_emp;
uint_t mbuf_bufsize;
uint32_t mbuf_buf;
};
#endif
int
mntfs_copyout_elem(mntelem_t *elemp, struct extmnttab *uemp,
char *ubufp, int cmd, int datamodel)
{
STRUCT_DECL(extmnttab, ktab);
char *dbbufp = elemp->mnte_text;
size_t dbbufsize = elemp->mnte_text_size;
struct extmnttab *dbtabp = &elemp->mnte_tab;
size_t ssize;
char *kbufp;
int error = 0;
STRUCT_INIT(ktab, datamodel);
STRUCT_FSETP(ktab, mnt_special,
MNTFS_REAL_FIELD(dbbufp) ? ubufp : NULL);
STRUCT_FSETP(ktab, mnt_mountp,
MNTFS_REAL_FIELD(dbbufp + (off_t)dbtabp->mnt_mountp) ?
ubufp + (off_t)dbtabp->mnt_mountp : NULL);
STRUCT_FSETP(ktab, mnt_fstype,
MNTFS_REAL_FIELD(dbbufp + (off_t)dbtabp->mnt_fstype) ?
ubufp + (off_t)dbtabp->mnt_fstype : NULL);
STRUCT_FSETP(ktab, mnt_mntopts,
MNTFS_REAL_FIELD(dbbufp + (off_t)dbtabp->mnt_mntopts) ?
ubufp + (off_t)dbtabp->mnt_mntopts : NULL);
STRUCT_FSETP(ktab, mnt_time,
ubufp + (off_t)dbtabp->mnt_time);
if (cmd == MNTIOC_GETEXTMNTENT) {
STRUCT_FSETP(ktab, mnt_major, dbtabp->mnt_major);
STRUCT_FSETP(ktab, mnt_minor, dbtabp->mnt_minor);
ssize = SIZEOF_STRUCT(extmnttab, datamodel);
} else {
ssize = SIZEOF_STRUCT(mnttab, datamodel);
}
if (copyout(STRUCT_BUF(ktab), uemp, ssize))
return (EFAULT);
kbufp = kmem_alloc(dbbufsize, KM_SLEEP);
bcopy(elemp->mnte_text, kbufp, dbbufsize);
*(kbufp + (off_t)dbtabp->mnt_mountp - 1) =
*(kbufp + (off_t)dbtabp->mnt_fstype - 1) =
*(kbufp + (off_t)dbtabp->mnt_mntopts - 1) =
*(kbufp + (off_t)dbtabp->mnt_time - 1) =
*(kbufp + dbbufsize - 1) = '\0';
if (copyout(kbufp, ubufp, dbbufsize))
error = EFAULT;
kmem_free(kbufp, dbbufsize);
return (error);
}
static int
mntioctl(struct vnode *vp, int cmd, intptr_t arg, int flag, cred_t *cr,
int *rvalp, caller_context_t *ct)
{
uint_t *up = (uint_t *)arg;
mntnode_t *mnp = VTOM(vp);
mntsnap_t *snapp = &mnp->mnt_ioctl;
int error = 0;
zone_t *zonep = MTOD(mnp)->mnt_zone_ref.zref_zone;
krwlock_t *dblockp = &zonep->zone_mntfs_db_lock;
model_t datamodel = flag & DATAMODEL_MASK;
switch (cmd) {
case MNTIOC_NMNTS:
{
rw_enter(&mnp->mnt_contents, RW_READER);
if (snapp->mnts_nmnts == 0 ||
(snapp->mnts_flags & MNTS_REWIND)) {
if (!rw_tryupgrade(&mnp->mnt_contents)) {
rw_exit(&mnp->mnt_contents);
rw_enter(&mnp->mnt_contents, RW_WRITER);
}
if (snapp->mnts_nmnts == 0 ||
(snapp->mnts_flags & MNTS_REWIND))
mntfs_snapshot(mnp, snapp);
}
rw_exit(&mnp->mnt_contents);
if (suword32(up, snapp->mnts_nmnts) != 0)
error = EFAULT;
break;
}
case MNTIOC_GETDEVLIST:
{
size_t len;
uint_t *devlist;
mntelem_t *elemp;
int i = 0;
rw_enter(&mnp->mnt_contents, RW_READER);
if (snapp->mnts_nmnts == 0 ||
(snapp->mnts_flags & MNTS_REWIND)) {
if (!rw_tryupgrade(&mnp->mnt_contents)) {
rw_exit(&mnp->mnt_contents);
rw_enter(&mnp->mnt_contents, RW_WRITER);
}
if (snapp->mnts_nmnts == 0 ||
(snapp->mnts_flags & MNTS_REWIND))
mntfs_snapshot(mnp, snapp);
rw_downgrade(&mnp->mnt_contents);
}
len = 2 * snapp->mnts_nmnts * sizeof (uint_t);
devlist = kmem_alloc(len, KM_SLEEP);
rw_enter(dblockp, RW_READER);
for (elemp = snapp->mnts_first; elemp;
elemp = mntfs_get_next_elem(snapp, elemp)) {
devlist[2 * i] = elemp->mnte_tab.mnt_major;
devlist[2 * i + 1] = elemp->mnte_tab.mnt_minor;
i++;
}
rw_exit(dblockp);
ASSERT(i == snapp->mnts_nmnts);
rw_exit(&mnp->mnt_contents);
error = xcopyout(devlist, up, len);
kmem_free(devlist, len);
break;
}
case MNTIOC_SETTAG:
case MNTIOC_CLRTAG:
{
struct mnttagdesc *dp = (struct mnttagdesc *)arg;
STRUCT_DECL(mnttagdesc, tagdesc);
char *cptr;
uint32_t major, minor;
char tagbuf[MAX_MNTOPT_TAG];
char *pbuf;
size_t len;
uint_t start = 0;
mntdata_t *mntdata = MTOD(mnp);
zone_t *zone = mntdata->mnt_zone_ref.zref_zone;
STRUCT_INIT(tagdesc, flag & DATAMODEL_MASK);
if (copyin(dp, STRUCT_BUF(tagdesc), STRUCT_SIZE(tagdesc))) {
error = EFAULT;
break;
}
pbuf = kmem_alloc(MAXPATHLEN, KM_SLEEP);
if (zone != global_zone) {
(void) strcpy(pbuf, zone->zone_rootpath);
start = zone->zone_rootpathlen - 2;
ASSERT(pbuf[start] == '/');
}
cptr = STRUCT_FGETP(tagdesc, mtd_mntpt);
error = copyinstr(cptr, pbuf + start, MAXPATHLEN - start, &len);
if (error) {
kmem_free(pbuf, MAXPATHLEN);
break;
}
if (start != 0 && pbuf[start] != '/') {
kmem_free(pbuf, MAXPATHLEN);
error = EINVAL;
break;
}
cptr = STRUCT_FGETP(tagdesc, mtd_tag);
if ((error = copyinstr(cptr, tagbuf, MAX_MNTOPT_TAG, &len))) {
kmem_free(pbuf, MAXPATHLEN);
break;
}
major = STRUCT_FGET(tagdesc, mtd_major);
minor = STRUCT_FGET(tagdesc, mtd_minor);
if (cmd == MNTIOC_SETTAG)
error = vfs_settag(major, minor, pbuf, tagbuf, cr);
else
error = vfs_clrtag(major, minor, pbuf, tagbuf, cr);
kmem_free(pbuf, MAXPATHLEN);
break;
}
case MNTIOC_SHOWHIDDEN:
{
rw_enter(&mnp->mnt_contents, RW_WRITER);
mnp->mnt_flags |= MNT_SHOWHIDDEN;
rw_exit(&mnp->mnt_contents);
break;
}
case MNTIOC_GETMNTANY:
{
STRUCT_DECL(mntentbuf, embuf);
STRUCT_DECL(extmnttab, ktab);
struct extmnttab *uemp;
char *ubufp;
size_t ubufsize;
struct extmnttab preftab;
char *prefbuf;
mntelem_t *elemp;
struct extmnttab *dbtabp;
char *dbbufp;
size_t dbbufsize;
vtype_t type;
STRUCT_INIT(embuf, datamodel);
if (copyin((void *) arg, STRUCT_BUF(embuf),
STRUCT_SIZE(embuf))) {
error = EFAULT;
break;
}
uemp = STRUCT_FGETP(embuf, mbuf_emp);
ubufp = STRUCT_FGETP(embuf, mbuf_buf);
ubufsize = STRUCT_FGET(embuf, mbuf_bufsize);
if (ubufsize != MNT_LINE_MAX) {
error = EINVAL;
break;
}
prefbuf = kmem_alloc(MNT_LINE_MAX, KM_SLEEP);
if (copyin(ubufp, prefbuf, MNT_LINE_MAX)) {
kmem_free(prefbuf, MNT_LINE_MAX);
error = EFAULT;
break;
}
*(prefbuf + MNT_LINE_MAX - 1) = 0;
STRUCT_INIT(ktab, datamodel);
if (copyin(uemp, STRUCT_BUF(ktab),
SIZEOF_STRUCT(mnttab, datamodel))) {
kmem_free(prefbuf, MNT_LINE_MAX);
error = EFAULT;
break;
}
preftab.mnt_special = mntfs_import_addr(STRUCT_FGETP(ktab,
mnt_special), ubufp, prefbuf, MNT_LINE_MAX);
preftab.mnt_mountp = mntfs_import_addr(STRUCT_FGETP(ktab,
mnt_mountp), ubufp, prefbuf, MNT_LINE_MAX);
preftab.mnt_fstype = mntfs_import_addr(STRUCT_FGETP(ktab,
mnt_fstype), ubufp, prefbuf, MNT_LINE_MAX);
preftab.mnt_mntopts = mntfs_import_addr(STRUCT_FGETP(ktab,
mnt_mntopts), ubufp, prefbuf, MNT_LINE_MAX);
preftab.mnt_time = mntfs_import_addr(STRUCT_FGETP(ktab,
mnt_time), ubufp, prefbuf, MNT_LINE_MAX);
type = mntfs_special_info_string(preftab.mnt_special,
&preftab.mnt_major, &preftab.mnt_minor, cr);
rw_enter(&mnp->mnt_contents, RW_WRITER);
if (snapp->mnts_nmnts == 0 ||
(snapp->mnts_flags & MNTS_REWIND))
mntfs_snapshot(mnp, snapp);
elemp = snapp->mnts_next;
rw_enter(dblockp, RW_READER);
for (;;) {
for (; elemp; elemp = mntfs_get_next_elem(snapp,
elemp)) {
dbtabp = &elemp->mnte_tab;
dbbufp = elemp->mnte_text;
dbbufsize = elemp->mnte_text_size;
if (((type &&
dbtabp->mnt_major == preftab.mnt_major &&
dbtabp->mnt_minor == preftab.mnt_minor &&
MNTFS_REAL_FIELD(dbbufp)) ||
(!type && (!preftab.mnt_special ||
mntfs_same_word(preftab.mnt_special,
prefbuf, MNT_LINE_MAX, (off_t)0, dbbufp,
dbbufsize)))) &&
(!preftab.mnt_mountp || mntfs_same_word(
preftab.mnt_mountp, prefbuf, MNT_LINE_MAX,
(off_t)dbtabp->mnt_mountp, dbbufp,
dbbufsize)) &&
(!preftab.mnt_fstype || mntfs_same_word(
preftab.mnt_fstype, prefbuf, MNT_LINE_MAX,
(off_t)dbtabp->mnt_fstype, dbbufp,
dbbufsize)) &&
(!preftab.mnt_mntopts || mntfs_same_word(
preftab.mnt_mntopts, prefbuf, MNT_LINE_MAX,
(off_t)dbtabp->mnt_mntopts, dbbufp,
dbbufsize)) &&
(!preftab.mnt_time || mntfs_same_word(
preftab.mnt_time, prefbuf, MNT_LINE_MAX,
(off_t)dbtabp->mnt_time, dbbufp,
dbbufsize)))
break;
}
rw_exit(dblockp);
if (elemp == NULL || type == 0 ||
type == mntfs_special_info_element(elemp, cr))
break;
rw_enter(dblockp, RW_READER);
elemp = mntfs_get_next_elem(snapp, elemp);
}
kmem_free(prefbuf, MNT_LINE_MAX);
if (elemp == NULL) {
rw_exit(&mnp->mnt_contents);
*rvalp = MNTFS_EOF;
break;
}
if (elemp->mnte_text_size > MNT_LINE_MAX) {
rw_exit(&mnp->mnt_contents);
*rvalp = MNTFS_TOOLONG;
break;
}
if (mntfs_copyout_elem(elemp, uemp, ubufp, cmd, datamodel)) {
error = EFAULT;
} else {
rw_enter(dblockp, RW_READER);
elemp = mntfs_get_next_elem(snapp, elemp);
rw_exit(dblockp);
snapp->mnts_next = elemp;
}
rw_exit(&mnp->mnt_contents);
break;
}
case MNTIOC_GETMNTENT:
case MNTIOC_GETEXTMNTENT:
{
STRUCT_DECL(mntentbuf, embuf);
struct extmnttab *uemp;
char *ubufp;
size_t ubufsize;
mntelem_t *elemp;
rw_enter(&mnp->mnt_contents, RW_WRITER);
if (snapp->mnts_nmnts == 0 ||
(snapp->mnts_flags & MNTS_REWIND))
mntfs_snapshot(mnp, snapp);
if ((elemp = snapp->mnts_next) == NULL) {
rw_exit(&mnp->mnt_contents);
*rvalp = MNTFS_EOF;
break;
}
STRUCT_INIT(embuf, datamodel);
if (copyin((void *) arg, STRUCT_BUF(embuf),
STRUCT_SIZE(embuf))) {
rw_exit(&mnp->mnt_contents);
error = EFAULT;
break;
}
uemp = STRUCT_FGETP(embuf, mbuf_emp);
ubufp = STRUCT_FGETP(embuf, mbuf_buf);
ubufsize = STRUCT_FGET(embuf, mbuf_bufsize);
if (elemp->mnte_text_size > ubufsize) {
rw_exit(&mnp->mnt_contents);
*rvalp = MNTFS_TOOLONG;
break;
}
if (mntfs_copyout_elem(elemp, uemp, ubufp, cmd, datamodel)) {
error = EFAULT;
} else {
rw_enter(dblockp, RW_READER);
elemp = mntfs_get_next_elem(snapp, elemp);
rw_exit(dblockp);
snapp->mnts_next = elemp;
}
rw_exit(&mnp->mnt_contents);
break;
}
default:
error = EINVAL;
break;
}
return (error);
}
int
mntcmp(vnode_t *vp1, vnode_t *vp2, caller_context_t *ct)
{
return (vp1 != NULL && vp2 != NULL && vp1->v_vfsp == vp2->v_vfsp);
}
const fs_operation_def_t mnt_vnodeops_template[] = {
VOPNAME_OPEN, { .vop_open = mntopen },
VOPNAME_CLOSE, { .vop_close = mntclose },
VOPNAME_READ, { .vop_read = mntread },
VOPNAME_IOCTL, { .vop_ioctl = mntioctl },
VOPNAME_GETATTR, { .vop_getattr = mntgetattr },
VOPNAME_ACCESS, { .vop_access = mntaccess },
VOPNAME_FSYNC, { .vop_fsync = mntfsync },
VOPNAME_INACTIVE, { .vop_inactive = mntinactive },
VOPNAME_SEEK, { .vop_seek = mntseek },
VOPNAME_POLL, { .vop_poll = mntpoll },
VOPNAME_CMP, { .vop_cmp = mntcmp },
VOPNAME_DISPOSE, { .error = fs_error },
VOPNAME_SHRLOCK, { .error = fs_error },
NULL, NULL
};