From bb2210b76c8757cb4263c881a350f2b7921a67ff Mon Sep 17 00:00:00 2001
From: Ian Moffett <ian@osmora.org>
Date: Wed, 13 Nov 2024 04:33:33 -0500
Subject: kernel: sysctl: Make sysctl(2) more flexible

This commit makes various changes such as updating how readonly and
writable entries are stored and how entry lookups are made to improve
flexibility.

Signed-off-by: Ian Moffett <ian@osmora.org>
---
 sys/kern/kern_sysctl.c | 130 ++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 97 insertions(+), 33 deletions(-)

diff --git a/sys/kern/kern_sysctl.c b/sys/kern/kern_sysctl.c
index f3f436c..a1bd91a 100644
--- a/sys/kern/kern_sysctl.c
+++ b/sys/kern/kern_sysctl.c
@@ -29,6 +29,7 @@
 
 #include <sys/sysctl.h>
 #include <sys/syscall.h>
+#include <sys/param.h>
 #include <sys/errno.h>
 #include <sys/systm.h>
 #include <vm/dynalloc.h>
@@ -38,16 +39,50 @@
         HYRA_VERSION " "              \
         HYRA_BUILDDATE
 
-static const char *hyra = "Hyra";
-static const char *hyra_version = HYRA_VERSION;
-static const char *osrelease = HYRA_RELEASE;
+static char hyra[] = "Hyra";
+static char hyra_version[] = HYRA_VERSION;
+static char osrelease[] = HYRA_RELEASE;
 
-static struct sysctl_entry common_kerntab[] = {
-    [KERN_OSTYPE] = { KERN_OSTYPE, SYSCTL_OPTYPE_STR_RO, &hyra },
+/*
+ * XXX: Readonly values can be statically allocated as they won't
+ *      ever be changed. Values that are not readonly *must* be dynamically
+ *      allocated through dynalloc(9).
+ */
+static struct sysctl_entry common_optab[] = {
+    [KERN_OSTYPE] = { KERN_OSTYPE, SYSCTL_OPTYPE_STR_RO, hyra },
     [KERN_OSRELEASE] = { KERN_OSRELEASE, SYSCTL_OPTYPE_STR_RO, &osrelease },
     [KERN_VERSION] = { KERN_VERSION, SYSCTL_OPTYPE_STR_RO, &hyra_version },
 };
 
+static int
+sysctl_write(struct sysctl_entry *entry, void *p, size_t len)
+{
+    void *tmp;
+
+    /* Allocate a new value if needed */
+    if (entry->data == NULL) {
+        entry->data = dynalloc(len);
+        goto done;
+    }
+    if (entry->data == NULL) {
+        return -ENOMEM;
+    }
+
+    /* If we already have data allocated, resize it */
+    if ((tmp = entry->data) != NULL) {
+        entry->data = dynrealloc(entry->data, len);
+        goto done;
+    }
+    if (entry->data == NULL) {
+        entry->data = tmp;
+        return -ENOMEM;
+    }
+
+done:
+    memcpy(entry->data, p, len);
+    return 0;
+}
+
 /*
  * Helper for sys_sysctl()
  */
@@ -122,50 +157,79 @@ int
 sysctl(struct sysctl_args *args)
 {
     struct sysctl_entry *tmp;
-    char *tmp_str;
+    char *tmp_str = NULL;
+    int tmp_int = 0;
     size_t oldlen, len;
+    int name;
 
     if (args->name == NULL) {
         return -EINVAL;
     }
 
-    if (args->oldlenp == NULL) {
+    /* If oldlenp is not set, oldp must be the same */
+    if (args->oldlenp == NULL && args->oldp != NULL) {
         return -EINVAL;
     }
 
-    /* TODO: Support writable values */
-    if (args->newp != NULL) {
+    /* Get the name and make sure it is in range */
+    name = *args->name;
+    if (name >= NELEM(common_optab) || name < 0) {
+        return -EINVAL;
+    }
+
+    tmp = &common_optab[name];
+    oldlen = (args->oldlenp == NULL) ? 0 : *args->oldlenp;
+
+    /*
+     * Make sure we aren't trying to write readonly
+     * entries to avoid issues...
+     */
+    switch (tmp->optype) {
+    case SYSCTL_OPTYPE_STR_RO:
+    case SYSCTL_OPTYPE_INT_RO:
+        if (args->newp != NULL) {
+            return -EACCES;
+        }
+    }
+
+    /* If the value is unknown, bail out */
+    if (args->oldp != NULL && tmp->data == NULL) {
         return -ENOTSUP;
     }
 
     /*
-     * TODO: We only support toplevel names as of now and should
-     *       expand this in the future. As of now, users will only
-     *       be able to access kern.* entries.
+     * Now it is time to extract the value from this sysctl
+     * entry if requested by the user.
      */
-    switch (*args->name) {
-    case KERN_OSTYPE:
-        tmp = &common_kerntab[KERN_OSTYPE];
-        tmp_str = *((char **)tmp->data);
-        len = strlen(tmp_str);
-        break;
-    case KERN_OSRELEASE:
-        tmp = &common_kerntab[KERN_OSRELEASE];
-        tmp_str = *((char **)tmp->data);
-        len = strlen(tmp_str);
-        break;
-    case KERN_VERSION:
-        tmp = &common_kerntab[KERN_VERSION];
-        tmp_str = *((char **)tmp->data);
-        len = strlen(tmp_str);
-        break;
-    default:
-        return -EINVAL;
+    if (args->oldp != NULL) {
+        switch (tmp->optype) {
+        /* Check for strings */
+        case SYSCTL_OPTYPE_STR_RO:
+        case SYSCTL_OPTYPE_STR:
+            tmp_str = (char *)tmp->data;
+            len = strlen(tmp_str);
+            break;
+        /* Check for ints */
+        case SYSCTL_OPTYPE_INT_RO:
+        case SYSCTL_OPTYPE_INT:
+            tmp_int = *((int *)tmp->data);
+            len = sizeof(tmp_int);
+            break;
+        }
+    }
+
+    /* If newp is set, write the new value */
+    if (args->newp != NULL) {
+        sysctl_write(tmp, args->newp, args->newlen);
+    }
+
+    /* Copy back old value if oldp is not NULL */
+    if (args->oldp != NULL && tmp_str != NULL) {
+        memcpy(args->oldp, tmp_str, oldlen);
+    } else if (args->oldp != NULL) {
+        memcpy(args->oldp, &tmp_int, oldlen);
     }
 
-    /* Copy new data */
-    oldlen = *args->oldlenp;
-    memcpy(args->oldp, tmp_str, oldlen);
     return (len > oldlen) ? -ENOMEM : 0;
 }
 
-- 
cgit v1.2.3