/*	$NetBSD: linux_sysctl.c,v 1.7 2003/01/18 21:21:37 thorpej Exp $	*/

/*-
 * Copyright (c) 1982, 1986, 1989, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Mike Karels at Berkeley Software Design, Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	@(#)kern_sysctl.c	8.9 (Berkeley) 5/20/95
 */

/*
 * sysctl system call.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: linux_sysctl.c,v 1.7 2003/01/18 21:21:37 thorpej Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/proc.h>
#include <sys/mount.h>
#include <sys/sysctl.h>
#include <sys/sa.h>
#include <sys/syscallargs.h>

#include <compat/linux/common/linux_types.h>
#include <compat/linux/common/linux_signal.h>

#include <compat/linux/linux_syscallargs.h>
#include <compat/linux/common/linux_sysctl.h>
#include <compat/linux/common/linux_exec.h>

int linux_kern_sysctl(int *, u_int, void *, size_t *, void *, size_t,
    struct proc *);
int linux_vm_sysctl(int *, u_int, void *, size_t *, void *, size_t,
    struct proc *);
int linux_net_sysctl(int *, u_int, void *, size_t *, void *, size_t,
    struct proc *);
int linux_proc_sysctl(int *, u_int, void *, size_t *, void *, size_t,
    struct proc *);
int linux_fs_sysctl(int *, u_int, void *, size_t *, void *, size_t,
    struct proc *);
#ifdef DEBUG
int linux_debug_sysctl(int *, u_int, void *, size_t *, void *, size_t,
    struct proc *);
#endif
int linux_dev_sysctl(int *, u_int, void *, size_t *, void *, size_t,
    struct proc *);
int linux_bus_sysctl(int *, u_int, void *, size_t *, void *, size_t,
    struct proc *);

int
linux_sys___sysctl(struct lwp *l, void *v, register_t *retval)
{
	struct linux_sys___sysctl_args /* {
		syscallarg(struct linux___sysctl *) lsp;
	} */ *uap = v;
	struct proc *p = l->l_proc;
	struct linux___sysctl ls;
	int error;
	size_t savelen = 0, oldlen = 0;
	sysctlfn *fn;
	int name[CTL_MAXNAME];
	size_t *oldlenp;


	if ((error = copyin(SCARG(uap, lsp), &ls, sizeof ls)))
		return error;
	/*
	 * all top-level sysctl names are non-terminal
	 */
	if (ls.nlen > CTL_MAXNAME || ls.nlen < 2)
		return (EINVAL);
	error = copyin(ls.name, &name, ls.nlen * sizeof(int));
	if (error)
		return (error);

	/*
	 * For all but CTL_PROC, must be root to change a value.
	 * For CTL_PROC, must be root, or owner of the proc (and not suid),
	 * this is checked in proc_sysctl() (once we know the targer proc).
	 */
	if (ls.newval != NULL && name[0] != CTL_PROC &&
		    (error = suser(p->p_ucred, &p->p_acflag)))
			return error;

	switch (name[0]) {
	case LINUX_CTL_KERN:
		fn = linux_kern_sysctl;
		break;
	case LINUX_CTL_VM:
		fn = linux_vm_sysctl;
		break;
	case LINUX_CTL_NET:
		fn = linux_net_sysctl;
		break;
	case LINUX_CTL_PROC:
		fn = linux_proc_sysctl;
		break;
	case LINUX_CTL_FS:
		fn = linux_fs_sysctl;
		break;
#ifdef DEBUG
	case LINUX_CTL_DEBUG:
		fn = linux_debug_sysctl;
		break;
#endif
	case LINUX_CTL_DEV:
		fn = linux_dev_sysctl;
		break;
	case LINUX_CTL_BUS:
		fn = linux_bus_sysctl;
		break;
	default:
		return (EOPNOTSUPP);
	}

	/*
	 * XXX Hey, we wire `oldval', but what about `newval'?
	 */
	oldlenp = ls.oldlenp;
	if (oldlenp) {
		if ((error = copyin(oldlenp, &oldlen, sizeof(oldlen))))
			return (error);
		oldlenp = &oldlen;
	}
	if (ls.oldval != NULL) {
		error = uvm_vslock(p, ls.oldval, oldlen,
		    VM_PROT_READ|VM_PROT_WRITE);
		savelen = oldlen;
	}
	error = (*fn)(name + 1, ls.nlen - 1, ls.oldval, oldlenp, ls.newval,
	    ls.newlen, p);
	if (ls.oldval != NULL)
		uvm_vsunlock(p, ls.oldval, savelen);
	if (error)
		return (error);
	if (ls.oldlenp)
		error = copyout(&oldlen, ls.oldlenp, sizeof(oldlen));
	return (error);
}

char linux_sysname[128] = "Linux";
#if defined(__i386__) || defined(__powerpc__)
char linux_release[128] = "2.4.18";
char linux_version[128] = "#0 Wed Feb 20 20:00:02 CET 2002";
#else
char linux_release[128] = "2.0.38";
char linux_version[128] = "#0 Sun Nov 11 11:11:11 MET 2000";
#endif

/*
 * kernel related system variables.
 */
int
linux_kern_sysctl(int *name, u_int nlen, void *oldp, size_t *oldlenp,
    void *newp, size_t newlen, struct proc *p)
{
	/*
	 * Note that we allow writing into this, so that userland
	 * programs can setup things as they see fit. This is suboptimal.
	 */
	switch (name[0]) {
	case LINUX_KERN_OSTYPE:
		return sysctl_string(oldp, oldlenp, newp, newlen,
		    linux_sysname, sizeof(linux_sysname));
	case LINUX_KERN_OSRELEASE:
		return sysctl_string(oldp, oldlenp, newp, newlen,
		    linux_release, sizeof(linux_release));
	case LINUX_KERN_VERSION:
		return sysctl_string(oldp, oldlenp, newp, newlen,
		    linux_version, sizeof(linux_version));
	default:
		return EOPNOTSUPP;
	}
}

/*
 * kernel related system variables.
 */
int
linux_sysctl(int *name, u_int nlen, void *oldp, size_t *oldlenp,
    void *newp, size_t newlen, struct proc *p)
{
	if (nlen != 2 || name[0] != EMUL_LINUX_KERN)
		return EOPNOTSUPP;

	/*
	 * Note that we allow writing into this, so that userland
	 * programs can setup things as they see fit. This is suboptimal.
	 */
	switch (name[1]) {
	case EMUL_LINUX_KERN_OSTYPE:
		return sysctl_string(oldp, oldlenp, newp, newlen,
		    linux_sysname, sizeof(linux_sysname));
	case EMUL_LINUX_KERN_OSRELEASE:
		return sysctl_string(oldp, oldlenp, newp, newlen,
		    linux_release, sizeof(linux_release));
	case EMUL_LINUX_KERN_VERSION:
		return sysctl_string(oldp, oldlenp, newp, newlen,
		    linux_version, sizeof(linux_version));
	default:
		return EOPNOTSUPP;
	}
}

/*
 * hardware related system variables.
 */
int
linux_vm_sysctl(int *name, u_int nlen, void *oldp, size_t *oldlenp,
    void *newp, size_t newlen, struct proc *p)
{
	return (EOPNOTSUPP);
}

int
linux_net_sysctl(int *name, u_int nlen, void *oldp, size_t *oldlenp,
    void *newp, size_t newlen, struct proc *p)
{
	return (EOPNOTSUPP);
}

int
linux_proc_sysctl(int *name, u_int nlen, void *oldp, size_t *oldlenp,
    void *newp, size_t newlen, struct proc *p)
{
	return (EOPNOTSUPP);
}

int
linux_fs_sysctl(int *name, u_int nlen, void *oldp, size_t *oldlenp,
    void *newp, size_t newlen, struct proc *p)
{
	return (EOPNOTSUPP);
}
#ifdef DEBUG
int
linux_debug_sysctl(int *name, u_int nlen, void *oldp, size_t *oldlenp,
    void *newp, size_t newlen, struct proc *p)
{
	return (EOPNOTSUPP);
}
#endif /* DEBUG */

int
linux_dev_sysctl(int *name, u_int nlen, void *oldp, size_t *oldlenp,
    void *newp, size_t newlen, struct proc *p)
{
	return (EOPNOTSUPP);
}

int
linux_bus_sysctl(int *name, u_int nlen, void *oldp, size_t *oldlenp,
    void *newp, size_t newlen, struct proc *p)
{
	return (EOPNOTSUPP);
}
