mirror_zfs/module/os/freebsd/spl/callb.c
Richard Yao 7584fbe846
Cleanup: Switch to strlcpy from strncpy
Coverity found a bug in `zfs_secpolicy_create_clone()` where it is
possible for us to pass an unterminated string when `zfs_get_parent()`
returns an error. Upon inspection, it is clear that using `strlcpy()`
would have avoided this issue.

Looking at the codebase, there are a number of other uses of `strncpy()`
that are unsafe and even when it is used safely, switching to
`strlcpy()` would make the code more readable. Therefore, we switch all
instances where we use `strncpy()` to use `strlcpy()`.

Unfortunately, we do not portably have access to `strlcpy()` in
tests/zfs-tests/cmd/zfs_diff-socket.c because it does not link to
libspl. Modifying the appropriate Makefile.am to try to link to it
resulted in an error from the naming choice used in the file. Trying to
disable the check on the file did not work on FreeBSD because Clang
ignores `#undef` when a definition is provided by `-Dstrncpy(...)=...`.
We workaround that by explictly including the C file from libspl into
the test. This makes things build correctly everywhere.

We add a deprecation warning to `config/Rules.am` and suppress it on the
remaining `strncpy()` usage. `strlcpy()` is not portably avaliable in
tests/zfs-tests/cmd/zfs_diff-socket.c, so we use `snprintf()` there as a
substitute.

This patch does not tackle the related problem of `strcpy()`, which is
even less safe. Thankfully, a quick inspection found that it is used far
more correctly than strncpy() was used. A quick inspection did not find
any problems with `strcpy()` usage outside of zhack, but it should be
said that I only checked around 90% of them.

Lastly, some of the fields in kstat_t varied in size by 1 depending on
whether they were in userspace or in the kernel. The origin of this
discrepancy appears to be 04a479f706 where
it was made for no apparent reason. It conflicts with the comment on
KSTAT_STRLEN, so we shrink the kernel field sizes to match the userspace
field sizes.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Ryan Moeller <ryan@iXsystems.com>
Signed-off-by: Richard Yao <richard.yao@alumni.stonybrook.edu>
Closes #13876
2022-09-27 16:35:29 -07:00

373 lines
9.8 KiB
C

/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or https://opensource.org/licenses/CDDL-1.0.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <sys/types.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/sysmacros.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/mutex.h>
#include <sys/condvar.h>
#include <sys/callb.h>
#include <sys/kmem.h>
#include <sys/cmn_err.h>
#include <sys/debug.h>
#include <sys/kobj.h>
#include <sys/systm.h> /* for delay() */
#include <sys/taskq.h> /* For TASKQ_NAMELEN */
#include <sys/kernel.h>
#define CB_MAXNAME TASKQ_NAMELEN
/*
* The callb mechanism provides generic event scheduling/echoing.
* A callb function is registered and called on behalf of the event.
*/
typedef struct callb {
struct callb *c_next; /* next in class or on freelist */
kthread_id_t c_thread; /* ptr to caller's thread struct */
char c_flag; /* info about the callb state */
uchar_t c_class; /* this callb's class */
kcondvar_t c_done_cv; /* signal callb completion */
boolean_t (*c_func)(void *, int);
/* cb function: returns true if ok */
void *c_arg; /* arg to c_func */
char c_name[CB_MAXNAME+1]; /* debug:max func name length */
} callb_t;
/*
* callb c_flag bitmap definitions
*/
#define CALLB_FREE 0x0
#define CALLB_TAKEN 0x1
#define CALLB_EXECUTING 0x2
/*
* Basic structure for a callb table.
* All callbs are organized into different class groups described
* by ct_class array.
* The callbs within a class are single-linked and normally run by a
* serial execution.
*/
typedef struct callb_table {
kmutex_t ct_lock; /* protect all callb states */
callb_t *ct_freelist; /* free callb structures */
boolean_t ct_busy; /* B_TRUE prevents additions */
kcondvar_t ct_busy_cv; /* to wait for not busy */
int ct_ncallb; /* num of callbs allocated */
callb_t *ct_first_cb[NCBCLASS]; /* ptr to 1st callb in a class */
} callb_table_t;
int callb_timeout_sec = CPR_KTHREAD_TIMEOUT_SEC;
static callb_id_t callb_add_common(boolean_t (*)(void *, int),
void *, int, char *, kthread_id_t);
static callb_table_t callb_table; /* system level callback table */
static callb_table_t *ct = &callb_table;
static kmutex_t callb_safe_mutex;
callb_cpr_t callb_cprinfo_safe = {
&callb_safe_mutex, CALLB_CPR_ALWAYS_SAFE, 0, {0, 0} };
/*
* Init all callb tables in the system.
*/
static void
callb_init(void *dummy __unused)
{
callb_table.ct_busy = B_FALSE; /* mark table open for additions */
mutex_init(&callb_safe_mutex, NULL, MUTEX_DEFAULT, NULL);
mutex_init(&callb_table.ct_lock, NULL, MUTEX_DEFAULT, NULL);
}
static void
callb_fini(void *dummy __unused)
{
callb_t *cp;
int i;
mutex_enter(&ct->ct_lock);
for (i = 0; i < 16; i++) {
while ((cp = ct->ct_freelist) != NULL) {
ct->ct_freelist = cp->c_next;
ct->ct_ncallb--;
kmem_free(cp, sizeof (callb_t));
}
if (ct->ct_ncallb == 0)
break;
/* Not all callbacks finished, waiting for the rest. */
mutex_exit(&ct->ct_lock);
tsleep(ct, 0, "callb", hz / 4);
mutex_enter(&ct->ct_lock);
}
if (ct->ct_ncallb > 0)
printf("%s: Leaked %d callbacks!\n", __func__, ct->ct_ncallb);
mutex_exit(&ct->ct_lock);
mutex_destroy(&callb_safe_mutex);
mutex_destroy(&callb_table.ct_lock);
}
/*
* callout_add() is called to register func() be called later.
*/
static callb_id_t
callb_add_common(boolean_t (*func)(void *arg, int code),
void *arg, int class, char *name, kthread_id_t t)
{
callb_t *cp;
ASSERT3S(class, <, NCBCLASS);
mutex_enter(&ct->ct_lock);
while (ct->ct_busy)
cv_wait(&ct->ct_busy_cv, &ct->ct_lock);
if ((cp = ct->ct_freelist) == NULL) {
ct->ct_ncallb++;
cp = (callb_t *)kmem_zalloc(sizeof (callb_t), KM_SLEEP);
}
ct->ct_freelist = cp->c_next;
cp->c_thread = t;
cp->c_func = func;
cp->c_arg = arg;
cp->c_class = (uchar_t)class;
cp->c_flag |= CALLB_TAKEN;
#ifdef ZFS_DEBUG
if (strlen(name) > CB_MAXNAME)
cmn_err(CE_WARN, "callb_add: name of callback function '%s' "
"too long -- truncated to %d chars",
name, CB_MAXNAME);
#endif
(void) strlcpy(cp->c_name, name, sizeof (cp->c_name));
/*
* Insert the new callb at the head of its class list.
*/
cp->c_next = ct->ct_first_cb[class];
ct->ct_first_cb[class] = cp;
mutex_exit(&ct->ct_lock);
return ((callb_id_t)cp);
}
/*
* The default function to add an entry to the callback table. Since
* it uses curthread as the thread identifier to store in the table,
* it should be used for the normal case of a thread which is calling
* to add ITSELF to the table.
*/
callb_id_t
callb_add(boolean_t (*func)(void *arg, int code),
void *arg, int class, char *name)
{
return (callb_add_common(func, arg, class, name, curthread));
}
/*
* A special version of callb_add() above for use by threads which
* might be adding an entry to the table on behalf of some other
* thread (for example, one which is constructed but not yet running).
* In this version the thread id is an argument.
*/
callb_id_t
callb_add_thread(boolean_t (*func)(void *arg, int code),
void *arg, int class, char *name, kthread_id_t t)
{
return (callb_add_common(func, arg, class, name, t));
}
/*
* callout_delete() is called to remove an entry identified by id
* that was originally placed there by a call to callout_add().
* return -1 if fail to delete a callb entry otherwise return 0.
*/
int
callb_delete(callb_id_t id)
{
callb_t **pp;
callb_t *me = (callb_t *)id;
mutex_enter(&ct->ct_lock);
for (;;) {
pp = &ct->ct_first_cb[me->c_class];
while (*pp != NULL && *pp != me)
pp = &(*pp)->c_next;
#ifdef ZFS_DEBUG
if (*pp != me) {
cmn_err(CE_WARN, "callb delete bogus entry 0x%p",
(void *)me);
mutex_exit(&ct->ct_lock);
return (-1);
}
#endif /* DEBUG */
/*
* It is not allowed to delete a callb in the middle of
* executing otherwise, the callb_execute() will be confused.
*/
if (!(me->c_flag & CALLB_EXECUTING))
break;
cv_wait(&me->c_done_cv, &ct->ct_lock);
}
/* relink the class list */
*pp = me->c_next;
/* clean up myself and return the free callb to the head of freelist */
me->c_flag = CALLB_FREE;
me->c_next = ct->ct_freelist;
ct->ct_freelist = me;
mutex_exit(&ct->ct_lock);
return (0);
}
/*
* class: indicates to execute all callbs in the same class;
* code: optional argument for the callb functions.
* return: = 0: success
* != 0: ptr to string supplied when callback was registered
*/
void *
callb_execute_class(int class, int code)
{
callb_t *cp;
void *ret = NULL;
ASSERT3S(class, <, NCBCLASS);
mutex_enter(&ct->ct_lock);
for (cp = ct->ct_first_cb[class];
cp != NULL && ret == 0; cp = cp->c_next) {
while (cp->c_flag & CALLB_EXECUTING)
cv_wait(&cp->c_done_cv, &ct->ct_lock);
/*
* cont if the callb is deleted while we're sleeping
*/
if (cp->c_flag == CALLB_FREE)
continue;
cp->c_flag |= CALLB_EXECUTING;
#ifdef CALLB_DEBUG
printf("callb_execute: name=%s func=%p arg=%p\n",
cp->c_name, (void *)cp->c_func, (void *)cp->c_arg);
#endif /* CALLB_DEBUG */
mutex_exit(&ct->ct_lock);
/* If callback function fails, pass back client's name */
if (!(*cp->c_func)(cp->c_arg, code))
ret = cp->c_name;
mutex_enter(&ct->ct_lock);
cp->c_flag &= ~CALLB_EXECUTING;
cv_broadcast(&cp->c_done_cv);
}
mutex_exit(&ct->ct_lock);
return (ret);
}
/*
* callers make sure no recursive entries to this func.
* dp->cc_lockp is registered by callb_add to protect callb_cpr_t structure.
*
* When calling to stop a kernel thread (code == CB_CODE_CPR_CHKPT) we
* use a cv_timedwait() in case the kernel thread is blocked.
*
* Note that this is a generic callback handler for daemon CPR and
* should NOT be changed to accommodate any specific requirement in a daemon.
* Individual daemons that require changes to the handler shall write
* callback routines in their own daemon modules.
*/
boolean_t
callb_generic_cpr(void *arg, int code)
{
callb_cpr_t *cp = (callb_cpr_t *)arg;
clock_t ret = 0; /* assume success */
mutex_enter(cp->cc_lockp);
switch (code) {
case CB_CODE_CPR_CHKPT:
cp->cc_events |= CALLB_CPR_START;
#ifdef CPR_NOT_THREAD_SAFE
while (!(cp->cc_events & CALLB_CPR_SAFE))
/* cv_timedwait() returns -1 if it times out. */
if ((ret = cv_reltimedwait(&cp->cc_callb_cv,
cp->cc_lockp, (callb_timeout_sec * hz),
TR_CLOCK_TICK)) == -1)
break;
#endif
break;
case CB_CODE_CPR_RESUME:
cp->cc_events &= ~CALLB_CPR_START;
cv_signal(&cp->cc_stop_cv);
break;
}
mutex_exit(cp->cc_lockp);
return (ret != -1);
}
/*
* The generic callback function associated with kernel threads which
* are always considered safe.
*/
boolean_t
callb_generic_cpr_safe(void *arg, int code)
{
(void) arg, (void) code;
return (B_TRUE);
}
/*
* Prevent additions to callback table.
*/
void
callb_lock_table(void)
{
mutex_enter(&ct->ct_lock);
ASSERT(!ct->ct_busy);
ct->ct_busy = B_TRUE;
mutex_exit(&ct->ct_lock);
}
/*
* Allow additions to callback table.
*/
void
callb_unlock_table(void)
{
mutex_enter(&ct->ct_lock);
ASSERT(ct->ct_busy);
ct->ct_busy = B_FALSE;
cv_broadcast(&ct->ct_busy_cv);
mutex_exit(&ct->ct_lock);
}
SYSINIT(sol_callb, SI_SUB_DRIVERS, SI_ORDER_FIRST, callb_init, NULL);
SYSUNINIT(sol_callb, SI_SUB_DRIVERS, SI_ORDER_FIRST, callb_fini, NULL);