blob: 3249f8d4391b24303a3223e3ffb3a709f8619535 [file] [log] [blame] [raw]
/*
* (c) 2002 Pawel Jakub Dawidek <nick@garage.freebsd.pl>
*
* $Id: sysctlfs_subr.c,v 1.5 2002/12/24 07:20:16 jules Exp $
*
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
#include <sys/proc.h>
#include <sys/mount.h>
#if __FreeBSD_version >= 500040
#include <sys/vnode.h>
#else
#include "vnode_4.h"
#endif
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/jail.h>
#include <vm/vm.h>
#include <vm/vm_extern.h>
#if __FreeBSD_version >= 700025
// sys/priv.h add in r164032
// suser_cred(9) declaration also moved there since 700048 (r170587)
#include <sys/priv.h>
#endif
#if 0
#include <miscfs/sysctlfs/sysctlfs.h>
#else
#include "sysctlfs.h"
#endif
#if __FreeBSD_version >= 500040
#define VT_SYSCTLFS "sysctlfs"
#endif
#if __FreeBSD_version >= 500038
#define v_flag v_vflag
#define VROOT VV_ROOT
#endif
#if __FreeBSD_version >= 500018
#define IS_JAILED(P) jailed((P)->p_ucred)
#else
#define IS_JAILED(P) ((P)->p_prison != NULL)
#endif
#if __FreeBSD_version >= 500023
#define uio_procp uio_td->td_proc
#endif
#if __FreeBSD_version >= 502128
#define PRISON_ROOT SUSER_ALLOWJAIL
#endif
static struct scfsnode *scfshead;
static int scfsvplock;
/*
* This function looking for parent directories.
* It's looks only in cached elements, but this should be enough.
*/
int
sysctlfs_findparent(mp, oidp, roidp)
struct mount *mp;
struct sysctl_oid *oidp;
struct sysctl_oid **roidp;
{
struct scfsnode *scfs;
struct vnode *vp;
for (scfs = scfshead; scfs != 0; scfs = scfs->scfs_next) {
vp = SCFSTOV(scfs);
if (scfs->scfs_oidp->oid_arg1 == (void *)oidp->oid_parent &&
vp->v_mount == mp) {
*roidp = scfs->scfs_oidp;
return (0);
}
}
*roidp = SLIST_FIRST(&sysctl__children);
return (0);
}
int
sysctlfs_allocvp(mp, vpp, oidp, scfs_type)
struct mount *mp;
struct vnode **vpp;
struct sysctl_oid *oidp;
scfstype scfs_type;
{
struct scfsnode *scfs;
struct vnode *vp;
struct scfsnode **pp;
struct proc *p = curproc;
int error, exists;
#ifdef DEBUG
printf("function: sysctlfs_allocvp(%p, %p, %p, %d)\n", mp, vpp, oidp, scfs_type);
#endif
error = 0;
loop:
for (scfs = scfshead; scfs != 0; scfs = scfs->scfs_next) {
vp = SCFSTOV(scfs);
if (scfs->scfs_oidp == oidp &&
scfs->scfs_type == scfs_type &&
vp->v_mount == mp) {
if (vget(vp, LK_EXCLUSIVE, curthread))
goto loop;
*vpp = vp;
if (vp->v_type == VREG) {
/*
* Informations about file permissions
* should be updated, because:
* - securelevel could be changed,
* - this function could be called
* for process jailed or not jailed
* with access to this same mount point
* with sysctlfs.
*/
exists = 1;
goto update;
}
return (0);
}
}
exists = 0;
if (scfsvplock & SYSCTLFS_LOCKED) {
scfsvplock |= SYSCTLFS_WANT;
(void) tsleep((caddr_t) &scfsvplock, PINOD, "scfsavp", 0);
goto loop;
}
scfsvplock |= SYSCTLFS_LOCKED;
/*
* Do the MALLOC before the getnewvnode since doing so afterward
* might cause a bogus v_data pointer to get dereferenced
* elsewhere if MALLOC should block.
*/
MALLOC(scfs, struct scfsnode *, sizeof (struct scfsnode), M_TEMP, M_WAITOK);
error = getnewvnode(VT_SYSCTLFS, mp,
#if __FreeBSD_version < 600007
sysctlfs_vnodeop_p,
#else
&sysctlfs_vnodeops,
#endif
vpp);
if (error != 0) {
FREE(scfs, M_TEMP);
printf("getnewvnode failed with error %d\n", error);
goto out;
}
vp = *vpp;
#if __FreeBSD_version >= 700034
vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
#endif
vp->v_data = scfs;
#if __FreeBSD_version >= 700034
error = insmntque(vp, mp);
if(error) {
FREE(scfs, M_TEMP);
printf("insmntque failed with error %d\n", error);
goto out;
}
#endif
scfs->scfs_next = 0;
scfs->scfs_oidp = oidp;
scfs->scfs_type = scfs_type;
scfs->scfs_vnode = vp;
scfs->scfs_flags = 0;
/* Oh my GOD! */
scfs->scfs_fileno = (u_long)oidp & 0xffffff;
switch (scfs_type) {
case Sroot:
scfs->scfs_mode = (VREAD|VEXEC) |
(VREAD|VEXEC) >> 3 |
(VREAD|VEXEC) >> 6;
vp->v_type = VDIR;
vp->v_flag = VROOT;
break;
case Snode:
scfs->scfs_mode = (VREAD|VEXEC) |
(VREAD|VEXEC) >> 3 |
(VREAD|VEXEC) >> 6;
vp->v_type = VDIR;
break;
case Sctl:
vp->v_type = VREG;
update:
scfs->scfs_mode = 0;
if (oidp->oid_kind & CTLFLAG_RD)
scfs->scfs_mode |= VREAD | VREAD >> 3 | VREAD >> 6;
if ((oidp->oid_kind & CTLFLAG_SECURE) &&
#if __FreeBSD_version < 500024
securelevel > 0
#else
securelevel_gt(p->p_ucred, 0)
#endif
) break;
if (IS_JAILED(p) && !(oidp->oid_kind & CTLFLAG_PRISON) &&
!(oidp->oid_kind & CTLFLAG_ANYBODY))
break;
if (oidp->oid_kind & CTLFLAG_WR) {
scfs->scfs_mode |= VWRITE;
if (oidp->oid_kind & CTLFLAG_ANYBODY)
scfs->scfs_mode |= VWRITE >> 3 | VWRITE >> 6;
}
break;
default:
panic("sysctlfs_allocvp");
}
if (exists)
return (0);
/* Adding to sysctlfs vnode list. */
for (pp = &scfshead; *pp; pp = &(*pp)->scfs_next)
continue;
*pp = scfs;
out:
scfsvplock &= ~SYSCTLFS_LOCKED;
if (scfsvplock & SYSCTLFS_WANT) {
scfsvplock &= ~SYSCTLFS_WANT;
wakeup((caddr_t) &scfsvplock);
}
return (error);
}
int
sysctlfs_freevp(vp)
struct vnode *vp;
{
struct scfsnode **scfspp;
struct scfsnode *scfs = VTOSCFS(vp);
for (scfspp = &scfshead; *scfspp != 0; scfspp = &(*scfspp)->scfs_next) {
if (*scfspp == scfs) {
*scfspp = scfs->scfs_next;
break;
}
}
FREE(vp->v_data, M_TEMP);
vp->v_data = 0;
return (0);
}
/*
* Locking and stats
*/
static struct sysctl_lock {
int sl_lock;
int sl_want;
int sl_locked;
} memlock;
static int
sysctl_old_kernel(struct sysctl_req *req, const void *p, size_t l)
{
size_t i = 0;
if (req->oldptr) {
i = l;
if (i > req->oldlen - req->oldidx)
i = req->oldlen - req->oldidx;
if (i > 0)
bcopy(p, (char *)req->oldptr + req->oldidx, i);
}
req->oldidx += l;
if (req->oldptr && i != l)
return (ENOMEM);
return (0);
}
static int
sysctl_new_kernel(struct sysctl_req *req, void *p, size_t l)
{
if (!req->newptr)
return 0;
if (req->newlen - req->newidx < l)
return (EINVAL);
bcopy((char *)req->newptr + req->newidx, p, l);
req->newidx += l;
return (0);
}
static int
sysctlfs_rw(struct vnode *vp, struct uio *uio)
{
#if __FreeBSD_version < 500028
struct proc *curp = uio->uio_procp;
#else
struct thread *curt = uio->uio_td;
#endif
struct scfsnode *scfs = VTOSCFS(vp);
struct sysctl_oid *oidp;
int xlen, error;
static size_t scsize;
static char *buf = NULL;
/* Not needed, really. */
if (uio->uio_rw != UIO_WRITE && uio->uio_rw != UIO_READ)
return (EACCES);
#if 0
while (scfs->scfs_lockowner) {
tsleep(&scfs->scfs_lockowner, PRIBIO, "scfslck", 0);
}
scfs->scfs_lockowner = curproc->p_pid;
#endif
oidp = scfs->scfs_oidp;
#if 1
if ((oidp->oid_kind & CTLTYPE) == CTLTYPE_NODE) {
#ifdef DEBUG
uprintf("Trying %s a node\n", uio->uio_rw == UIO_READ ? "read" : "write");
#endif
error = EISDIR;
goto end;
}
#else
if ((oidp->oid_kind & CTLTYPE) == CTLTYPE_NODE) {
/*
* You can't call a sysctl when it's a node, but has
* no handler. Inform the user that it's a node.
*/
if (oidp->oid_handler == NULL) {
error = EISDIR;
goto end;
}
}
#endif
if (uio->uio_rw == UIO_READ) {
if (buf == NULL) {
error = sysctlfs_getval(
#if __FreeBSD_version < 500028
curp,
#else
curt,
#endif
oidp, &buf, &scsize);
if (error != 0)
goto end;
}
xlen = scsize - 1;
xlen -= uio->uio_offset;
xlen = imin(xlen, uio->uio_resid);
if (xlen <= 0) {
error = 0;
if (oidp->oid_kind & CTLTYPE_STRING)
free(buf, M_TEMP);
buf = NULL;
} else {
error = uiomove(buf + uio->uio_offset, xlen, uio);
}
} else /* if (uio->uio_rw == UIO_WRITE) */ {
/* Everything have to be pushed in one step here. */
scsize = uio->uio_resid;
if (scsize >= MAXSYSCTLBUF || uio->uio_offset != 0)
return (EINVAL);
buf = malloc(scsize, M_TEMP, M_NOWAIT);
if (buf == NULL)
return (ENOMEM);
error = uiomove(buf, scsize, uio);
if (error != 0)
goto endwrite;
if (buf[scsize - 1] == '\n')
buf[scsize--] = '\0';
error = sysctlfs_putval(
#if __FreeBSD_version < 500028
curp,
#else
curt,
#endif
oidp, buf, scsize);
endwrite:
free(buf, M_TEMP);
buf = NULL;
scsize = 0;
}
end:
#if 0
scfs->scfs_lockowner = 0;
wakeup(&scfs->scfs_lockowner);
#endif
if (error == 0)
return (0);
scsize = 0;
if (buf != NULL) {
if (oidp->oid_kind & CTLTYPE_STRING)
free(buf, M_TEMP);
buf = NULL;
}
return (error);
}
int sysctlfs_read(struct vop_read_args *ap) {
return sysctlfs_rw(ap->a_vp, ap->a_uio);
}
int sysctlfs_write(struct vop_write_args *ap) {
return sysctlfs_rw(ap->a_vp, ap->a_uio);
}
int
sysctlfs_sysctl(
#if __FreeBSD_version < 500028
struct proc *p,
#else
struct thread *t,
#endif
struct sysctl_oid *oidp, void *old, size_t *oldlenp,
void *new, size_t newlen, size_t *retval)
{
struct sysctl_req req;
int error;
bzero(&req, sizeof req);
#if __FreeBSD_version < 500028
req.p = p;
#else
req.td = t;
#endif
if (old != NULL)
req.oldptr = old;
if (oldlenp != NULL)
req.oldlen = *oldlenp;
if (new != NULL) {
req.newptr = new;
req.newlen = newlen;
}
req.oldfunc = sysctl_old_kernel;
req.newfunc = sysctl_new_kernel;
req.lock = 1;
/* XXX this should probably be done in a general way */
while (memlock.sl_lock) {
memlock.sl_want = 1;
(void) tsleep((caddr_t)&memlock, PRIBIO+1, "sysctl", 0);
memlock.sl_locked++;
}
memlock.sl_lock = 1;
/* If writing isn't allowed */
if (req.newptr && (!(oidp->oid_kind & CTLFLAG_WR) ||
((oidp->oid_kind & CTLFLAG_SECURE) &&
#if __FreeBSD_version < 500024
securelevel > 0
#elif __FreeBSD_version < 500028
securelevel_gt(p->p_ucred, 0)
#else
securelevel_gt(t->td_ucred, 0)
#endif
))) {
error = EPERM;
goto errend;
}
/* Most likely only root can write */
if (!(oidp->oid_kind & CTLFLAG_ANYBODY) && req.newptr &&
#if __FreeBSD_version < 500028
req.p
#else
req.td
#endif
) {
#if __FreeBSD_version < 500033
error = suser_xxx(0,
#if __FreeBSD_version < 500028
req.p,
#else
req.td,
#endif
(oidp->oid_kind & CTLFLAG_PRISON) ? PRISON_ROOT : 0);
#elif __FreeBSD_version < 700025
error = suser_cred(req.td->td_ucred, (oidp->oid_kind & CTLFLAG_PRISON) ? PRISON_ROOT : 0);
#else
error = priv_check(req.td, (oidp->oid_kind & CTLFLAG_PRISON) ? PRIV_SYSCTL_WRITEJAIL : PRIV_SYSCTL_WRITE);
#endif
if (error != 0)
goto errend;
}
if (!oidp->oid_handler) {
error = EINVAL;
goto errend;
}
#if 0
if ((oidp->oid_kind & CTLTYPE) == CTLTYPE_NODE)
error = oidp->oid_handler(oid, (int *)arg1 + indx, arg2 - indx,
req);
else
#endif
error = oidp->oid_handler(oidp, oidp->oid_arg1, oidp->oid_arg2,
&req);
errend:
if (req.lock == 2)
vsunlock(req.oldptr, req.oldlen);
memlock.sl_lock = 0;
if (memlock.sl_want) {
memlock.sl_want = 0;
wakeup((caddr_t)&memlock);
}
if (error != 0 && error != ENOMEM)
return (error);
if (retval != NULL) {
if (req.oldptr != NULL && req.oldidx > req.oldlen)
*retval = req.oldlen;
else
*retval = req.oldidx;
}
return (error);
}
int
sysctlfs_getval(
#if __FreeBSD_version < 500028
struct proc *p,
#else
struct thread *t,
#endif
struct sysctl_oid *oidp, char **out, size_t *scsize)
{
static char xbuf[32];
int error;
int intval;
long longval;
switch (oidp->oid_kind & CTLTYPE) {
case CTLTYPE_STRING:
/* First of all we have to find sysctl size. */
error = sysctlfs_sysctl(
#if __FreeBSD_version < 500028
p,
#else
t,
#endif
oidp, NULL, 0, NULL, 0, scsize);
if (error != 0)
return (error);
*out = malloc(*scsize + 1, M_TEMP, M_NOWAIT);
if (*out == NULL)
return (ENOMEM);
error = sysctlfs_sysctl(
#if __FreeBSD_version < 500028
p,
#else
t,
#endif
oidp, *out, scsize, NULL, 0, NULL);
if (error != 0)
return (error);
(*out)[*scsize - 1] = '\n';
(*out)[*scsize] = '\0';
(*scsize)++;
return (0);
case CTLTYPE_INT:
*scsize = sizeof(intval);
error = sysctlfs_sysctl(
#if __FreeBSD_version < 500028
p,
#else
t,
#endif
oidp, &intval, scsize, NULL, 0, NULL);
if (error != 0)
return (error);
snprintf(xbuf, sizeof(xbuf), "%d\n", intval);
break;
case CTLTYPE_UINT:
*scsize = sizeof(intval);
error = sysctlfs_sysctl(
#if __FreeBSD_version < 500028
p,
#else
t,
#endif
oidp, &intval, scsize, NULL, 0, NULL);
if (error != 0)
return (error);
snprintf(xbuf, sizeof(xbuf), "%u\n", (u_int)intval);
break;
case CTLTYPE_LONG:
*scsize = sizeof(longval);
error = sysctlfs_sysctl(
#if __FreeBSD_version < 500028
p,
#else
t,
#endif
oidp, &longval, scsize, NULL, 0, NULL);
if (error != 0)
return (error);
snprintf(xbuf, sizeof(xbuf), "%ld\n", longval);
break;
case CTLTYPE_ULONG:
*scsize = sizeof(longval);
error = sysctlfs_sysctl(
#if __FreeBSD_version < 500028
p,
#else
t,
#endif
oidp, &longval, scsize, NULL, 0, NULL);
if (error != 0)
return (error);
snprintf(xbuf, sizeof(xbuf), "%lu\n", (u_long)longval);
break;
default:
return (EOPNOTSUPP);
}
/*
if ((oidp->oid_kind & CTLTYPE_STRING) != 0)
*/
*out = strdup(xbuf, M_TEMP);
*scsize = strlen(xbuf) + 1;
return (0);
}
int
sysctlfs_putval(
#if __FreeBSD_version < 500028
struct proc *p,
#else
struct thread *t,
#endif
struct sysctl_oid *oidp, char *in, size_t scsize)
{
int error;
int intval;
long longval;
switch (oidp->oid_kind & CTLTYPE) {
case CTLTYPE_STRING:
/* First of all we have to find sysctl size. */
error = sysctlfs_sysctl(
#if __FreeBSD_version < 500028
p,
#else
t,
#endif
oidp, NULL, NULL, in, scsize, NULL);
return (error);
case CTLTYPE_INT:
intval = (int)strtol(in, NULL, 0);
scsize = sizeof(intval);
error = sysctlfs_sysctl(
#if __FreeBSD_version < 500028
p,
#else
t,
#endif
oidp, NULL, NULL, &intval, scsize, NULL);
return (error);
case CTLTYPE_UINT:
intval = (int)strtoul(in, NULL, 0);
scsize = sizeof(intval);
error = sysctlfs_sysctl(
#if __FreeBSD_version < 500028
p,
#else
t,
#endif
oidp, NULL, NULL, &intval, scsize, NULL);
return (error);
case CTLTYPE_LONG:
longval = strtol(in, NULL, 0);
scsize = sizeof(longval);
error = sysctlfs_sysctl(
#if __FreeBSD_version < 500028
p,
#else
t,
#endif
oidp, NULL, NULL, &intval, scsize, NULL);
return (error);
case CTLTYPE_ULONG:
longval = strtoul(in, NULL, 0);
scsize = sizeof(longval);
error = sysctlfs_sysctl(
#if __FreeBSD_version < 500028
p,
#else
t,
#endif
oidp, NULL, NULL, &intval, scsize, NULL);
return (error);
default:
return (EOPNOTSUPP);
}
return (0);
}