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,42 @@ 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; + int size; +}; +#define TUNABLE_CHARP(path, var, size) \ + static struct tunable_charp __CONCAT(__tunable_charp_, __LINE__) = { \ + (path), \ + (var), \ + (size), \ + }; \ + 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, len, descr) \ + SYSCTL_OID(parent, nbr, name, \ + CTLTYPE_STRING | CTLFLAG_MPSAFE | CTLFLAG_NOFETCH | (access), \ + arg, len, 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_PASS(SYSCTL_CHARP(LINUXKPI_PARAM_PARENT, OID_AUTO, \ LINUXKPI_PARAM_NAME(name), LINUXKPI_PARAM_PERM(perm), &(var), 0, \ - LINUXKPI_PARAM_DESC(name))) + LINUXKPI_PARAM_DESC(name))); \ + TUNABLE_CHARP(LINUXKPI_PARAM_PARENT_ENV __XSTRING(LINUXKPI_PARAM_NAME(name)), \ + &(var), 1024); /* XXX Arbitrary size */ \ + 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,129 @@ #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) { + for (;;) { + size_t len; + sx_slock(&sysctlcharplock); + len = *charpp != NULL ? strlen(*charpp) + 1 : 0; + sx_sunlock(&sysctlcharplock); + if (len == 0) { + tmparg = NULL; + outlen = 0; + break; + } + + tmparg = malloc(len, M_KMALLOC, M_WAITOK); + sx_slock(&sysctlcharplock); + outlen = strlen(*charpp) + 1; + if (outlen > len) { + sx_sunlock(&sysctlcharplock); + free(tmparg, M_KMALLOC); + continue; /* Retry a snapshot */ + } + 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 || !req->newptr) + 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 *tmp, *p; + size_t size; + + MPASS(*d->var == NULL); + tmp = malloc(d->size, M_KMALLOC, M_WAITOK | M_ZERO); + TUNABLE_STR_FETCH(d->path, tmp, d->size); + + size = strlen(tmp) + 1; + if (size == 1) { + free(tmp, M_KMALLOC); + return; + } + + p = malloc(size, M_KMALLOC, M_WAITOK); + memcpy(p, tmp, size); + free(tmp, M_KMALLOC); + *d->var = p; +} + + +void +linux_free_charp(void *arg) +{ + char *p = *(char **)arg; + free(p, M_KMALLOC); +} + static void linux_compat_init(void *arg) { @@ -2778,6 +2901,7 @@ } #endif rw_init(&linux_vma_lock, "lkpi-vma-lock"); + sx_init(&sysctlcharplock, "linux sysctl charp handler"); rootoid = SYSCTL_ADD_ROOT_NODE(NULL, OID_AUTO, "sys", CTLFLAG_RD|CTLFLAG_MPSAFE, NULL, "sys");