#include <sys/param.h>
#include <sys/kenv.h>
#include <sys/kernel.h>
#include <sys/libkern.h>
#include <sys/malloc.h>
#include <sys/caps.h>
#include <sys/spinlock.h>
#include <sys/spinlock2.h>
#include <sys/sysmsg.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
char *kern_envp;
static char **kenv_dynp;
static int kenv_isdynamic;
static struct spinlock kenv_dynlock;
MALLOC_DEFINE(M_KENV, "kenv", "kernel environment dynamic storage");
#define KENV_DYNMAXNUM 512
static char *kenv_getstring_dynamic(const char *name, int *idx);
static char *kenv_getstring_static(const char *name);
static char *kernenv_next(char *cp);
int
sys_kenv(struct sysmsg *sysmsg, const struct kenv_args *uap)
{
char *name, *value, *buffer = NULL;
size_t len, done, needed, buflen;
int error, i;
KASSERT(kenv_isdynamic, ("kenv: kenv_isdynamic = 0"));
error = 0;
if (uap->what == KENV_DUMP) {
done = needed = 0;
buflen = uap->len;
if (buflen > KENV_DYNMAXNUM * (KENV_MNAMELEN + KENV_MVALLEN + 2))
buflen = KENV_DYNMAXNUM *
(KENV_MNAMELEN + KENV_MVALLEN + 2);
if (uap->len > 0 && uap->value != NULL)
buffer = kmalloc(buflen, M_TEMP, M_WAITOK|M_ZERO);
spin_lock(&kenv_dynlock);
for (i = 0; kenv_dynp[i] != NULL; i++) {
len = strlen(kenv_dynp[i]) + 1;
needed += len;
len = min(len, buflen - done);
if (uap->value != NULL && buffer != NULL && len > 0) {
bcopy(kenv_dynp[i], buffer + done, len);
done += len;
}
}
spin_unlock(&kenv_dynlock);
if (buffer != NULL) {
error = copyout(buffer, uap->value, done);
kfree(buffer, M_TEMP);
}
sysmsg->sysmsg_result = ((done == needed) ? 0 : needed);
return (error);
}
switch (uap->what) {
case KENV_SET:
error = caps_priv_check_self(SYSCAP_NOKENV_WR);
if (error)
return (error);
break;
case KENV_UNSET:
error = caps_priv_check_self(SYSCAP_NOKENV_WR);
if (error)
return (error);
break;
}
name = kmalloc(KENV_MNAMELEN + 1, M_TEMP, M_WAITOK);
error = copyinstr(uap->name, name, KENV_MNAMELEN + 1, NULL);
if (error)
goto done;
switch (uap->what) {
case KENV_GET:
value = kgetenv(name);
if (value == NULL) {
error = ENOENT;
goto done;
}
len = strlen(value) + 1;
if (len > uap->len)
len = uap->len;
error = copyout(value, uap->value, len);
kfreeenv(value);
if (error)
goto done;
sysmsg-> sysmsg_result = len;
break;
case KENV_SET:
len = uap->len;
if (len < 1) {
error = EINVAL;
goto done;
}
if (len > KENV_MVALLEN + 1)
len = KENV_MVALLEN + 1;
value = kmalloc(len, M_TEMP, M_WAITOK);
error = copyinstr(uap->value, value, len, NULL);
if (error) {
kfree(value, M_TEMP);
goto done;
}
ksetenv(name, value);
kfree(value, M_TEMP);
break;
case KENV_UNSET:
error = kunsetenv(name);
if (error)
error = ENOENT;
break;
default:
error = EINVAL;
break;
}
done:
kfree(name, M_TEMP);
return (error);
}
static char *
kenv_getstring_dynamic(const char *name, int *idx)
{
char *cp;
int len, i;
len = strlen(name);
for (i = 0; (cp = kenv_dynp[i]) != NULL; i++) {
if ((strncmp(cp, name, len) == 0) && (cp[len] == '=')) {
if (idx != NULL)
*idx = i;
return(cp + len + 1);
}
}
return(NULL);
}
static char *
kenv_getstring_static(const char *name)
{
char *cp, *ep;
int len;
for (cp = kern_envp; cp != NULL; cp = kernenv_next(cp)) {
for (ep = cp; (*ep != '=') && (*ep != 0); ep++)
;
if (*ep != '=')
continue;
len = ep - cp;
ep++;
if (!strncmp(name, cp, len) && name[len] == 0)
return(ep);
}
return(NULL);
}
char *
kgetenv(const char *name)
{
char buf[KENV_MNAMELEN + 1 + KENV_MVALLEN + 1];
char *cp, *ret;
int len;
if (kenv_isdynamic) {
spin_lock(&kenv_dynlock);
cp = kenv_getstring_dynamic(name, NULL);
if (cp != NULL) {
strcpy(buf, cp);
spin_unlock(&kenv_dynlock);
len = strlen(buf) + 1;
ret = kmalloc(len, M_KENV, M_WAITOK);
strcpy(ret, buf);
} else {
spin_unlock(&kenv_dynlock);
ret = NULL;
}
} else
ret = kenv_getstring_static(name);
return(ret);
}
int
ksetenv(const char *name, const char *value)
{
char *cp, *buf, *oldenv;
int namelen, vallen, i;
if (kenv_isdynamic) {
namelen = strlen(name) + 1;
vallen = strlen(value) + 1;
if ((namelen > KENV_MNAMELEN) || (vallen > KENV_MVALLEN))
return(-1);
buf = kmalloc(namelen + vallen, M_KENV, M_WAITOK);
ksprintf(buf, "%s=%s", name, value);
spin_lock(&kenv_dynlock);
cp = kenv_getstring_dynamic(name, &i);
if (cp != NULL) {
oldenv = kenv_dynp[i];
kenv_dynp[i] = buf;
spin_unlock(&kenv_dynlock);
kfree(oldenv, M_KENV);
} else {
for (i = 0; (cp = kenv_dynp[i]) != NULL; i++)
;
if (i < 0 || i >= (KENV_DYNMAXNUM - 1)) {
kfree(buf, M_KENV);
spin_unlock(&kenv_dynlock);
return(-1);
}
kenv_dynp[i] = buf;
kenv_dynp[i + 1] = NULL;
spin_unlock(&kenv_dynlock);
}
return(0);
} else {
kprintf("WARNING: ksetenv: dynamic array not created yet\n");
return(-1);
}
}
int
kunsetenv(const char *name)
{
char *cp, *oldenv;
int i, j;
if (kenv_isdynamic) {
spin_lock(&kenv_dynlock);
cp = kenv_getstring_dynamic(name, &i);
if (cp != NULL) {
oldenv = kenv_dynp[i];
for (j = i + 1; kenv_dynp[j] != NULL; j++)
kenv_dynp[i++] = kenv_dynp[j];
kenv_dynp[i] = NULL;
spin_unlock(&kenv_dynlock);
kfree(oldenv, M_KENV);
return(0);
}
spin_unlock(&kenv_dynlock);
return(-1);
} else {
kprintf("WARNING: kunsetenv: dynamic array not created yet\n");
return(-1);
}
}
void
kfreeenv(char *env)
{
if (kenv_isdynamic)
kfree(env, M_KENV);
}
int
ktestenv(const char *name)
{
char *cp;
if (kenv_isdynamic) {
spin_lock(&kenv_dynlock);
cp = kenv_getstring_dynamic(name, NULL);
spin_unlock(&kenv_dynlock);
} else
cp = kenv_getstring_static(name);
if (cp != NULL)
return(1);
return(0);
}
int
kgetenv_string(const char *name, char *data, int size)
{
char *tmp;
tmp = kgetenv(name);
if (tmp != NULL) {
strncpy(data, tmp, size);
data[size - 1] = 0;
kfreeenv(tmp);
return (1);
} else
return (0);
}
int
kgetenv_int(const char *name, int *data)
{
quad_t tmp;
int rval;
rval = kgetenv_quad(name, &tmp);
if (rval)
*data = (int) tmp;
return (rval);
}
int
kgetenv_long(const char *name, long *data)
{
quad_t tmp;
int rval;
rval = kgetenv_quad(name, &tmp);
if (rval)
*data = (long)tmp;
return (rval);
}
int
kgetenv_ulong(const char *name, unsigned long *data)
{
quad_t tmp;
int rval;
rval = kgetenv_quad(name, &tmp);
if (rval)
*data = (unsigned long) tmp;
return (rval);
}
int
kgetenv_quad(const char *name, quad_t *data)
{
char* value;
char* vtp;
quad_t iv;
if ((value = kgetenv(name)) == NULL)
return(0);
iv = strtoq(value, &vtp, 0);
switch(*vtp) {
case 't':
case 'T':
iv <<= 10;
case 'g':
case 'G':
iv <<= 10;
case 'm':
case 'M':
iv <<= 10;
case 'k':
case 'K':
iv <<= 10;
++vtp;
break;
default:
break;
}
if ((vtp == value) || (*vtp != '\0')) {
kfreeenv(value);
return(0);
}
*data = iv;
kfreeenv(value);
return(1);
}
static int
sysctl_kenv_boot(SYSCTL_HANDLER_ARGS)
{
int *name = (int *)arg1;
u_int namelen = arg2;
char *cp;
int i, error;
if (kern_envp == NULL)
return(ENOENT);
name++;
namelen--;
if (namelen != 1)
return(EINVAL);
cp = kern_envp;
for (i = 0; i < name[0]; i++) {
cp = kernenv_next(cp);
if (cp == NULL)
break;
}
if (cp == NULL)
return(ENOENT);
error = SYSCTL_OUT(req, cp, strlen(cp) + 1);
return (error);
}
SYSCTL_NODE(_kern, OID_AUTO, environment, CTLFLAG_RD, sysctl_kenv_boot,
"boottime (static) kernel environment space");
static char *
kernenv_next(char *cp)
{
if (cp != NULL) {
while (*cp != 0)
cp++;
cp++;
if (*cp == 0)
cp = NULL;
}
return(cp);
}
void
tunable_int_init(void *data)
{
struct tunable_int *d = (struct tunable_int *)data;
TUNABLE_INT_FETCH(d->path, d->var);
}
void
tunable_long_init(void *data)
{
struct tunable_long *d = (struct tunable_long *)data;
TUNABLE_LONG_FETCH(d->path, d->var);
}
void
tunable_ulong_init(void *data)
{
struct tunable_ulong *d = (struct tunable_ulong *)data;
TUNABLE_ULONG_FETCH(d->path, d->var);
}
void
tunable_quad_init(void *data)
{
struct tunable_quad *d = (struct tunable_quad *)data;
TUNABLE_QUAD_FETCH(d->path, d->var);
}
void
tunable_str_init(void *data)
{
struct tunable_str *d = (struct tunable_str *)data;
TUNABLE_STR_FETCH(d->path, d->var, d->size);
}
static void
kenv_init(void *dummy)
{
char *cp;
int len, i;
kenv_dynp = kmalloc(KENV_DYNMAXNUM * sizeof(char *), M_KENV,
M_WAITOK | M_ZERO);
for (i = 0, cp = kern_envp; cp != NULL; cp = kernenv_next(cp)) {
len = strlen(cp) + 1;
if (i < (KENV_DYNMAXNUM - 1)) {
kenv_dynp[i] = kmalloc(len, M_KENV, M_WAITOK);
strcpy(kenv_dynp[i++], cp);
} else
kprintf("WARNING: kenv: exhausted dynamic storage, "
"ignoring string %s\n", cp);
}
kenv_dynp[i] = NULL;
spin_init(&kenv_dynlock, "kenvdynlock");
kenv_isdynamic = 1;
}
SYSINIT(kenv, SI_BOOT1_POST, SI_ORDER_ANY, kenv_init, NULL);