diff --git a/sys/compat/linuxkpi/common/include/linux/moduleparam.h b/sys/compat/linuxkpi/common/include/linux/moduleparam.h --- a/sys/compat/linuxkpi/common/include/linux/moduleparam.h +++ b/sys/compat/linuxkpi/common/include/linux/moduleparam.h @@ -36,6 +36,7 @@ #ifndef LINUXKPI_PARAM_PARENT #define LINUXKPI_PARAM_PARENT _compat_linuxkpi +#define LINUXKPI_PARAM_PARENT_ENV "compat.linuxkpi." #endif #ifndef LINUXKPI_PARAM_PREFIX @@ -109,11 +110,40 @@ LINUXKPI_PARAM_NAME(name), LINUXKPI_PARAM_PERM(perm), &(var), 0, \ LINUXKPI_PARAM_DESC(name))) +extern int linux_sysctl_handle_charp(SYSCTL_HANDLER_ARGS); +extern void linux_tunable_charp_init(void *); +extern void linux_free_charp(void *); + +struct tunable_charp { + const char *path; + char **var; +}; +#define TUNABLE_CHARP(path, var) \ + static struct tunable_charp __CONCAT(__tunable_charp_, __LINE__) = { \ + (path), \ + (var), \ + }; \ + SYSINIT(__CONCAT(__Tunable_init_, __LINE__), \ + SI_SUB_KMEM + 1, SI_ORDER_ANY, linux_tunable_charp_init, \ + &__CONCAT(__tunable_charp_, __LINE__)) + +/* Oid for a character pointer. */ +#define SYSCTL_CHARP(parent, nbr, name, access, arg, descr) \ + SYSCTL_OID(parent, nbr, name, \ + CTLTYPE_STRING | CTLFLAG_MPSAFE | CTLFLAG_NOFETCH | (access), \ + arg, 0, linux_sysctl_handle_charp, "A", descr); \ + CTASSERT(((access) & CTLTYPE) == 0 || \ + ((access) & SYSCTL_CT_ASSERT_MASK) == CTLTYPE_STRING) + #define LINUXKPI_PARAM_charp(name, var, perm) \ extern const char LINUXKPI_PARAM_DESC(name)[]; \ - LINUXKPI_PARAM_PASS(SYSCTL_STRING(LINUXKPI_PARAM_PARENT, OID_AUTO, \ - LINUXKPI_PARAM_NAME(name), LINUXKPI_PARAM_PERM(perm), &(var), 0, \ - LINUXKPI_PARAM_DESC(name))) + LINUXKPI_PARAM_PASS(SYSCTL_CHARP(LINUXKPI_PARAM_PARENT, OID_AUTO, \ + LINUXKPI_PARAM_NAME(name), LINUXKPI_PARAM_PERM(perm), &(var), \ + LINUXKPI_PARAM_DESC(name))); \ + TUNABLE_CHARP(LINUXKPI_PARAM_PARENT_ENV __XSTRING(LINUXKPI_PARAM_NAME(name)), \ + &(var)); \ + SYSUNINIT(linux_free_charp_##name, SI_SUB_KMEM + 1, SI_ORDER_ANY, \ + linux_free_charp, &(var)) #define module_param_string(name, str, len, perm) \ extern const char LINUXKPI_PARAM_DESC(name)[]; \ diff --git a/sys/compat/linuxkpi/common/src/linux_compat.c b/sys/compat/linuxkpi/common/src/linux_compat.c --- a/sys/compat/linuxkpi/common/src/linux_compat.c +++ b/sys/compat/linuxkpi/common/src/linux_compat.c @@ -2735,6 +2735,118 @@ #endif } +static struct sx sysctlcharplock; + +int +linux_sysctl_handle_charp(SYSCTL_HANDLER_ARGS) +{ + char **charpp = arg1; + char *tmparg; + int error = 0; + + /* + * If the sysctl isn't writable, microoptimise and treat it as a + * read-only string. + * In ddb, don't worry about trying to make a malloced snapshot. + */ + if ((oidp->oid_kind & CTLFLAG_WR) == 0 || kdb_active) { + size_t outlen; + + outlen = *charpp != NULL ? strlen(*charpp) + 1 : 0; + tmparg = req->oldptr != NULL ? *charpp : NULL; + error = SYSCTL_OUT(req, tmparg, outlen); + } else { + size_t outlen; + + if (req->oldptr != NULL) { + sx_slock(&sysctlcharplock); + if (*charpp == NULL) { + tmparg = NULL; + outlen = 0; + } else { + outlen = strlen(*charpp) + 1; + tmparg = malloc(outlen, M_KMALLOC, M_WAITOK); + memcpy(tmparg, *charpp, outlen); + } + sx_sunlock(&sysctlcharplock); + } else { + tmparg = NULL; + sx_slock(&sysctlcharplock); + outlen = *charpp != NULL ? strlen(*charpp) + 1 : 0; + sx_sunlock(&sysctlcharplock); + } + error = SYSCTL_OUT(req, tmparg, outlen); + free(tmparg, M_KMALLOC); + } + if (error != 0 || req->newptr == NULL) + return (error); + + /* XXX Limit the maximum requested length ? */ + if (req->newlen - req->newidx < 0) { + error = EINVAL; + } else if (req->newlen - req->newidx == 0) { + char *old; + + sx_xlock(&sysctlcharplock); + old = *charpp; + *charpp = NULL; + sx_xunlock(&sysctlcharplock); + free(old, M_KMALLOC); + } else { + char *old, *new; + size_t inlen; + + inlen = req->newlen - req->newidx; + new = malloc(inlen + 1, M_KMALLOC, M_WAITOK); + error = SYSCTL_IN(req, new, inlen); + if (error != 0) { + free(new, M_KMALLOC); + return (error); + } + + sx_xlock(&sysctlcharplock); + old = *charpp; + new[inlen] = '\0'; + *charpp = new; + sx_xunlock(&sysctlcharplock); + free(old, M_KMALLOC); + req->newidx += inlen; + } + return (error); +} + +void +linux_tunable_charp_init(void *data) +{ + struct tunable_charp *d = (struct tunable_charp *)data; + char *buf, *penv; + size_t len; + + MPASS(*d->var == NULL); + + penv = kern_getenv(d->path); + if (penv == NULL) + return; + if ((len = strlen(penv)) == 0) + goto free; + + buf = malloc(len + 1, M_KMALLOC, M_WAITOK); + memcpy(buf, penv, len); + buf[len] = '\0'; + *d->var = buf; +free: + freeenv(penv); +} + +void +linux_free_charp(void *arg) +{ + char **p = arg; + + free(*p, M_KMALLOC); + *p = NULL; +} + static void linux_compat_init(void *arg) { @@ -2778,6 +2890,7 @@ } #endif rw_init(&linux_vma_lock, "lkpi-vma-lock"); + sx_init(&sysctlcharplock, "lkpi-sysctl-charp-handler"); rootoid = SYSCTL_ADD_ROOT_NODE(NULL, OID_AUTO, "sys", CTLFLAG_RD|CTLFLAG_MPSAFE, NULL, "sys");