#include <sys/systm.h>
#include <sys/cred.h>
#include <sys/time.h>
#include <sys/vfs.h>
#include <sys/vnode.h>
#include <fs/fs_subr.h>
#include <sys/sysmacros.h>
#include <sys/kmem.h>
#include <sys/mkdev.h>
#include <sys/mount.h>
#include <sys/statvfs.h>
#include <sys/errno.h>
#include <sys/debug.h>
#include <sys/disp.h>
#include <sys/cmn_err.h>
#include <sys/modctl.h>
#include <sys/policy.h>
#include <sys/atomic.h>
#include <sys/zone.h>
#include <sys/vfs_opreg.h>
#include <sys/mntent.h>
#include <sys/priv.h>
#include <sys/taskq.h>
#include <sys/tsol/label.h>
#include <sys/tsol/tndb.h>
#include <inet/ip.h>
#include <netsmb/smb_osdep.h>
#include <netsmb/smb.h>
#include <netsmb/smb_conn.h>
#include <netsmb/smb_subr.h>
#include <netsmb/smb_dev.h>
#include <smbfs/smbfs.h>
#include <smbfs/smbfs_node.h>
#include <smbfs/smbfs_subr.h>
#ifndef _KERNEL
#include <libfksmbfs.h>
#define STRUCT_DECL(s, a) struct s a
#define STRUCT_FGET(handle, field) ((handle).field)
#define _init(v) fksmbfs_init(v)
#define _fini(v) fksmbfs_fini(v)
#endif
int smbfs_default_opt_acl = 0;
int smbfs_tq_nthread = 1;
int smbfsinit(int fstyp, char *name);
void smbfsfini();
#ifdef _KERNEL
static int smbfs_mount_label_policy(vfs_t *, void *, int, cred_t *);
#endif
static char *intr_cancel[] = { MNTOPT_NOINTR, NULL };
static char *nointr_cancel[] = { MNTOPT_INTR, NULL };
static char *acl_cancel[] = { MNTOPT_NOACL, NULL };
static char *noacl_cancel[] = { MNTOPT_ACL, NULL };
static char *xattr_cancel[] = { MNTOPT_NOXATTR, NULL };
static char *noxattr_cancel[] = { MNTOPT_XATTR, NULL };
static mntopt_t mntopts[] = {
{ MNTOPT_INTR, intr_cancel, NULL, MO_DEFAULT, 0 },
{ MNTOPT_NOINTR, nointr_cancel, NULL, 0, 0 },
{ MNTOPT_ACL, acl_cancel, NULL, 0, 0 },
{ MNTOPT_NOACL, noacl_cancel, NULL, 0, 0 },
{ MNTOPT_XATTR, xattr_cancel, NULL, MO_DEFAULT, 0 },
{ MNTOPT_NOXATTR, noxattr_cancel, NULL, 0, 0 },
#ifndef _KERNEL
{ MNTOPT_NOAC, NULL, NULL, 0, 0 },
#endif
};
static mntopts_t smbfs_mntopts = {
sizeof (mntopts) / sizeof (mntopt_t),
mntopts
};
static const char fs_type_name[FSTYPSZ] = "smbfs";
static vfsdef_t vfw = {
VFSDEF_VERSION,
(char *)fs_type_name,
smbfsinit,
VSW_HASPROTO|VSW_NOTZONESAFE,
&smbfs_mntopts
};
#ifdef _KERNEL
static struct modlfs modlfs = {
&mod_fsops,
"SMBFS filesystem",
&vfw
};
static struct modlinkage modlinkage = {
MODREV_1, (void *)&modlfs, NULL
};
#endif
extern kmutex_t smbfs_minor_lock;
extern int smbfs_major;
extern int smbfs_minor;
uint32_t smbfs_mountcount;
static int smbfs_mount(vfs_t *, vnode_t *, struct mounta *, cred_t *);
static int smbfs_unmount(vfs_t *, int, cred_t *);
static int smbfs_root(vfs_t *, vnode_t **);
static int smbfs_statvfs(vfs_t *, statvfs64_t *);
static int smbfs_sync(vfs_t *, short, cred_t *);
static void smbfs_freevfs(vfs_t *);
int
_init(void)
{
int error;
if (nsmb_version != NSMB_VERSION) {
cmn_err(CE_WARN, "_init: nsmb version mismatch");
return (ENOTTY);
}
smbfs_mountcount = 0;
if ((error = smbfs_subrinit()) != 0) {
cmn_err(CE_WARN, "_init: smbfs_subrinit failed");
return (error);
}
if ((error = smbfs_vfsinit()) != 0) {
cmn_err(CE_WARN, "_init: smbfs_vfsinit failed");
smbfs_subrfini();
return (error);
}
if ((error = smbfs_clntinit()) != 0) {
cmn_err(CE_WARN, "_init: smbfs_clntinit failed");
smbfs_vfsfini();
smbfs_subrfini();
return (error);
}
#ifdef _KERNEL
error = mod_install((struct modlinkage *)&modlinkage);
#else
error = fake_installfs(&vfw);
#endif
return (error);
}
int
_fini(void)
{
int error;
if (smbfs_mountcount)
return (EBUSY);
#ifdef _KERNEL
error = mod_remove(&modlinkage);
#else
error = fake_removefs(&vfw);
#endif
if (error)
return (error);
smbfs_clntfini();
smbfs_vfsfini();
smbfs_subrfini();
smbfsfini();
return (0);
}
#ifdef _KERNEL
int
_info(struct modinfo *modinfop)
{
return (mod_info((struct modlinkage *)&modlinkage, modinfop));
}
#endif
int smbfs_fstyp;
vfsops_t *smbfs_vfsops = NULL;
static const fs_operation_def_t smbfs_vfsops_template[] = {
{ VFSNAME_MOUNT, { .vfs_mount = smbfs_mount } },
{ VFSNAME_UNMOUNT, { .vfs_unmount = smbfs_unmount } },
{ VFSNAME_ROOT, { .vfs_root = smbfs_root } },
{ VFSNAME_STATVFS, { .vfs_statvfs = smbfs_statvfs } },
{ VFSNAME_SYNC, { .vfs_sync = smbfs_sync } },
{ VFSNAME_VGET, { .error = fs_nosys } },
{ VFSNAME_MOUNTROOT, { .error = fs_nosys } },
{ VFSNAME_FREEVFS, { .vfs_freevfs = smbfs_freevfs } },
{ NULL, NULL }
};
int
smbfsinit(int fstyp, char *name)
{
int error;
error = vfs_setfsops(fstyp, smbfs_vfsops_template, &smbfs_vfsops);
if (error != 0) {
cmn_err(CE_WARN,
"smbfsinit: bad vfs ops template");
return (error);
}
error = vn_make_ops(name, smbfs_vnodeops_template, &smbfs_vnodeops);
if (error != 0) {
(void) vfs_freevfsops_by_type(fstyp);
cmn_err(CE_WARN,
"smbfsinit: bad vnode ops template");
return (error);
}
smbfs_fstyp = fstyp;
return (0);
}
void
smbfsfini()
{
if (smbfs_vfsops) {
(void) vfs_freevfsops_by_type(smbfs_fstyp);
smbfs_vfsops = NULL;
}
if (smbfs_vnodeops) {
vn_freevnodeops(smbfs_vnodeops);
smbfs_vnodeops = NULL;
}
}
void
smbfs_free_smi(smbmntinfo_t *smi)
{
if (smi == NULL)
return;
#ifdef _KERNEL
if (smi->smi_zone_ref.zref_zone != NULL)
zone_rele_ref(&smi->smi_zone_ref, ZONE_REF_SMBFS);
#endif
if (smi->smi_share != NULL)
smb_share_rele(smi->smi_share);
avl_destroy(&smi->smi_hash_avl);
rw_destroy(&smi->smi_hash_lk);
cv_destroy(&smi->smi_statvfs_cv);
mutex_destroy(&smi->smi_lock);
kmem_free(smi, sizeof (smbmntinfo_t));
}
static int
smbfs_mount(vfs_t *vfsp, vnode_t *mvp, struct mounta *uap, cred_t *cr)
{
char *data = uap->dataptr;
int error;
smbnode_t *rtnp = NULL;
smbmntinfo_t *smi = NULL;
dev_t smbfs_dev;
int version;
int devfd;
zone_t *zone = curzone;
#ifdef _KERNEL
zone_t *mntzone = NULL;
#else
short minclsyspri = MINCLSYSPRI;
#endif
smb_share_t *ssp = NULL;
smb_cred_t scred;
int flags, sec;
STRUCT_DECL(smbfs_args, args);
#ifdef _KERNEL
if ((error = secpolicy_fs_mount(cr, mvp, vfsp)) != 0)
return (error);
#endif
if (mvp->v_type != VDIR)
return (ENOTDIR);
#ifdef _KERNEL
STRUCT_INIT(args, get_udatamodel());
bzero(STRUCT_BUF(args), SIZEOF_STRUCT(smbfs_args, DATAMODEL_NATIVE));
if (copyin(data, STRUCT_BUF(args), MIN(uap->datalen,
SIZEOF_STRUCT(smbfs_args, DATAMODEL_NATIVE))))
return (EFAULT);
#else
bzero(&args, sizeof (args));
if (copyin(data, &args, MIN(uap->datalen, sizeof (args))))
return (EFAULT);
#endif
version = STRUCT_FGET(args, version);
if (version != SMBFS_VERSION) {
cmn_err(CE_WARN, "mount version mismatch:"
" kernel=%d, mount=%d\n",
SMBFS_VERSION, version);
return (EINVAL);
}
if (uap->flags & MS_REMOUNT) {
cmn_err(CE_WARN, "MS_REMOUNT not implemented");
return (ENOTSUP);
}
mutex_enter(&mvp->v_lock);
if (!(uap->flags & MS_OVERLAY) &&
(mvp->v_count != 1 || (mvp->v_flag & VROOT))) {
mutex_exit(&mvp->v_lock);
return (EBUSY);
}
mutex_exit(&mvp->v_lock);
devfd = STRUCT_FGET(args, devfd);
error = smb_dev2share(devfd, &ssp);
if (error) {
cmn_err(CE_WARN, "invalid device handle %d (%d)\n",
devfd, error);
return (error);
}
#ifdef _KERNEL
zone_hold(mntzone = zone);
if (getzoneid() == GLOBAL_ZONEID) {
zone_rele(mntzone);
mntzone = zone_find_by_path(refstr_value(vfsp->vfs_mntpt));
ASSERT(mntzone != NULL);
if (mntzone != zone) {
error = EBUSY;
goto errout;
}
}
if (zone_status_get(mntzone) >= ZONE_IS_SHUTTING_DOWN) {
error = EBUSY;
goto errout;
}
if (is_system_labeled()) {
void *addr;
int ipvers = 0;
struct smb_vc *vcp;
vcp = SSTOVC(ssp);
addr = smb_vc_getipaddr(vcp, &ipvers);
error = smbfs_mount_label_policy(vfsp, addr, ipvers, cr);
if (error > 0)
goto errout;
if (error == -1) {
vfs_setmntopt(vfsp, MNTOPT_RO, NULL, 0);
}
}
#endif
atomic_inc_32(&smbfs_mountcount);
smi = kmem_zalloc(sizeof (*smi), KM_SLEEP);
mutex_init(&smi->smi_lock, NULL, MUTEX_DEFAULT, NULL);
cv_init(&smi->smi_statvfs_cv, NULL, CV_DEFAULT, NULL);
rw_init(&smi->smi_hash_lk, NULL, RW_DEFAULT, NULL);
smbfs_init_hash_avl(&smi->smi_hash_avl);
smi->smi_share = ssp;
ssp = NULL;
#ifdef _KERNEL
zone_init_ref(&smi->smi_zone_ref);
zone_hold_ref(mntzone, &smi->smi_zone_ref, ZONE_REF_SMBFS);
zone_rele(mntzone);
mntzone = NULL;
#else
smi->smi_zone_ref.zref_zone = curzone;
#endif
smi->smi_acregmin = SEC2HR(SMBFS_ACREGMIN);
smi->smi_acregmax = SEC2HR(SMBFS_ACREGMAX);
smi->smi_acdirmin = SEC2HR(SMBFS_ACDIRMIN);
smi->smi_acdirmax = SEC2HR(SMBFS_ACDIRMAX);
smi->smi_flags = SMI_LLOCK;
#ifndef _KERNEL
smi->smi_flags |= SMI_DIRECTIO;
#endif
if (smbfs_default_opt_acl ||
vfs_optionisset(vfsp, MNTOPT_ACL, NULL))
smi->smi_flags |= SMI_ACL;
if (vfs_optionisset(vfsp, MNTOPT_NOACL, NULL))
smi->smi_flags &= ~SMI_ACL;
if (vfs_optionisset(vfsp, MNTOPT_INTR, NULL))
smi->smi_flags |= SMI_INT;
flags = STRUCT_FGET(args, flags);
smi->smi_fmode = STRUCT_FGET(args, file_mode) & 0777;
smi->smi_dmode = STRUCT_FGET(args, dir_mode) & 0777;
#ifdef _KERNEL
smi->smi_uid = STRUCT_FGET(args, uid);
smi->smi_gid = STRUCT_FGET(args, gid);
#else
smi->smi_uid = crgetuid(cr);
smi->smi_gid = crgetgid(cr);
if (vfs_optionisset(vfsp, MNTOPT_NOAC, NULL))
flags |= SMBFS_MF_NOAC;
#endif
if (flags & SMBFS_MF_NOAC)
smi->smi_flags |= SMI_NOAC;
if (flags & SMBFS_MF_ACREGMIN) {
sec = STRUCT_FGET(args, acregmin);
if (sec < 0 || sec > SMBFS_ACMINMAX)
sec = SMBFS_ACMINMAX;
smi->smi_acregmin = SEC2HR(sec);
}
if (flags & SMBFS_MF_ACREGMAX) {
sec = STRUCT_FGET(args, acregmax);
if (sec < 0 || sec > SMBFS_ACMAXMAX)
sec = SMBFS_ACMAXMAX;
smi->smi_acregmax = SEC2HR(sec);
}
if (flags & SMBFS_MF_ACDIRMIN) {
sec = STRUCT_FGET(args, acdirmin);
if (sec < 0 || sec > SMBFS_ACMINMAX)
sec = SMBFS_ACMINMAX;
smi->smi_acdirmin = SEC2HR(sec);
}
if (flags & SMBFS_MF_ACDIRMAX) {
sec = STRUCT_FGET(args, acdirmax);
if (sec < 0 || sec > SMBFS_ACMAXMAX)
sec = SMBFS_ACMAXMAX;
smi->smi_acdirmax = SEC2HR(sec);
}
smb_credinit(&scred, cr);
error = smbfs_smb_qfsattr(smi->smi_share, &smi->smi_fsa, &scred);
smb_credrele(&scred);
if (error) {
SMBVDEBUG("smbfs_smb_qfsattr error %d\n", error);
}
if ((smi->smi_fsattr & FILE_NAMED_STREAMS) == 0)
vfs_setmntopt(vfsp, MNTOPT_NOXATTR, NULL, 0);
if ((smi->smi_fsattr & FILE_PERSISTENT_ACLS) == 0) {
vfs_setmntopt(vfsp, MNTOPT_NOACL, NULL, 0);
smi->smi_flags &= ~SMI_ACL;
}
mutex_enter(&smbfs_minor_lock);
do {
smbfs_minor = (smbfs_minor + 1) & MAXMIN32;
smbfs_dev = makedevice(smbfs_major, smbfs_minor);
} while (vfs_devismounted(smbfs_dev));
mutex_exit(&smbfs_minor_lock);
vfsp->vfs_dev = smbfs_dev;
vfs_make_fsid(&vfsp->vfs_fsid, smbfs_dev, smbfs_fstyp);
vfsp->vfs_data = (caddr_t)smi;
vfsp->vfs_fstype = smbfs_fstyp;
vfsp->vfs_bsize = MAXBSIZE;
vfsp->vfs_bcount = 0;
smi->smi_vfsp = vfsp;
smbfs_zonelist_add(smi);
vfs_set_feature(vfsp, VFSFT_XVATTR);
vfs_set_feature(vfsp, VFSFT_SYSATTR_VIEWS);
rtnp = smbfs_node_findcreate(smi, "\\", 1, NULL, 0, 0,
&smbfs_fattr0);
ASSERT(rtnp != NULL);
rtnp->r_vnode->v_type = VDIR;
rtnp->r_vnode->v_flag |= VROOT;
smi->smi_root = rtnp;
smi->smi_taskq = taskq_create_proc("smbfs",
smbfs_tq_nthread, minclsyspri,
smbfs_tq_nthread, smbfs_tq_nthread * 2,
zone->zone_zsched, TASKQ_PREPOPULATE);
return (0);
#ifdef _KERNEL
errout:
vfsp->vfs_data = NULL;
if (smi != NULL)
smbfs_free_smi(smi);
if (mntzone != NULL)
zone_rele(mntzone);
if (ssp != NULL)
smb_share_rele(ssp);
return (error);
#endif
}
static int
smbfs_unmount(vfs_t *vfsp, int flag, cred_t *cr)
{
smbmntinfo_t *smi;
smbnode_t *rtnp;
smi = VFTOSMI(vfsp);
#ifdef _KERNEL
if (secpolicy_fs_unmount(cr, vfsp) != 0)
return (EPERM);
#endif
if ((flag & MS_FORCE) == 0) {
smbfs_rflush(vfsp, cr);
if (smbfs_check_table(vfsp, smi->smi_root))
return (EBUSY);
if (smi->smi_root &&
smi->smi_root->r_vnode->v_count > 1)
return (EBUSY);
}
vfsp->vfs_flag |= VFS_UNMOUNTED;
if (smi->smi_root) {
rtnp = smi->smi_root;
smi->smi_root = NULL;
VN_RELE(rtnp->r_vnode);
}
smbfs_destroy_table(vfsp);
smb_share_kill(smi->smi_share);
taskq_destroy(smi->smi_taskq);
if (smi->smi_io_kstats) {
kstat_delete(smi->smi_io_kstats);
smi->smi_io_kstats = NULL;
}
if (smi->smi_ro_kstats) {
kstat_delete(smi->smi_ro_kstats);
smi->smi_ro_kstats = NULL;
}
return (0);
}
static int
smbfs_root(vfs_t *vfsp, vnode_t **vpp)
{
smbmntinfo_t *smi;
vnode_t *vp;
smi = VFTOSMI(vfsp);
if (curproc->p_zone != smi->smi_zone_ref.zref_zone)
return (EPERM);
if (smi->smi_flags & SMI_DEAD || vfsp->vfs_flag & VFS_UNMOUNTED)
return (EIO);
if (smi->smi_root == NULL)
return (EIO);
vp = SMBTOV(smi->smi_root);
VN_HOLD(vp);
*vpp = vp;
return (0);
}
static int
smbfs_statvfs(vfs_t *vfsp, statvfs64_t *sbp)
{
int error;
smbmntinfo_t *smi = VFTOSMI(vfsp);
smb_share_t *ssp = smi->smi_share;
statvfs64_t stvfs;
hrtime_t now;
smb_cred_t scred;
if (curproc->p_zone != smi->smi_zone_ref.zref_zone)
return (EPERM);
if (smi->smi_flags & SMI_DEAD || vfsp->vfs_flag & VFS_UNMOUNTED)
return (EIO);
mutex_enter(&smi->smi_lock);
recheck:
now = gethrtime();
if (now < smi->smi_statfstime) {
error = 0;
goto cache_hit;
}
if (smi->smi_status & SM_STATUS_STATFS_BUSY) {
smi->smi_status |= SM_STATUS_STATFS_WANT;
if (!cv_wait_sig(&smi->smi_statvfs_cv, &smi->smi_lock)) {
mutex_exit(&smi->smi_lock);
return (EINTR);
}
goto recheck;
}
smi->smi_status |= SM_STATUS_STATFS_BUSY;
mutex_exit(&smi->smi_lock);
smb_credinit(&scred, NULL);
bzero(&stvfs, sizeof (stvfs));
error = smbfs_smb_statfs(ssp, &stvfs, &scred);
smb_credrele(&scred);
if (error) {
SMBVDEBUG("statfs error=%d\n", error);
} else {
stvfs.f_frsize = stvfs.f_bsize;
stvfs.f_favail = stvfs.f_ffree;
stvfs.f_fsid = (unsigned long)vfsp->vfs_fsid.val[0];
bcopy(fs_type_name, stvfs.f_basetype, FSTYPSZ);
stvfs.f_flag = vf_to_stf(vfsp->vfs_flag);
stvfs.f_namemax = smi->smi_fsa.fsa_maxname;
now = gethrtime();
smi->smi_statfstime = now +
(SM_MAX_STATFSTIME * (hrtime_t)NANOSEC);
smi->smi_statvfsbuf = stvfs;
}
mutex_enter(&smi->smi_lock);
if (smi->smi_status & SM_STATUS_STATFS_WANT)
cv_broadcast(&smi->smi_statvfs_cv);
smi->smi_status &= ~(SM_STATUS_STATFS_BUSY | SM_STATUS_STATFS_WANT);
cache_hit:
if (error == 0)
*sbp = smi->smi_statvfsbuf;
mutex_exit(&smi->smi_lock);
return (error);
}
static int
smbfs_sync(vfs_t *vfsp, short flag, cred_t *cr)
{
if (flag & SYNC_ATTR)
return (0);
if (vfsp == NULL) {
smbfs_flushall(cr);
return (0);
}
smbfs_rflush(vfsp, cr);
return (0);
}
int
smbfs_vfsinit(void)
{
return (0);
}
void
smbfs_vfsfini(void)
{
}
void
smbfs_freevfs(vfs_t *vfsp)
{
smbmntinfo_t *smi;
smi = VFTOSMI(vfsp);
ASSERT(smi->smi_io_kstats == NULL);
smbfs_zonelist_remove(smi);
smbfs_free_smi(smi);
atomic_dec_32(&smbfs_mountcount);
}
#ifdef _KERNEL
static int
smbfs_mount_label_policy(vfs_t *vfsp, void *ipaddr, int addr_type, cred_t *cr)
{
bslabel_t *server_sl, *mntlabel;
zone_t *mntzone = NULL;
ts_label_t *zlabel;
tsol_tpc_t *tp;
ts_label_t *tsl = NULL;
int retv;
mntzone = zone_find_by_any_path(refstr_value(vfsp->vfs_mntpt), B_FALSE);
zlabel = mntzone->zone_slabel;
ASSERT(zlabel != NULL);
label_hold(zlabel);
retv = EACCES;
tp = find_tpc(ipaddr, addr_type, B_FALSE);
if (tp == NULL)
goto out;
if (tp->tpc_tp.tp_doi != zlabel->tsl_doi)
goto rel_tpc;
if ((tp->tpc_tp.host_type != UNLABELED))
goto rel_tpc;
server_sl = &tp->tpc_tp.tp_def_label;
mntlabel = label2bslabel(zlabel);
if (blequal(mntlabel, server_sl) ||
(crgetzoneid(cr) == GLOBAL_ZONEID &&
getpflags(NET_MAC_AWARE, cr) != 0)) {
if ((mntzone == global_zone) ||
!blequal(mntlabel, server_sl))
retv = -1;
else
retv = 0;
} else if (bldominates(mntlabel, server_sl)) {
retv = -1;
} else {
retv = EACCES;
}
if (tsl != NULL)
label_rele(tsl);
rel_tpc:
TPC_RELE(tp);
out:
if (mntzone)
zone_rele(mntzone);
label_rele(zlabel);
return (retv);
}
#endif