| /* |
| * (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 |
| |
| #ifndef SYSCTL_CHILDREN |
| #define SYSCTL_CHILDREN(OID_PTR) ((struct sysctl_oid_list *)(OID_PTR)->oid_arg1) |
| #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 (SYSCTL_CHILDREN(scfs->scfs_oidp) == 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); |
| } |