diff --git a/include/sys/nvpair.h b/include/sys/nvpair.h index 66362a9db..6bec9702e 100644 --- a/include/sys/nvpair.h +++ b/include/sys/nvpair.h @@ -267,6 +267,8 @@ _SYS_NVPAIR_H int nvlist_lookup_double(const nvlist_t *, const char *, double *); #endif +_SYS_NVPAIR_H int nvlist_snprintf(char *, size_t, nvlist_t *, int); + _SYS_NVPAIR_H int nvlist_lookup_nvpair(nvlist_t *, const char *, nvpair_t **); _SYS_NVPAIR_H int nvlist_lookup_nvpair_embedded_index(nvlist_t *, const char *, nvpair_t **, int *, const char **); diff --git a/include/sys/zfs_debug.h b/include/sys/zfs_debug.h index 871936da1..4d4cd4c39 100644 --- a/include/sys/zfs_debug.h +++ b/include/sys/zfs_debug.h @@ -39,6 +39,8 @@ extern "C" { #define FALSE 0 #endif +#include + extern int zfs_flags; extern int zfs_recover; extern int zfs_free_leak_on_eio; @@ -104,6 +106,24 @@ extern void zfs_panic_recover(const char *fmt, ...); extern void zfs_dbgmsg_init(void); extern void zfs_dbgmsg_fini(void); +/* + * When printing an nvlist, print one beginning line with the file/func/line + * number and the text "nvlist :" followed by all the nvlist lines + * without the file/fun/line number. This makes the nvlist lines easy to read. + */ +#define zfs_dbgmsg_nvlist(nv) \ + if (zfs_dbgmsg_enable) { \ + zfs_dbgmsg("nvlist "#nv":"); \ + __zfs_dbgmsg_nvlist(nv); \ + } + +#define zfs_dbgmsg(...) \ + if (zfs_dbgmsg_enable) \ + __dprintf(B_FALSE, __FILE__, __func__, __LINE__, __VA_ARGS__) + + +extern void __zfs_dbgmsg_nvlist(nvlist_t *nv); + #ifndef _KERNEL extern int dprintf_find_string(const char *string); extern void zfs_dbgmsg_print(int fd, const char *tag); diff --git a/lib/libnvpair/libnvpair.abi b/lib/libnvpair/libnvpair.abi index e3eacb195..a7f51f494 100644 --- a/lib/libnvpair/libnvpair.abi +++ b/lib/libnvpair/libnvpair.abi @@ -196,6 +196,7 @@ + @@ -967,6 +968,13 @@ + + + + + + + @@ -1126,12 +1134,6 @@ - - - - - - @@ -1140,6 +1142,11 @@ + + + + + @@ -1180,12 +1187,6 @@ - - - - - - @@ -1398,11 +1399,6 @@ - - - - - @@ -1418,6 +1414,11 @@ + + + + + @@ -2194,6 +2195,7 @@ + @@ -2306,6 +2308,10 @@ + + + + diff --git a/lib/libnvpair/libnvpair.c b/lib/libnvpair/libnvpair.c index fb26ed638..41be7c678 100644 --- a/lib/libnvpair/libnvpair.c +++ b/lib/libnvpair/libnvpair.c @@ -783,160 +783,6 @@ nvlist_prt(nvlist_t *nvl, nvlist_prtctl_t pctl) nvlist_print_with_indent(nvl, pctl); } -#define NVP(elem, type, vtype, ptype, format) { \ - vtype value; \ -\ - (void) nvpair_value_##type(elem, &value); \ - (void) printf("%*s%s: " format "\n", indent, "", \ - nvpair_name(elem), (ptype)value); \ -} - -#define NVPA(elem, type, vtype, ptype, format) { \ - uint_t i, count; \ - vtype *value; \ -\ - (void) nvpair_value_##type(elem, &value, &count); \ - for (i = 0; i < count; i++) { \ - (void) printf("%*s%s[%d]: " format "\n", indent, "", \ - nvpair_name(elem), i, (ptype)value[i]); \ - } \ -} - -/* - * Similar to nvlist_print() but handles arrays slightly differently. - */ -void -dump_nvlist(nvlist_t *list, int indent) -{ - nvpair_t *elem = NULL; - boolean_t bool_value; - nvlist_t *nvlist_value; - nvlist_t **nvlist_array_value; - uint_t i, count; - - if (list == NULL) { - return; - } - - while ((elem = nvlist_next_nvpair(list, elem)) != NULL) { - switch (nvpair_type(elem)) { - case DATA_TYPE_BOOLEAN: - (void) printf("%*s%s\n", indent, "", nvpair_name(elem)); - break; - - case DATA_TYPE_BOOLEAN_VALUE: - (void) nvpair_value_boolean_value(elem, &bool_value); - (void) printf("%*s%s: %s\n", indent, "", - nvpair_name(elem), bool_value ? "true" : "false"); - break; - - case DATA_TYPE_BYTE: - NVP(elem, byte, uchar_t, int, "%u"); - break; - - case DATA_TYPE_INT8: - NVP(elem, int8, int8_t, int, "%d"); - break; - - case DATA_TYPE_UINT8: - NVP(elem, uint8, uint8_t, int, "%u"); - break; - - case DATA_TYPE_INT16: - NVP(elem, int16, int16_t, int, "%d"); - break; - - case DATA_TYPE_UINT16: - NVP(elem, uint16, uint16_t, int, "%u"); - break; - - case DATA_TYPE_INT32: - NVP(elem, int32, int32_t, long, "%ld"); - break; - - case DATA_TYPE_UINT32: - NVP(elem, uint32, uint32_t, ulong_t, "%lu"); - break; - - case DATA_TYPE_INT64: - NVP(elem, int64, int64_t, longlong_t, "%lld"); - break; - - case DATA_TYPE_UINT64: - NVP(elem, uint64, uint64_t, u_longlong_t, "%llu"); - break; - - case DATA_TYPE_STRING: - NVP(elem, string, const char *, const char *, "'%s'"); - break; - - case DATA_TYPE_BYTE_ARRAY: - NVPA(elem, byte_array, uchar_t, int, "%u"); - break; - - case DATA_TYPE_INT8_ARRAY: - NVPA(elem, int8_array, int8_t, int, "%d"); - break; - - case DATA_TYPE_UINT8_ARRAY: - NVPA(elem, uint8_array, uint8_t, int, "%u"); - break; - - case DATA_TYPE_INT16_ARRAY: - NVPA(elem, int16_array, int16_t, int, "%d"); - break; - - case DATA_TYPE_UINT16_ARRAY: - NVPA(elem, uint16_array, uint16_t, int, "%u"); - break; - - case DATA_TYPE_INT32_ARRAY: - NVPA(elem, int32_array, int32_t, long, "%ld"); - break; - - case DATA_TYPE_UINT32_ARRAY: - NVPA(elem, uint32_array, uint32_t, ulong_t, "%lu"); - break; - - case DATA_TYPE_INT64_ARRAY: - NVPA(elem, int64_array, int64_t, longlong_t, "%lld"); - break; - - case DATA_TYPE_UINT64_ARRAY: - NVPA(elem, uint64_array, uint64_t, u_longlong_t, - "%llu"); - break; - - case DATA_TYPE_STRING_ARRAY: - NVPA(elem, string_array, const char *, const char *, - "'%s'"); - break; - - case DATA_TYPE_NVLIST: - (void) nvpair_value_nvlist(elem, &nvlist_value); - (void) printf("%*s%s:\n", indent, "", - nvpair_name(elem)); - dump_nvlist(nvlist_value, indent + 4); - break; - - case DATA_TYPE_NVLIST_ARRAY: - (void) nvpair_value_nvlist_array(elem, - &nvlist_array_value, &count); - for (i = 0; i < count; i++) { - (void) printf("%*s%s[%u]:\n", indent, "", - nvpair_name(elem), i); - dump_nvlist(nvlist_array_value[i], indent + 4); - } - break; - - default: - (void) printf(dgettext(TEXT_DOMAIN, "bad config type " - "%d for %s\n"), nvpair_type(elem), - nvpair_name(elem)); - } - } -} - /* * ====================================================================== * | | @@ -1291,3 +1137,30 @@ nvpair_value_match(nvpair_t *nvp, int ai, const char *value, const char **ep) { return (nvpair_value_match_regex(nvp, ai, value, NULL, ep)); } + +/* + * Similar to nvlist_print() but handles arrays slightly differently. + */ +void +dump_nvlist(nvlist_t *list, int indent) +{ + int len; + char *buf; + + len = nvlist_snprintf(NULL, 0, list, indent); + len++; /* Add null terminator */ + + buf = malloc(len); + if (buf == NULL) + return; + + (void) nvlist_snprintf(buf, len, list, indent); + + /* + * fputs does not have limitations on the size of the buffer being + * printed (unlike printf). + */ + fputs(buf, stdout); + + free(buf); +} diff --git a/lib/libuutil/libuutil.abi b/lib/libuutil/libuutil.abi index 7cb92ac9f..0052f0d47 100644 --- a/lib/libuutil/libuutil.abi +++ b/lib/libuutil/libuutil.abi @@ -652,6 +652,7 @@ + @@ -763,6 +764,10 @@ + + + + @@ -1011,16 +1016,9 @@ - - - - - - - @@ -1130,25 +1128,6 @@ - - - - - - - - - - - - - - - - - - - @@ -1157,23 +1136,12 @@ - - - - - - - - - - - @@ -1185,9 +1153,8 @@ - + - diff --git a/lib/libzfs/libzfs.abi b/lib/libzfs/libzfs.abi index 7e6a9211b..26ae5d2f1 100644 --- a/lib/libzfs/libzfs.abi +++ b/lib/libzfs/libzfs.abi @@ -631,7 +631,7 @@ - + @@ -1170,6 +1170,7 @@ + @@ -1275,6 +1276,10 @@ + + + + @@ -2482,6 +2487,13 @@ + + + + + + + @@ -2698,7 +2710,7 @@ - + @@ -2750,6 +2762,12 @@ + + + + + + @@ -2767,7 +2785,7 @@ - + @@ -2840,37 +2858,37 @@ - + - + - + - + - + - + - + - + - + - + - + @@ -3069,9 +3087,6 @@ - - - @@ -3084,10 +3099,6 @@ - - - - @@ -3212,24 +3223,6 @@ - - - - - - - - - - - - - - - - - - @@ -3245,7 +3238,6 @@ - @@ -3481,13 +3473,9 @@ - - - - @@ -3501,6 +3489,7 @@ + @@ -3520,7 +3509,6 @@ - @@ -3546,13 +3534,6 @@ - - - - - - - @@ -3625,10 +3606,6 @@ - - - - @@ -3639,6 +3616,14 @@ + + + + + + + + @@ -3712,9 +3697,8 @@ - + - @@ -4084,7 +4068,6 @@ - @@ -4537,14 +4520,6 @@ - - - - - - - - @@ -8375,6 +8350,11 @@ + + + + + @@ -9539,8 +9519,8 @@ - - + + diff --git a/lib/libzfs_core/libzfs_core.abi b/lib/libzfs_core/libzfs_core.abi index 6a9c20a2b..f6aa9a742 100644 --- a/lib/libzfs_core/libzfs_core.abi +++ b/lib/libzfs_core/libzfs_core.abi @@ -651,6 +651,7 @@ + @@ -762,6 +763,10 @@ + + + + @@ -982,13 +987,6 @@ - - - - - - - @@ -1092,42 +1090,12 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1144,9 +1112,8 @@ - + - @@ -2111,7 +2078,7 @@ - + @@ -2163,6 +2130,12 @@ + + + + + + @@ -2180,7 +2153,7 @@ - + @@ -2253,37 +2226,37 @@ - + - + - + - + - + - + - + - + - + - + - + diff --git a/lib/libzfsbootenv/libzfsbootenv.abi b/lib/libzfsbootenv/libzfsbootenv.abi index 5903d5dcb..bf866b0fa 100644 --- a/lib/libzfsbootenv/libzfsbootenv.abi +++ b/lib/libzfsbootenv/libzfsbootenv.abi @@ -1,6 +1,6 @@ - + diff --git a/lib/libzpool/Makefile.am b/lib/libzpool/Makefile.am index 8875393dc..829d626d4 100644 --- a/lib/libzpool/Makefile.am +++ b/lib/libzpool/Makefile.am @@ -178,6 +178,7 @@ nodist_libzpool_la_SOURCES = \ module/zfs/zfeature.c \ module/zfs/zfs_byteswap.c \ module/zfs/zfs_chksum.c \ + module/zfs/zfs_debug_common.c \ module/zfs/zfs_fm.c \ module/zfs/zfs_fuid.c \ module/zfs/zfs_ratelimit.c \ diff --git a/module/Kbuild.in b/module/Kbuild.in index 569c3a869..edb475c7d 100644 --- a/module/Kbuild.in +++ b/module/Kbuild.in @@ -413,6 +413,7 @@ ZFS_OBJS := \ zfeature.o \ zfs_byteswap.o \ zfs_chksum.o \ + zfs_debug_common.o \ zfs_fm.o \ zfs_fuid.o \ zfs_impl.o \ diff --git a/module/nvpair/nvpair.c b/module/nvpair/nvpair.c index db1b595a6..811cfc87d 100644 --- a/module/nvpair/nvpair.c +++ b/module/nvpair/nvpair.c @@ -3681,6 +3681,240 @@ nvs_xdr(nvstream_t *nvs, nvlist_t *nvl, char *buf, size_t *buflen) return (err); } +#define NVP(buf, size, len, buf_end, elem, type, vtype, ptype, format) { \ + vtype value; \ + int rc; \ +\ + (void) nvpair_value_##type(elem, &value); \ + rc = snprintf(buf, size, "%*s%s: " format "\n", indent, "", \ + nvpair_name(elem), (ptype)value); \ + if (rc < 0) \ + return (rc); \ + size = MAX((int)size - rc, 0); \ + buf = size == 0 ? NULL : buf_end - size; \ + len += rc; \ +} + +#define NVPA(buf, size, len, buf_end, elem, type, vtype, ptype, format) \ +{ \ + uint_t i, count; \ + vtype *value; \ + int rc; \ +\ + (void) nvpair_value_##type(elem, &value, &count); \ + for (i = 0; i < count; i++) { \ + rc = snprintf(buf, size, "%*s%s[%d]: " format "\n", indent, \ + "", nvpair_name(elem), i, (ptype)value[i]); \ + if (rc < 0) \ + return (rc); \ + size = MAX((int)size - rc, 0); \ + buf = size == 0 ? NULL : buf_end - size; \ + len += rc; \ + } \ +} + +/* + * snprintf() version of dump_nvlist() + * + * Works just like snprintf(), but with an nvlist and indent count as args. + * + * Output is similar to nvlist_print() but handles arrays slightly differently. + * + * Return value matches C99 snprintf() return value conventions. + */ +int +nvlist_snprintf(char *buf, size_t size, nvlist_t *list, int indent) +{ + nvpair_t *elem = NULL; + boolean_t bool_value; + nvlist_t *nvlist_value; + nvlist_t **nvlist_array_value; + uint_t i, count; + int len = 0; + int rc; + char *buf_end = &buf[size]; + + if (list == NULL) + return (0); + + while ((elem = nvlist_next_nvpair(list, elem)) != NULL) { + switch (nvpair_type(elem)) { + case DATA_TYPE_BOOLEAN: + rc = snprintf(buf, size, "%*s%s\n", indent, "", + nvpair_name(elem)); + if (rc < 0) + return (rc); + size = MAX((int)size - rc, 0); + buf = size == 0 ? NULL : buf_end - size; + len += rc; + break; + + case DATA_TYPE_BOOLEAN_VALUE: + (void) nvpair_value_boolean_value(elem, &bool_value); + rc = snprintf(buf, size, "%*s%s: %s\n", indent, "", + nvpair_name(elem), bool_value ? "true" : "false"); + if (rc < 0) + return (rc); + size = MAX((int)size - rc, 0); + buf = size == 0 ? NULL : buf_end - size; + len += rc; + break; + + case DATA_TYPE_BYTE: + NVP(buf, size, len, buf_end, elem, byte, uchar_t, int, + "%u"); + break; + + case DATA_TYPE_INT8: + NVP(buf, size, len, buf_end, elem, int8, int8_t, int, + "%d"); + break; + + case DATA_TYPE_UINT8: + NVP(buf, size, len, buf_end, elem, uint8, uint8_t, int, + "%u"); + break; + + case DATA_TYPE_INT16: + NVP(buf, size, len, buf_end, elem, int16, int16_t, int, + "%d"); + break; + + case DATA_TYPE_UINT16: + NVP(buf, size, len, buf_end, elem, uint16, uint16_t, + int, "%u"); + break; + + case DATA_TYPE_INT32: + NVP(buf, size, len, buf_end, elem, int32, int32_t, + long, "%ld"); + break; + + case DATA_TYPE_UINT32: + NVP(buf, size, len, buf_end, elem, uint32, uint32_t, + ulong_t, "%lu"); + break; + + case DATA_TYPE_INT64: + NVP(buf, size, len, buf_end, elem, int64, int64_t, + longlong_t, "%lld"); + break; + + case DATA_TYPE_UINT64: + NVP(buf, size, len, buf_end, elem, uint64, uint64_t, + u_longlong_t, "%llu"); + break; + + case DATA_TYPE_STRING: + NVP(buf, size, len, buf_end, elem, string, const char *, + const char *, "'%s'"); + break; + + case DATA_TYPE_BYTE_ARRAY: + NVPA(buf, size, len, buf_end, elem, byte_array, uchar_t, + int, "%u"); + break; + + case DATA_TYPE_INT8_ARRAY: + NVPA(buf, size, len, buf_end, elem, int8_array, int8_t, + int, "%d"); + break; + + case DATA_TYPE_UINT8_ARRAY: + NVPA(buf, size, len, buf_end, elem, uint8_array, + uint8_t, int, "%u"); + break; + + case DATA_TYPE_INT16_ARRAY: + NVPA(buf, size, len, buf_end, elem, int16_array, + int16_t, int, "%d"); + break; + + case DATA_TYPE_UINT16_ARRAY: + NVPA(buf, size, len, buf_end, elem, uint16_array, + uint16_t, int, "%u"); + break; + + case DATA_TYPE_INT32_ARRAY: + NVPA(buf, size, len, buf_end, elem, int32_array, + int32_t, long, "%ld"); + break; + + case DATA_TYPE_UINT32_ARRAY: + NVPA(buf, size, len, buf_end, elem, uint32_array, + uint32_t, ulong_t, "%lu"); + break; + + case DATA_TYPE_INT64_ARRAY: + NVPA(buf, size, len, buf_end, elem, int64_array, + int64_t, longlong_t, "%lld"); + break; + + case DATA_TYPE_UINT64_ARRAY: + NVPA(buf, size, len, buf_end, elem, uint64_array, + uint64_t, u_longlong_t, "%llu"); + break; + + case DATA_TYPE_STRING_ARRAY: + NVPA(buf, size, len, buf_end, elem, string_array, + const char *, const char *, "'%s'"); + break; + + case DATA_TYPE_NVLIST: + (void) nvpair_value_nvlist(elem, &nvlist_value); + + rc = snprintf(buf, size, "%*s%s:\n", indent, "", + nvpair_name(elem)); + if (rc < 0) + return (rc); + size = MAX((int)size - rc, 0); + buf = size == 0 ? NULL : buf_end - size; + len += rc; + + rc = nvlist_snprintf(buf, size, nvlist_value, + indent + 4); + if (rc < 0) + return (rc); + size = MAX((int)size - rc, 0); + buf = size == 0 ? NULL : buf_end - size; + len += rc; + break; + + case DATA_TYPE_NVLIST_ARRAY: + (void) nvpair_value_nvlist_array(elem, + &nvlist_array_value, &count); + for (i = 0; i < count; i++) { + rc = snprintf(buf, size, "%*s%s[%u]:\n", + indent, "", nvpair_name(elem), i); + if (rc < 0) + return (rc); + size = MAX((int)size - rc, 0); + buf = size == 0 ? NULL : buf_end - size; + len += rc; + + rc = nvlist_snprintf(buf, size, + nvlist_array_value[i], indent + 4); + if (rc < 0) + return (rc); + size = MAX((int)size - rc, 0); + buf = size == 0 ? NULL : buf_end - size; + len += rc; + } + break; + + default: + rc = snprintf(buf, size, "bad config type %d for %s\n", + nvpair_type(elem), nvpair_name(elem)); + if (rc < 0) + return (rc); + size = MAX((int)size - rc, 0); + buf = size == 0 ? NULL : buf_end - size; + len += rc; + } + } + return (len); +} + EXPORT_SYMBOL(nv_alloc_init); EXPORT_SYMBOL(nv_alloc_reset); EXPORT_SYMBOL(nv_alloc_fini); @@ -3766,6 +4000,8 @@ EXPORT_SYMBOL(nvlist_lookup_pairs); EXPORT_SYMBOL(nvlist_lookup_nvpair); EXPORT_SYMBOL(nvlist_exists); +EXPORT_SYMBOL(nvlist_snprintf); + /* processing nvpair */ EXPORT_SYMBOL(nvpair_name); EXPORT_SYMBOL(nvpair_type); diff --git a/module/zfs/zfs_debug_common.c b/module/zfs/zfs_debug_common.c new file mode 100644 index 000000000..cff0dc01c --- /dev/null +++ b/module/zfs/zfs_debug_common.c @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: CDDL-1.0 +/* + * 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 (c) 2025 by Lawrence Livermore National Security, LLC. + */ +/* + * This file contains zfs_dbgmsg() specific functions that are not OS or + * userspace specific. + */ +#if !defined(_KERNEL) +#include +#endif + +#include +#include +#include + +/* + * Given a multi-line string, print out one of the lines and return a pointer + * to the next line. Lines are demarcated by '\n'. Note: this modifies the + * input string (buf[]). + * + * This function is meant to be used in a loop like: + * while (buf != NULL) + * buf = kernel_print_one_line(buf); + * + * This function is useful for printing large, multi-line text buffers. + * + * Returns the pointer to the beginning of the next line in buf[], or NULL + * if it's the last line, or nothing more to print. + */ +static char * +zfs_dbgmsg_one_line(char *buf) +{ + char *nl; + if (!buf) + return (NULL); + + nl = strchr(buf, '\n'); + if (nl == NULL) { + __zfs_dbgmsg(buf); + return (NULL); /* done */ + } + *nl = '\0'; + __zfs_dbgmsg(buf); + + return (nl + 1); +} + +/* + * Dump an nvlist tree to dbgmsg. + * + * This is the zfs_dbgmsg version of userspace's dump_nvlist() from libnvpair. + */ +void +__zfs_dbgmsg_nvlist(nvlist_t *nv) +{ + int len; + char *buf; + + len = nvlist_snprintf(NULL, 0, nv, 4); + len++; /* Add null terminator */ + + buf = vmem_alloc(len, KM_SLEEP); + if (buf == NULL) + return; + + (void) nvlist_snprintf(buf, len, nv, 4); + + while (buf != NULL) + buf = zfs_dbgmsg_one_line(buf); + + vmem_free(buf, len); +} + +#ifdef _KERNEL +EXPORT_SYMBOL(__zfs_dbgmsg_nvlist); +#endif