diff options
Diffstat (limited to 'usr/src')
47 files changed, 3921 insertions, 749 deletions
diff --git a/usr/src/boot/Makefile.version b/usr/src/boot/Makefile.version index 88ac6f56a0..30c6982235 100644 --- a/usr/src/boot/Makefile.version +++ b/usr/src/boot/Makefile.version @@ -34,4 +34,4 @@ LOADER_VERSION = 1.1 # Use date like formatting here, YYYY.MM.DD.XX, without leading zeroes. # The version is processed from left to right, the version number can only # be increased. -BOOT_VERSION = $(LOADER_VERSION)-2021.04.03.1 +BOOT_VERSION = $(LOADER_VERSION)-2021.05.12.1 diff --git a/usr/src/boot/sys/boot/efi/libefi/time_event.c b/usr/src/boot/sys/boot/efi/libefi/time_event.c index d76af38bf9..afb30c1d9e 100644 --- a/usr/src/boot/sys/boot/efi/libefi/time_event.c +++ b/usr/src/boot/sys/boot/efi/libefi/time_event.c @@ -1,4 +1,4 @@ -/*- +/* * Copyright (c) 2016 Andrew Turner * All rights reserved. * @@ -39,7 +39,7 @@ static void time_update(EFI_EVENT event, void *context) { - curtime += 10; + curtime++; } void @@ -49,8 +49,8 @@ efi_time_init(void) /* Create a timer event */ BS->CreateEvent(EVT_TIMER | EVT_NOTIFY_SIGNAL, TPL_CALLBACK, time_update, 0, &time_event); - /* Use a 10ms timer */ - BS->SetTimer(time_event, TimerPeriodic, 100000); + /* Use a 1s timer */ + BS->SetTimer(time_event, TimerPeriodic, 10000000); } void @@ -67,7 +67,7 @@ time(time_t *tloc) { time_t t; - t = curtime / 1000; + t = curtime; if (tloc != NULL) *tloc = t; @@ -77,5 +77,5 @@ time(time_t *tloc) time_t getsecs(void) { - return time(0); + return (time(0)); } diff --git a/usr/src/cmd/zdb/zdb.c b/usr/src/cmd/zdb/zdb.c index d367120589..c62d5c2f53 100644 --- a/usr/src/cmd/zdb/zdb.c +++ b/usr/src/cmd/zdb/zdb.c @@ -2676,6 +2676,8 @@ dump_l2arc_log_entries(uint64_t log_entries, (u_longlong_t)L2BLK_GET_PREFETCH((&le[j])->le_prop)); (void) printf("|\t\t\t\taddress: %llu\n", (u_longlong_t)le[j].le_daddr); + (void) printf("|\t\t\t\tARC state: %llu\n", + (u_longlong_t)L2BLK_GET_STATE((&le[j])->le_prop)); (void) printf("|\n"); } (void) printf("\n"); diff --git a/usr/src/lib/libdemangle/Makefile.com b/usr/src/lib/libdemangle/Makefile.com index 76f2e444fc..4cd8ce653c 100644 --- a/usr/src/lib/libdemangle/Makefile.com +++ b/usr/src/lib/libdemangle/Makefile.com @@ -11,12 +11,22 @@ # # Copyright 2018 Jason King -# Copyright 2018, Joyent, Inc. +# Copyright 2019 Joyent, Inc. # LIBRARY = libdemangle-sys.a VERS = .1 -OBJECTS = str.o strview.o util.o cxx_util.o cxx.o demangle.o rust.o +OBJECTS = \ + cxx.o \ + cxx_util.o \ + demangle.o \ + rust.o \ + rust-legacy.o \ + rust-v0puny.o \ + rust-v0.o \ + str.o \ + strview.o \ + util.o include ../../Makefile.lib @@ -29,10 +39,8 @@ CSTD = $(CSTD_GNU99) CFLAGS += $(CCVERBOSE) CPPFLAGS += -I$(SRCDIR) -D_REENTRANT -D__EXTENSIONS__ - .KEEP_STATE: all: $(LIBS) - include $(SRC)/lib/Makefile.targ diff --git a/usr/src/lib/libdemangle/common/cxx.c b/usr/src/lib/libdemangle/common/cxx.c index b0e9566e6c..0ec5e51294 100644 --- a/usr/src/lib/libdemangle/common/cxx.c +++ b/usr/src/lib/libdemangle/common/cxx.c @@ -12,24 +12,16 @@ /* * Copyright 2021 Jason King. */ -#include <ctype.h> #include <errno.h> -#include <locale.h> #include <note.h> #include <string.h> #include <setjmp.h> #include <stdio.h> #include <stdlib.h> -#include <sys/isa_defs.h> -#include <sys/debug.h> #include "demangle-sys.h" #include "demangle_int.h" #include "cxx.h" -#ifndef ARRAY_SIZE -#define ARRAY_SIZE(x) (sizeof (x) / sizeof (x[0])) -#endif - #define CPP_QUAL_CONST (1U) #define CPP_QUAL_VOLATILE (2U) #define CPP_QUAL_RESTRICT (4U) @@ -47,7 +39,6 @@ typedef struct cpp_db_s { boolean_t cpp_tag_templates; boolean_t cpp_fix_forward_references; boolean_t cpp_try_to_parse_template_args; - locale_t cpp_loc; } cpp_db_t; #define CK(x) \ @@ -81,7 +72,7 @@ static void tpush(cpp_db_t *); static void tpop(cpp_db_t *); static void tsave(cpp_db_t *, size_t); -static boolean_t db_init(cpp_db_t *, sysdem_ops_t *); +static void db_init(cpp_db_t *, sysdem_ops_t *); static void db_fini(cpp_db_t *); static void dump(cpp_db_t *, FILE *); @@ -96,8 +87,8 @@ static const char *parse_block_invoke(const char *, const char *, cpp_db_t *); static const char *parse_special_name(const char *, const char *, cpp_db_t *); static const char *parse_name(const char *, const char *, boolean_t *, cpp_db_t *); -static const char *parse_call_offset(const char *, const char *, locale_t); -static const char *parse_number(const char *, const char *, locale_t); +static const char *parse_call_offset(const char *, const char *); +static const char *parse_number(const char *, const char *); static const char *parse_nested_name(const char *, const char *, boolean_t *, cpp_db_t *); static const char *parse_local_name(const char *, const char *, boolean_t *, @@ -105,7 +96,7 @@ static const char *parse_local_name(const char *, const char *, boolean_t *, static const char *parse_unscoped_name(const char *, const char *, cpp_db_t *); static const char *parse_template_args(const char *, const char *, cpp_db_t *); static const char *parse_substitution(const char *, const char *, cpp_db_t *); -static const char *parse_discriminator(const char *, const char *, locale_t); +static const char *parse_discriminator(const char *, const char *); static const char *parse_cv_qualifiers(const char *, const char *, unsigned *); static const char *parse_template_param(const char *, const char *, cpp_db_t *); static const char *parse_decltype(const char *, const char *, cpp_db_t *); @@ -170,8 +161,8 @@ cpp_demangle(const char *src, size_t srclen, sysdem_ops_t *ops) char *volatile result = NULL; cpp_db_t db; - if (!db_init(&db, ops)) - goto done; + db_init(&db, ops); + if (setjmp(db.cpp_jmp) != 0) goto done; @@ -315,12 +306,12 @@ parse_block_invoke(const char *first, const char *last, cpp_db_t *db) if (t[0] == '_') { /* need at least one digit */ - if (t + 1 == last || !isdigit_l(t[1], db->cpp_loc)) + if (t + 1 == last || ISDIGIT(t[1])) return (first); t += 2; } - while (t < last && isdigit_l(t[0], db->cpp_loc)) + while (t < last && ISDIGIT(t[0])) t++; done: @@ -498,10 +489,10 @@ parse_special_name(const char *first, const char *last, cpp_db_t *db) break; case 'c': nadd_l(db, "covariant return thunk to", 0); - t1 = parse_call_offset(first + 2, last, db->cpp_loc); + t1 = parse_call_offset(first + 2, last); if (t1 == t) return (first); - t = parse_call_offset(t1, last, db->cpp_loc); + t = parse_call_offset(t1, last); if (t == t1) return (first); t1 = parse_encoding(t, last, db); @@ -512,7 +503,7 @@ parse_special_name(const char *first, const char *last, cpp_db_t *db) t = parse_type(first + 2, last, db); if (t == first + 2) return (first); - t1 = parse_number(t, last, db->cpp_loc); + t1 = parse_number(t, last); if (*t1 != '_') return (first); t = parse_type(t1 + 1, last, db); @@ -536,7 +527,7 @@ parse_special_name(const char *first, const char *last, cpp_db_t *db) nadd_l(db, "non-virtual thunk to", 0); } - t = parse_call_offset(first + 1, last, db->cpp_loc); + t = parse_call_offset(first + 1, last); if (t == first + 1) return (first); t1 = parse_encoding(t, last, db); @@ -583,7 +574,7 @@ parse_special_name(const char *first, const char *last, cpp_db_t *db) * # virtual base override, with vcall offset */ static const char * -parse_call_offset(const char *first, const char *last, locale_t loc) +parse_call_offset(const char *first, const char *last) { VERIFY3P(first, <=, last); @@ -596,7 +587,7 @@ parse_call_offset(const char *first, const char *last, locale_t loc) if (first[0] != 'h' && first[0] != 'v') return (first); - t = parse_number(first + 1, last, loc); + t = parse_number(first + 1, last); if (t == first + 1 || t == last || t[0] != '_') return (first); @@ -606,7 +597,7 @@ parse_call_offset(const char *first, const char *last, locale_t loc) if (first[0] == 'h') return (t); - t1 = parse_number(t, last, loc); + t1 = parse_number(t, last); if (t == t1 || t1 == last || t1[0] != '_') return (first); @@ -712,11 +703,11 @@ parse_local_name(const char *first, const char *last, if (t[0] == 's') { nfmt(db, "{0:L}::string literal", "{0:R}"); - return (parse_discriminator(t, last, db->cpp_loc)); + return (parse_discriminator(t, last)); } if (t[0] == 'd') { - t1 = parse_number(t + 1, last, db->cpp_loc); + t1 = parse_number(t + 1, last); if (t1[0] != '_') return (first); t1++; @@ -732,7 +723,7 @@ parse_local_name(const char *first, const char *last, /* parsed, but ignored */ if (t[0] != 'd') - t2 = parse_discriminator(t2, last, db->cpp_loc); + t2 = parse_discriminator(t2, last); return (t2); } @@ -1992,7 +1983,7 @@ parse_function_param(const char *first, const char *last, cpp_db_t *db) unsigned cv = 0; if (first[1] == 'L') { - t2 = parse_number(t1, last, db->cpp_loc); + t2 = parse_number(t1, last); if (t2 == last || t2[0] != 'p') return (first); t1 = t2; @@ -2002,7 +1993,7 @@ parse_function_param(const char *first, const char *last, cpp_db_t *db) return (first); t1 = parse_cv_qualifiers(t1, last, &cv); - t2 = parse_number(t1, last, db->cpp_loc); + t2 = parse_number(t1, last); if (t2 == last || t2[0] != '_') return (first); @@ -2439,8 +2430,7 @@ parse_unnamed_type_name(const char *first, const char *last, cpp_db_t *db) const char *t2 = NULL; if (first[1] == 't') { - while (t1 != last && t1[0] != '_' && - isdigit_l(t1[0], db->cpp_loc)) + while (t1 != last && t1[0] != '_' && ISDIGIT(t1[0])) t1++; if (t1[0] != '_') @@ -2483,7 +2473,8 @@ parse_unnamed_type_name(const char *first, const char *last, cpp_db_t *db) t2 = t1; while (t2 != last && t2[0] != '_') { - if (!isdigit_l(*t2++, db->cpp_loc)) + char c = *t2++; + if (!ISDIGIT(c)) return (first); } @@ -2653,7 +2644,7 @@ parse_integer_literal(const char *first, const char *last, const char *fmt, { VERIFY3P(first, <=, last); - const char *t = parse_number(first, last, db->cpp_loc); + const char *t = parse_number(first, last); const char *start = first; if (t == first || t == last || t[0] != 'E') @@ -2736,11 +2727,9 @@ parse_floating_literal(const char *first, const char *last, cpp_db_t *db) if (!is_xdigit(t[0])) return (first); - unsigned d1 = isdigit_l(t[0], db->cpp_loc) ? - t[0] - '0' : t[0] - 'a' + 10; + unsigned d1 = ISDIGIT(t[0]) ? t[0] - '0' : t[0] - 'a' + 10; t++; - unsigned d0 = isdigit_l(t[0], db->cpp_loc) ? - t[0] - '0' : t[0] - 'a' + 10; + unsigned d0 = ISDIGIT(t[0]) ? t[0] - '0' : t[0] - 'a' + 10; *e = (d1 << 4) + d0; } @@ -2749,11 +2738,9 @@ parse_floating_literal(const char *first, const char *last, cpp_db_t *db) if (!is_xdigit(t[0])) return (first); - unsigned d0 = isdigit_l(t[0], db->cpp_loc) ? - t[0] - '0' : t[0] - 'a' + 10; + unsigned d0 = ISDIGIT(t[0]) ? t[0] - '0' : t[0] - 'a' + 10; t--; - unsigned d1 = isdigit_l(t[0], db->cpp_loc) ? - t[0] - '0' : t[0] - 'a' + 10; + unsigned d1 = ISDIGIT(t[0]) ? t[0] - '0' : t[0] - 'a' + 10; *e = (d1 << 4) + d0; } @@ -2898,7 +2885,7 @@ parse_expr_primary(const char *first, const char *last, cpp_db_t *db) return (t + 1); const char *n; - for (n = t; n != last && isdigit_l(n[0], db->cpp_loc); n++) + for (n = t; n != last && ISDIGIT(n[0]); n++) ; if (n == last || nempty(db) || n[0] != 'E') return (first); @@ -3046,7 +3033,7 @@ parse_operator_name(const char *first, const char *last, cpp_db_t *db) } if (first[0] == 'v') { - if (!isdigit_l(first[1], db->cpp_loc)) + if (!ISDIGIT(first[1])) return (first); t = parse_source_name(first + 2, last, db); @@ -3155,19 +3142,19 @@ parse_builtin_type(const char *first, const char *last, cpp_db_t *db) } static const char * -parse_base36(const char *first, const char *last, size_t *val, locale_t loc) +parse_base36(const char *first, const char *last, size_t *val) { VERIFY3P(first, <=, last); const char *t; for (t = first, *val = 0; t != last; t++) { - if (!isdigit_l(t[0], loc) && !isupper_l(t[0], loc)) + if (!ISDIGIT(t[0]) && !ISUPPER(t[0])) return (t); *val *= 36; - if (isdigit_l(t[0], loc)) + if (ISDIGIT(t[0])) *val += t[0] - '0'; else *val += t[0] - 'A' + 10; @@ -3206,7 +3193,7 @@ parse_substitution(const char *first, const char *last, cpp_db_t *db) size_t n = 0; if (t[0] != '_') { - t = parse_base36(first + 1, last, &n, db->cpp_loc); + t = parse_base36(first + 1, last, &n); if (t == first + 1 || t[0] != '_') return (first); @@ -3240,7 +3227,7 @@ parse_source_name(const char *first, const char *last, cpp_db_t *db) const char *t = NULL; size_t n = 0; - for (t = first; t != last && isdigit_l(t[0], db->cpp_loc); t++) { + for (t = first; t != last && ISDIGIT(t[0]); t++) { /* make sure we don't overflow */ size_t nn = n * 10; if (nn < n) @@ -3287,8 +3274,8 @@ parse_vector_type(const char *first, const char *last, cpp_db_t *db) const char *t = first + 2; const char *t1 = NULL; - if (isdigit_l(first[2], db->cpp_loc) && first[2] != '0') { - t1 = parse_number(t, last, db->cpp_loc); + if (ISDIGIT(first[2]) && first[2] != '0') { + t1 = parse_number(t, last); if (t1 == last || t1 + 1 == last || t1[0] != '_') return (first); @@ -3376,8 +3363,8 @@ parse_array_type(const char *first, const char *last, cpp_db_t *db) size_t n = nlen(db); if (t[0] != '_') { - if (isdigit_l(t[0], db->cpp_loc) && t[0] != '0') { - t1 = parse_number(t, last, db->cpp_loc); + if (ISDIGIT(t[0]) && t[0] != '0') { + t1 = parse_number(t, last); if (t1 == last) return (first); @@ -3765,7 +3752,7 @@ parse_template_param(const char *first, const char *last, cpp_db_t *db) size_t idx = 0; while (t != last && t[0] != '_') { - if (!isdigit_l(t[0], db->cpp_loc)) + if (!ISDIGIT(t[0])) return (first); idx *= 10; @@ -3870,7 +3857,7 @@ parse_template_args(const char *first, const char *last, cpp_db_t *db) * extension := decimal-digit+ # at the end of string */ static const char * -parse_discriminator(const char *first, const char *last, locale_t loc) +parse_discriminator(const char *first, const char *last) { VERIFY3P(first, <=, last); @@ -3879,8 +3866,8 @@ parse_discriminator(const char *first, const char *last, locale_t loc) if (first == last) return (first); - if (isdigit_l(first[0], loc)) { - for (t = first; t != last && isdigit_l(t[0], loc); t++) + if (ISDIGIT(first[0])) { + for (t = first; t != last && ISDIGIT(t[0]); t++) ; /* not at the end of the string */ @@ -3893,13 +3880,13 @@ parse_discriminator(const char *first, const char *last, locale_t loc) } t = first + 1; - if (isdigit_l(t[0], loc)) + if (ISDIGIT(t[0])) return (t + 1); if (t[0] != '_' || t + 1 == last) return (first); - for (t++; t != last && isdigit_l(t[0], loc); t++) + for (t++; t != last && ISDIGIT(t[0]); t++) ; if (t == last || t[0] != '_') return (first); @@ -3937,13 +3924,13 @@ parse_cv_qualifiers(const char *first, const char *last, unsigned *cv) * <number> ::= [n] <non-negative decimal integer> */ static const char * -parse_number(const char *first, const char *last, locale_t loc) +parse_number(const char *first, const char *last) { VERIFY3P(first, <=, last); const char *t = first; - if (first == last || (first[0] != 'n' && !isdigit_l(first[0], loc))) + if (first == last || (first[0] != 'n' && !ISDIGIT(first[0]))) return (first); if (t[0] == 'n') @@ -3952,7 +3939,7 @@ parse_number(const char *first, const char *last, locale_t loc) if (t[0] == '0') return (t + 1); - while (isdigit_l(t[0], loc)) + while (ISDIGIT(t[0])) t++; return (t); @@ -4051,7 +4038,7 @@ tsave(cpp_db_t *db, size_t amt) CK(templ_save(&db->cpp_name, amt, &db->cpp_templ)); } -static boolean_t +static void db_init(cpp_db_t *db, sysdem_ops_t *ops) { (void) memset(db, 0, sizeof (*db)); @@ -4062,8 +4049,6 @@ db_init(cpp_db_t *db, sysdem_ops_t *ops) db->cpp_tag_templates = B_TRUE; db->cpp_try_to_parse_template_args = B_TRUE; tpush(db); - db->cpp_loc = newlocale(LC_CTYPE_MASK, "C", 0); - return ((db->cpp_loc != NULL) ? B_TRUE : B_FALSE); } static void @@ -4072,7 +4057,6 @@ db_fini(cpp_db_t *db) name_fini(&db->cpp_name); sub_fini(&db->cpp_subs); templ_fini(&db->cpp_templ); - freelocale(db->cpp_loc); (void) memset(db, 0, sizeof (*db)); } diff --git a/usr/src/lib/libdemangle/common/cxx_util.c b/usr/src/lib/libdemangle/common/cxx_util.c index 91abb504d3..f4ca32fae5 100644 --- a/usr/src/lib/libdemangle/common/cxx_util.c +++ b/usr/src/lib/libdemangle/common/cxx_util.c @@ -13,8 +13,6 @@ * Copyright 2017 Jason King */ -#include <sys/debug.h> -#include <sys/sysmacros.h> #include <string.h> #include <errno.h> #include <stdlib.h> diff --git a/usr/src/lib/libdemangle/common/demangle-sys.h b/usr/src/lib/libdemangle/common/demangle-sys.h index 3452d39667..21b2624cf3 100644 --- a/usr/src/lib/libdemangle/common/demangle-sys.h +++ b/usr/src/lib/libdemangle/common/demangle-sys.h @@ -26,7 +26,7 @@ extern "C" { typedef enum sysdem_lang_e { SYSDEM_LANG_AUTO, SYSDEM_LANG_CPP, - SYSDEM_LANG_RUST + SYSDEM_LANG_RUST, } sysdem_lang_t; typedef struct sysdem_alloc_s { diff --git a/usr/src/lib/libdemangle/common/demangle.c b/usr/src/lib/libdemangle/common/demangle.c index bf7c9ab8c7..f8f322757a 100644 --- a/usr/src/lib/libdemangle/common/demangle.c +++ b/usr/src/lib/libdemangle/common/demangle.c @@ -11,7 +11,7 @@ /* * Copyright 2021 Jason King - * Copyright 2019, Joyent, Inc. + * Copyright 2019 Joyent, Inc. */ #include <stdlib.h> @@ -86,6 +86,8 @@ is_mangled(const char *str, size_t n) (void) sv_consume_if_c(&sv, '_'); if (sv_consume_if_c(&sv, 'Z')) return (B_TRUE); + if (sv_consume_if_c(&sv, 'R')) + return (B_TRUE); return (B_FALSE); } @@ -101,6 +103,7 @@ char * sysdemangle(const char *str, sysdem_lang_t lang, sysdem_ops_t *ops) { char *res = NULL; + /* * While the language specific demangler code can handle non-NUL * terminated strings, we currently don't expose this to consumers. diff --git a/usr/src/lib/libdemangle/common/demangle_int.h b/usr/src/lib/libdemangle/common/demangle_int.h index 66a34cf41d..d4c227a87f 100644 --- a/usr/src/lib/libdemangle/common/demangle_int.h +++ b/usr/src/lib/libdemangle/common/demangle_int.h @@ -11,24 +11,97 @@ /* * Copyright 2017 Jason King - * Copyright 2019, Joyent, Inc. + * Copyright 2019 Joyent, Inc. */ #ifndef _DEMANGLE_INT_H #define _DEMANGLE_INT_H +#include <inttypes.h> #include <stdio.h> +#include <sys/byteorder.h> +#include <sys/ctype.h> /* Use ASCII ISXXXX() macros */ +#include <sys/debug.h> +#include <sys/sysmacros.h> +#include <sys/isa_defs.h> #include "demangle-sys.h" #ifdef __cplusplus extern "C" { #endif +#ifdef __CHECKER__ +/* + * smatch seems to have a bug which chokes on the builtins, so + * we just have it fallback to the non-builtin definitions + */ +#elif __GNUC__ >= 5 && __GNUC_MINOR__ > 1 +#define USE_BUILTIN_OVERFLOW +#elif defined(__clang__) +#define USE_BUILTIN_OVERFLOW +#endif + +#ifdef USE_BUILTIN_OVERFLOW +static inline boolean_t +mul_overflow(uint64_t a, uint64_t b, uint64_t *v) +{ + return (__builtin_mul_overflow(a, b, v)); +} + +static inline boolean_t +add_overflow(uint64_t a, uint64_t b, uint64_t *v) +{ + return (__builtin_add_overflow(a, b, v)); +} + +static inline boolean_t +sub_overflow(uint64_t a, uint64_t b, uint64_t *v) +{ + return (__builtin_sub_overflow(a, b, v)); +} +#else +static inline boolean_t +mul_overflow(uint64_t a, uint64_t b, uint64_t *v) +{ + uint64_t val = a * b; + + if (a != 0 && val / a != b) + return (B_TRUE); + *v = val; + return (B_FALSE); +} + +static inline boolean_t +add_overflow(uint64_t a, uint64_t b, uint64_t *v) +{ + uint64_t val = a + b; + + if (val < a || val < b) + return (B_TRUE); + *v = val; + return (B_FALSE); +} + +static inline boolean_t +sub_overflow(uint64_t a, uint64_t b, uint64_t *v) +{ + uint64_t val = a - b; + + if (val > a) + return (B_TRUE); + *v = val; + return (B_FALSE); +} +#endif + extern sysdem_ops_t *sysdem_ops_default; char *cpp_demangle(const char *, size_t, sysdem_ops_t *); char *rust_demangle(const char *, size_t, sysdem_ops_t *); +struct custr_alloc; + void *zalloc(sysdem_ops_t *, size_t); +void *xcalloc(sysdem_ops_t *, size_t, size_t); void *xrealloc(sysdem_ops_t *, void *, size_t, size_t); void xfree(sysdem_ops_t *, void *, size_t); char *xstrdup(sysdem_ops_t *, const char *); diff --git a/usr/src/lib/libdemangle/common/rust-legacy.c b/usr/src/lib/libdemangle/common/rust-legacy.c new file mode 100644 index 0000000000..5b1518f619 --- /dev/null +++ b/usr/src/lib/libdemangle/common/rust-legacy.c @@ -0,0 +1,386 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2019 Joyent, Inc. + * Copyright 2021 Jason King + */ + +#include <errno.h> +#include <libcustr.h> +#include <limits.h> +#include <string.h> +#include <stdio.h> + +#include "rust.h" + +/* + * Unfortunately, there is currently no official specification for the legacy + * rust name mangling. This is an attempt to document the understanding of the + * mangling used here. It is based off examination of + * https://docs.rs/rustc-demangle/0.1.13/rustc_demangle/ + * + * A mangled rust name is: + * <prefix> <name> + * + * <prefix> ::= _Z + * __Z + * + * <name> ::= N <name-segment>+ [<hash>] E + * + * <name-segment> ::= <len> <name-chars>{len} + * + * <len> ::= [1-9][0-9]+ + * + * <name-chars> ::= <[A-Za-z]> <[A-Za-z0-9]>* + * <separator> + * <special> + * + * <separator> ::= '..' # '::' + * + * <special> ::= $SP$ # '@' + * $BP$ # '*' + * $RF$ # '&' + * $LT$ # '<' + * $GT$ # '>' + * $LP$ # '(' + * $RP$ # ')' + * $C$ # ',' + * + * <hash> := <len> h <hex-digits>+ + * + * <hex-digits> := <[0-9a-f]> + */ + +static const struct rust_charmap { + const char *ruc_seq; + char ruc_ch; +} rust_charmap[] = { + { "$SP$", '@' }, + { "$BP$", '*' }, + { "$RF$", '&' }, + { "$LT$", '<' }, + { "$GT$", '>' }, + { "$LP$", '(' }, + { "$RP$", ')' }, + { "$C$", ',' }, +}; +static const size_t rust_charmap_sz = ARRAY_SIZE(rust_charmap); + +static boolean_t rustleg_valid_sym(const strview_t *); +static boolean_t rustleg_parse_name(rust_state_t *, strview_t *); +static boolean_t rustleg_parse_hash(rust_state_t *, strview_t *); +static boolean_t rustleg_parse_special(rust_state_t *, strview_t *); +static boolean_t rustleg_add_sep(rust_state_t *); + +boolean_t +rust_demangle_legacy(rust_state_t *restrict st, strview_t *restrict sv) +{ + + /* Make sure the whole thing contains valid characters */ + if (!rustleg_valid_sym(sv)) { + st->rs_error = EINVAL; + return (B_FALSE); + } + + if (sv_peek(sv, -1) != 'E') { + DEMDEBUG("ERROR: string does not end with 'E'"); + st->rs_error = EINVAL; + return (B_FALSE); + } + + if (!rustleg_parse_name(st, sv)) + return (B_FALSE); + + if (sv_remaining(sv) != 0) { + DEMDEBUG("ERROR: trailing characters in name"); + st->rs_error = EINVAL; + return (B_FALSE); + } + + return (B_TRUE); +} + +static boolean_t +rustleg_parse_name_segment(rust_state_t *st, strview_t *svp, boolean_t first) +{ + strview_t orig; + strview_t name; + uint64_t len; + size_t rem; + boolean_t last = B_FALSE; + + if (HAS_ERROR(st) || sv_remaining(svp) == 0) + return (B_FALSE); + + sv_init_sv(&orig, svp); + + if (!rust_parse_base10(st, svp, &len)) { + DEMDEBUG("ERROR: no leading length"); + st->rs_error = EINVAL; + return (B_FALSE); + } + + rem = sv_remaining(svp); + + if (rem < len) { + DEMDEBUG("ERROR: segment length (%" PRIu64 ") > remaining " + "bytes in string (%zu)", len, rem); + st->rs_error = EINVAL; + return (B_FALSE); + } + + /* Is this the last segment before the terminating E? */ + if (rem == len + 1) { + VERIFY3U(sv_peek(svp, -1), ==, 'E'); + last = B_TRUE; + } + + if (!first && !rustleg_add_sep(st)) + return (B_FALSE); + + /* Reduce length of seg to the length we parsed */ + (void) sv_init_sv_range(&name, svp, len); + + DEMDEBUG("%s: segment='%.*s'", __func__, SV_PRINT(&name)); + + /* + * A rust hash starts with 'h', and is the last component of a name + * before the terminating 'E'. It is however not always present + * in every mangled symbol, and a last segment that starts with 'h' + * could be confused for it, so failing to part it just means + * we don't have a trailing hash. + */ + if (sv_peek(&name, 0) == 'h' && last) { + if (rustleg_parse_hash(st, &name)) + goto done; + + /* + * However any error other than 'not a hash' (e.g. ENOMEM) + * means we should fail. + */ + if (st->rs_error != 0) + goto done; + } + + /* A '_' followed by $ is ignored at the start of a name segment */ + if (sv_peek(&name, 0) == '_' && sv_peek(&name, 1) == '$') + (void) sv_consume_n(&name, 1); + + while (sv_remaining(&name) > 0) { + switch (sv_peek(&name, 0)) { + case '$': + if (rustleg_parse_special(st, &name)) + continue; + break; + case '.': + /* Convert '..' to '::' */ + if (sv_peek(&name, 1) != '.') + break; + + if (!rustleg_add_sep(st)) + return (B_FALSE); + + sv_consume_n(&name, 2); + continue; + default: + break; + } + + if (!rust_appendc(st, sv_consume_c(&name))) { + SET_ERROR(st); + return (B_FALSE); + } + } + +done: + sv_consume_n(svp, len); + + VERIFY3P(orig.sv_first, <=, svp->sv_first); + DEMDEBUG("%s: consumed '%.*s'", __func__, + (int)(uintptr_t)(svp->sv_first - orig.sv_first), orig.sv_first); + return (B_TRUE); +} + +/* + * Parse N (<num><name>{num})+ [<num>h<hex digits]E + */ +static boolean_t +rustleg_parse_name(rust_state_t *st, strview_t *svp) +{ + strview_t name; + boolean_t first = B_TRUE; + + sv_init_sv(&name, svp); + + if (HAS_ERROR(st)) + return (B_FALSE); + + DEMDEBUG("%s: name = '%.*s'", __func__, SV_PRINT(&name)); + + if (sv_remaining(svp) == 0) { + DEMDEBUG("%s: empty name", __func__); + return (B_FALSE); + } + + if (!sv_consume_if_c(svp, 'N')) { + DEMDEBUG("%s: does not start with 'N'", __func__); + return (B_FALSE); + } + + while (sv_remaining(svp) > 0 && sv_peek(svp, 0) != 'E') { + if (!rustleg_parse_name_segment(st, svp, first)) + return (B_FALSE); + first = B_FALSE; + } + + if (!sv_consume_if_c(svp, 'E')) { + DEMDEBUG("%s: ERROR no terminating 'E'", __func__); + return (B_FALSE); + } + + VERIFY3P(name.sv_first, <=, svp->sv_first); + DEMDEBUG("%s: consumed '%.*s'", __func__, + (int)(uintptr_t)(svp->sv_first - name.sv_first), name.sv_first); + + return (B_TRUE); +} + +static boolean_t +rustleg_parse_hash(rust_state_t *st, strview_t *svp) +{ + if (HAS_ERROR(st)) + return (B_FALSE); + + VERIFY(sv_consume_if_c(svp, 'h')); + if (!rust_appendc(st, 'h')) + return (B_FALSE); + + while (sv_remaining(svp) > 0) { + char c = sv_consume_c(svp); + + switch (c) { + /* + * The upper-case hex digits (A-F) are excluded as valid + * hash values for several reasons: + * + * 1. It would result in two different possible names for + * the same function, leading to ambiguity in linking (among + * other things). + * + * 2. It would cause potential ambiguity in parsing -- is a + * trailing 'E' part of the hash, or the terminating character + * in the mangled name? + * + * 3. No examples were able to be found in the wild where + * uppercase digits are used, and other rust demanglers all + * seem to assume the hash must contain lower-case hex digits. + */ + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + case '8': case '9': case 'a': case 'b': + case 'c': case 'd': case 'e': case 'f': + if (!rust_appendc(st, c)) + return (B_FALSE); + break; + default: + return (B_FALSE); + } + } + + return (B_TRUE); +} + +static boolean_t +rustleg_parse_special(rust_state_t *restrict st, strview_t *restrict svp) +{ + if (HAS_ERROR(st)) + return (B_FALSE); + + if (sv_peek(svp, 0) != '$') + return (B_FALSE); + + for (size_t i = 0; i < rust_charmap_sz; i++) { + if (sv_consume_if(svp, rust_charmap[i].ruc_seq)) { + if (!rust_appendc(st, rust_charmap[i].ruc_ch)) + return (B_FALSE); + return (B_TRUE); + } + } + + /* Handle $uXXXX$ */ + + strview_t sv; + uint32_t val = 0; + uint_t ndigits = 0; + + sv_init_sv(&sv, svp); + + /* We peeked at this earlier, so it should still be there */ + VERIFY(sv_consume_if_c(&sv, '$')); + + if (!sv_consume_if_c(&sv, 'u')) + return (B_FALSE); + + while (sv_remaining(&sv) > 0) { + uint32_t cval = 0; + char c; + + if (ndigits == 4) + return (B_FALSE); + + c = sv_consume_c(&sv); + if (c >= '0' && c <= '9') + cval = c - '0'; + else if (c >= 'a' && c <= 'f') + cval = c - 'a' + 10; + else if (c == '$') + break; + else + return (B_FALSE); + + val <<= 4; + val |= cval; + ndigits++; + } + + if (!rust_append_utf8_c(st, val)) + return (B_FALSE); + + sv_consume_n(svp, ndigits + 3); + return (B_TRUE); +} + +static boolean_t +rustleg_add_sep(rust_state_t *st) +{ + if (HAS_ERROR(st)) + return (B_FALSE); + + return (rust_append(st, "::")); +} + +static boolean_t +rustleg_valid_sym(const strview_t *sv) +{ + size_t i; + + for (i = 0; i < sv->sv_rem; i++) { + char c = sv->sv_first[i]; + + if ((c & 0x80) == 0) + continue; + DEMDEBUG("%s: ERROR found 8-bit character '%c' in '%.*s' " + "at index %zu", __func__, c, SV_PRINT(sv), i); + return (B_FALSE); + } + return (B_TRUE); +} diff --git a/usr/src/lib/libdemangle/common/rust-v0.c b/usr/src/lib/libdemangle/common/rust-v0.c new file mode 100644 index 0000000000..598d8457c9 --- /dev/null +++ b/usr/src/lib/libdemangle/common/rust-v0.c @@ -0,0 +1,1449 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2019 Joyent, Inc. + * Copyright 2021 Jason King + */ + +/* BEGIN CSTYLED */ + +/* + * This implements the 'symbol_name_mangling_v2' demangling for rust as + * described in Rust RFC 2603 as opposed to the original (now called + * legacy) mangling older versions of rust used (implemented in rust.c). + * + * The specification can be viewed at: + * https://github.com/rust-lang/rfcs/blob/master/text/2603-rust-symbol-name-mangling-v0.md + */ + +/* END CSTYLED */ + +#include <errno.h> +#include <libcustr.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "rust.h" + +/* + * Help track amount of additional output added to rs_demangled across + * a function call (to allow that portion to be output for debugging) + */ +#define SAVE_LEN(_st, _len) _len = custr_len((_st)->rs_demangled) +#define CSTR_END(_st, _len) \ + ((int)(custr_len((_st)->rs_demangled) - (_len))), \ + custr_cstr((_st)->rs_demangled) + (_len) + +typedef enum const_type_class { + CTC_INVALID = -1, + CTC_UNSIGNED, + CTC_SIGNED, + CTC_CHAR, + CTC_BOOL, +} const_type_class_t; + +/* + * Sometimes, parsing something is optional. In this case a failure to + * parse is fine, however we still want to consider a fatal error as + * failure. + */ +#define OPTIONAL(_st, _f) ((_f) || !HAS_ERROR(_st)) + +static boolean_t rustv0_valid_sym(const strview_t *); +static const_type_class_t rustv0_classify_const_type(char); +static boolean_t rustv0_parse_hex_num(rust_state_t *restrict, + strview_t *restrict, uint64_t *restrict); +static boolean_t rustv0_parse_base62(rust_state_t *restrict, + strview_t *restrict, uint64_t *restrict); + +static boolean_t rustv0_parse_undisambiguated_identifier( + rust_state_t *restrict, strview_t *restrict, boolean_t); +static boolean_t rustv0_parse_disambiguator(rust_state_t *restrict, + strview_t *restrict, uint64_t *restrict); + +static boolean_t rustv0_parse_path(rust_state_t *restrict, strview_t *restrict, + boolean_t); +static boolean_t rustv0_parse_impl_path(rust_state_t *restrict, + strview_t *restrict, boolean_t); +static boolean_t rustv0_parse_nested_path(rust_state_t *restrict, + strview_t *restrict, boolean_t); +static boolean_t rustv0_parse_basic_type(rust_state_t *restrict, + strview_t *restrict); +static boolean_t rustv0_parse_backref(rust_state_t *restrict, + strview_t *restrict, + boolean_t (*)(rust_state_t *restrict, strview_t *restrict, boolean_t), + boolean_t); +static boolean_t rustv0_parse_lifetime(rust_state_t *restrict, + strview_t *restrict); +static boolean_t rustv0_parse_const(rust_state_t *restrict, + strview_t *restrict, boolean_t); +static boolean_t rustv0_parse_fnsig(rust_state_t *restrict, + strview_t *restrict); +static boolean_t rustv0_parse_dynbounds(rust_state_t *restrict, + strview_t *restrict); +static boolean_t rustv0_parse_generic_arg(rust_state_t *restrict, + strview_t *restrict, boolean_t); + +boolean_t +rust_demangle_v0(rust_state_t *restrict st, strview_t *restrict sv) +{ + boolean_t save_skip; + boolean_t ret; + + /* Make sure all the characters are valid */ + if (!rustv0_valid_sym(sv)) { + st->rs_error = EINVAL; + return (B_FALSE); + } + + /* + * <symbol-name> = "_R" [<decimal-number>] <path> + * [<instantiating-crate>] + * + * We've already parsed the prefix in rust_demangle(), as well + * as made sure there's no [<decimal-number>] present, so + * start with <path>. + */ + if (!rustv0_parse_path(st, sv, B_TRUE)) + return (B_FALSE); + + /* [<instantiating crate>] -- parse but don't save */ + SKIP_BEGIN(st, save_skip); + ret = OPTIONAL(st, rustv0_parse_path(st, sv, B_FALSE)); + SKIP_END(st, save_skip); + if (!ret) + return (B_FALSE); + + /* If nothing's left, we know we're done */ + if (sv_remaining(sv) == 0) + return (!HAS_ERROR(st)); + + /* + * LLVM sometimes will suffix symbols starting with a '.' + * followed by extra data. For things that start with + * ".llvm.", we discard the rest of the string. For + * other things that start with '.', we copy the + * results to the final string. This matches + * what the rust native demangler crate does, and + * we don't see a reason to deviate from their + * behavior. + */ + if (sv_consume_if(sv, ".llvm.")) + return (!HAS_ERROR(st)); + + if (sv_peek(sv, 0) != '.') { + DEMDEBUG("%s: Unexpected trailing data at the end of the " + "name: '%.*s'", __func__, SV_PRINT(sv)); + st->rs_error = EINVAL; + return (B_FALSE); + } + + return (rust_append_sv(st, sv_remaining(sv), sv)); +} + +/* + * Parse an optional list terminated by 'E'. Each result of 'fn' is + * separated by 'sep' in the output. + */ +static boolean_t +rustv0_parse_opt_list(rust_state_t *restrict st, strview_t *restrict sv, + boolean_t (*fn)(rust_state_t *restrict, strview_t *restrict, boolean_t), + const char *restrict sep, boolean_t bval, size_t *restrict countp) +{ + size_t count = 0; + + DEMDEBUG("%s: str = '%.*s'", __func__, SV_PRINT(sv)); + + while (sv_remaining(sv) > 0) { + if (sv_consume_if_c(sv, 'E')) { + if (countp != NULL) + *countp += count; + return (B_TRUE); + } + + if (count > 0 && !rust_append(st, sep)) + return (B_FALSE); + + if (!fn(st, sv, bval)) + return (B_FALSE); + + count++; + } + + /* + * An optional list should terminate with an 'E'. If we get here, + * we ran out of charaters and didn't terminate as we should. + */ + return (B_FALSE); +} + +static boolean_t +rustv0_parse_uint_type(rust_state_t *restrict st, strview_t *sv) +{ + const char *str = NULL; + strview_t save; + char c; + + if (HAS_ERROR(st) || sv_remaining(sv) == 0) + return (B_FALSE); + + sv_init_sv(&save, sv); + + switch (c = sv_consume_c(sv)) { + case 'h': + str = "u8"; + break; + case 't': + str = "u16"; + break; + case 'm': + str = "u32"; + break; + case 'y': + str = "u64"; + break; + case 'o': + str = "u128"; + break; + case 'j': /* usize */ + str = "usize"; + break; + default: + sv_init_sv(sv, &save); + return (B_FALSE); + } + + DEMDEBUG("%s: %c -> %s", __func__, c, str); + return (rust_append(st, str)); +} + +static boolean_t +rustv0_parse_basic_type(rust_state_t *restrict st, strview_t *restrict sv) +{ + const char *str = NULL; + strview_t save; + char c; + + if (HAS_ERROR(st) || sv_remaining(sv) == 0) + return (B_FALSE); + + if (rustv0_parse_uint_type(st, sv)) + return (B_TRUE); + + sv_init_sv(&save, sv); + + switch (c = sv_consume_c(sv)) { + case 'a': + str = "i8"; + break; + case 'b': + str = "bool"; + break; + case 'c': + str = "char"; + break; + case 'd': + str = "f64"; + break; + case 'e': + str = "str"; + break; + case 'f': + str = "f32"; + break; + case 'i': + str = "isize"; + break; + case 'l': + str = "i32"; + break; + case 'n': + str = "i128"; + break; + case 'p': + str = "_"; + break; + case 's': + str = "i16"; + break; + case 'u': + str = "()"; + break; + case 'v': + str = "..."; + break; + case 'x': + str = "i64"; + break; + case 'z': + str = "!"; + break; + default: + sv_init_sv(sv, &save); + return (B_FALSE); + } + + DEMDEBUG("%s: %c -> %s", __func__, c, str); + return (rust_append(st, str)); +} + +static boolean_t +rustv0_parse_type(rust_state_t *restrict st, strview_t *restrict sv, + boolean_t dummy __unused) +{ + strview_t save; + size_t len, tuple_elem_count; + boolean_t ret; + char c; + + if (HAS_ERROR(st)) + return (B_FALSE); + + DEMDEBUG("%s: str='%.*s'", __func__, SV_PRINT(sv)); + + if (sv_remaining(sv) == 0) + return (B_FALSE); + + SAVE_LEN(st, len); + sv_init_sv(&save, sv); + + switch (c = sv_consume_c(sv)) { + case 'A': + ret = rust_appendc(st, '[') && + rustv0_parse_type(st, sv, B_FALSE) && + rust_append(st, "; ") && + rustv0_parse_const(st, sv, B_FALSE) && + rust_appendc(st, ']'); + break; + case 'S': + ret = rust_appendc(st, '[') && + rustv0_parse_type(st, sv, B_FALSE) && + rust_appendc(st, ']'); + break; + case 'T': + tuple_elem_count = 0; + ret = rust_appendc(st, '(') && + rustv0_parse_opt_list(st, sv, rustv0_parse_type, ", ", + B_FALSE, &tuple_elem_count) && + rust_append(st, (tuple_elem_count == 1) ? ",)" : ")"); + break; + case 'R': + case 'Q': + /* `&mut T` or `&'... mut T` */ + if (!(ret = rust_appendc(st, '&'))) + break; + + /* + * lifetime is optional, but we need to add a trailing + * space if present (so we cannot use the OPTIONAL macro). + */ + if (rustv0_parse_lifetime(st, sv)) { + if (!(ret = rust_appendc(st, ' '))) + break; + } else if (HAS_ERROR(st)) { + break; + } + + ret = rust_append(st, (c == 'Q') ? "mut " : "") && + rustv0_parse_type(st, sv, B_FALSE); + break; + case 'P': + ret = rust_append(st, "*const ") && + rustv0_parse_type(st, sv, B_FALSE); + break; + case 'O': + ret = rust_append(st, "*mut ") && + rustv0_parse_type(st, sv, B_FALSE); + break; + case 'F': + ret = rustv0_parse_fnsig(st, sv); + break; + case 'D': + ret = rust_append(st, "dyn ") && + rustv0_parse_dynbounds(st, sv); + if (!ret) + break; + + /* + * Rust RFC2603 shows the lifetime as required, however + * it appears this is optional. + */ + DEMDEBUG("%s: pre-lifetime: '%*s'", __func__, SV_PRINT(sv)); + + /* + * We only want to print a non-zero (non "'_") + * lifetime. + */ + if (sv_consume_if(sv, "L_")) + break; + + /* + * But if there is a lifetime we want to print, + * we want to prepend " + " before it. + */ + if (sv_peek(sv, 0) == 'L' && + !(ret = rust_append(st, " + "))) + break; + + ret = rustv0_parse_lifetime(st, sv); + break; + default: + sv_init_sv(sv, &save); + + ret = rustv0_parse_backref(st, sv, rustv0_parse_type, + B_FALSE) || + rustv0_parse_basic_type(st, sv); + if (ret) + break; + + ret = rustv0_parse_path(st, sv, B_FALSE); + break; + } + + DEMDEBUG("%s: type='%.*s' (%s)", __func__, CSTR_END(st, len), + ret ? "success" : "fail"); + + return (ret); +} + +/* + * <path> = "C" <identifier> crate root + * | "M" <impl-path> <type> <T> + * | "X" <impl-path> <type> <path> <T as Trait> (trait impl) + * | "Y" <type> <path> <T as Trait> (trait definition) + * | "N" <ns> <path> <identifier> ...::ident (nested path) + * | "I" <path> {<generic-arg>} "E" ...<T, U> + * | <backref> + */ +static boolean_t +rustv0_parse_path(rust_state_t *restrict st, strview_t *restrict sv, + boolean_t in_value) +{ + strview_t save; + uint64_t disamb = 0; + size_t len; + boolean_t ret = B_FALSE; + boolean_t save_skip; + boolean_t args_stay_save = st->rs_args_stay_open; + boolean_t args_open_save = st->rs_args_is_open; + + if (HAS_ERROR(st)) + return (B_FALSE); + + DEMDEBUG("%s: str='%.*s'", __func__, SV_PRINT(sv)); + + if (sv_remaining(sv) == 0) + return (B_FALSE); + + SAVE_LEN(st, len); + sv_init_sv(&save, sv); + + switch (sv_consume_c(sv)) { + case 'C': + if (!OPTIONAL(st, rustv0_parse_disambiguator(st, sv, &disamb))) + goto done; + + if (!rustv0_parse_undisambiguated_identifier(st, sv, B_FALSE)) + goto done; + + if (st->rs_verbose && + !rust_append_printf(st, "[%" PRIx64 "]", disamb)) + goto done; + break; + case 'M': + SKIP_BEGIN(st, save_skip); + if (!rustv0_parse_impl_path(st, sv, in_value)) { + SKIP_END(st, save_skip); + goto done; + } + SKIP_END(st, save_skip); + + if (!rust_appendc(st, '<') || + !rustv0_parse_type(st, sv, B_FALSE) || + !rust_appendc(st, '>')) + goto done; + break; + case 'X': + SKIP_BEGIN(st, save_skip); + if (!rustv0_parse_impl_path(st, sv, in_value)) { + SKIP_END(st, save_skip); + goto done; + } + SKIP_END(st, save_skip); + /*FALLTHRU*/ + case 'Y': + if (!rust_appendc(st, '<') || + !rustv0_parse_type(st, sv, B_FALSE) || + !rust_append(st, " as ") || + !rustv0_parse_path(st, sv, B_FALSE) || + !rust_appendc(st, '>')) + goto done; + break; + case 'N': + if (!rustv0_parse_nested_path(st, sv, in_value)) + goto done; + break; + case 'I': + st->rs_args_stay_open = B_FALSE; + st->rs_args_is_open = B_FALSE; + + if (!rustv0_parse_path(st, sv, in_value)) + goto done; + + if (in_value && !rust_append(st, "::")) + goto done; + + if (!rust_appendc(st, '<') || + !rustv0_parse_opt_list(st, sv, rustv0_parse_generic_arg, + ", ", B_FALSE, NULL)) + goto done; + + st->rs_args_stay_open = args_stay_save; + st->rs_args_is_open = args_open_save; + + /* + * If we were asked to not close our list, then don't and + * indicate that the list is open. + */ + if (st->rs_args_stay_open) { + st->rs_args_stay_open = B_FALSE; + st->rs_args_is_open = B_TRUE; + } else if (!rust_appendc(st, '>')) { + goto done; + } + break; + default: + /* + * Didn't recognize the letter, so it has to be a path. Restore + * sv to state prior to switch and continue. + */ + sv_init_sv(sv, &save); + if (!rustv0_parse_backref(st, sv, rustv0_parse_path, in_value)) + goto done; + } + + ret = B_TRUE; + +done: + DEMDEBUG("%s: path='%.*s' (%s)", __func__, CSTR_END(st, len), + ret ? "success" : "fail"); + + return (ret); +} + +static boolean_t +rustv0_parse_impl_path(rust_state_t *restrict st, strview_t *restrict sv, + boolean_t in_value) +{ + uint64_t val = 0; + + return (OPTIONAL(st, rustv0_parse_disambiguator(st, sv, &val)) && + rustv0_parse_path(st, sv, in_value)); +} + +/* + * A bit of a hack -- when printing a nested path, we need to know + * if the identifier is there or not in order to correctly format + * the output preceeding it (when present). This peeks ahead and + * determines this. + */ +static boolean_t +rustv0_has_name(rust_state_t *restrict st, strview_t *restrict sv, + boolean_t *has_namep) +{ + strview_t save; + + if (HAS_ERROR(st)) + return (B_FALSE); + + DEMDEBUG("%s: str='%.*s'", __func__, SV_PRINT(sv)); + + if (sv_remaining(sv) == 0) + return (B_FALSE); + + sv_init_sv(&save, sv); + + /* For checking the length, we don't care if it's punycode or not */ + (void) sv_consume_if_c(&save, 'u'); + + if (sv_remaining(sv) == 0) { + st->rs_error = EINVAL; + return (B_FALSE); + } + + if (sv_consume_if_c(&save, '0')) { + *has_namep = B_FALSE; + return (B_TRUE); + } + + *has_namep = B_TRUE; + return (B_TRUE); +} + +static boolean_t +rustv0_parse_nested_path(rust_state_t *restrict st, strview_t *restrict sv, + boolean_t in_value) +{ + uint64_t disambiguator = 0; + size_t len = 0; + char ns; + boolean_t ret = B_FALSE; + boolean_t has_name; + + if (HAS_ERROR(st)) + return (B_FALSE); + + DEMDEBUG("%s: str='%.*s'", __func__, SV_PRINT(sv)); + + if (sv_remaining(sv) == 0) + return (B_FALSE); + + SAVE_LEN(st, len); + + ns = sv_consume_c(sv); + + if (!rustv0_parse_path(st, sv, in_value)) + goto done; + + if (!OPTIONAL(st, rustv0_parse_disambiguator(st, sv, &disambiguator))) + goto done; + + if (!rustv0_has_name(st, sv, &has_name)) + goto done; + + if (ISUPPER(ns)) { + if (!rust_append(st, "::{")) + goto done; + + switch (ns) { + case 'C': + if (!rust_append(st, "closure")) + goto done; + break; + case 'S': + if (!rust_append(st, "shim")) + goto done; + break; + default: + if (!rust_appendc(st, ns)) + goto done; + break; + } + + if (has_name && !rust_appendc(st, ':')) + goto done; + + if (!rustv0_parse_undisambiguated_identifier(st, sv, B_FALSE)) + goto done; + + ret = rust_append_printf(st, "#%" PRIu64 "}", disambiguator); + } else { + if (has_name) { + if (!(ret = rust_append(st, "::"))) + goto done; + } + ret = rustv0_parse_undisambiguated_identifier(st, sv, B_FALSE); + } + +done: + DEMDEBUG("%s: nested path = '%.*s' (%s)", __func__, CSTR_END(st, len), + ret ? "success" : "fail"); + + return (ret); +} + +/* + * <disambiguator> = "s" <base-64-number> + * + */ +static boolean_t +rustv0_parse_disambiguator(rust_state_t *restrict st, strview_t *restrict sv, + uint64_t *valp) +{ + if (HAS_ERROR(st) || sv_remaining(sv) < 2) + return (B_FALSE); + + DEMDEBUG("%s: str='%.*s'", __func__, SV_PRINT(sv)); + + *valp = 0; + + if (!sv_consume_if_c(sv, 's')) + return (B_FALSE); + + if (!rustv0_parse_base62(st, sv, valp)) { + st->rs_error = EINVAL; + return (B_FALSE); + } + + /* + * Rust RFC 2603 details this in Appendix A, but not the main + * portion of the RFC. If no disambiguator is present, the value + * is 0, if the decoded value is 0, the index is 1, ... + * rustv0_parse_base62() already adjusts _ -> 0, 0 -> 1, so we + * only need to add one here to complete the adjustment. + */ + *valp = *valp + 1; + + DEMDEBUG("%s: disambiguator=%" PRIu64, __func__, *valp); + return (B_TRUE); +} + +/* <undisambiguated-identifier> = ["u"] <decimal-number> ["_"] <bytes> */ +static boolean_t +rustv0_parse_undisambiguated_identifier(rust_state_t *restrict st, + strview_t *restrict sv, boolean_t repl_underscore) +{ + uint64_t len = 0; + boolean_t puny = B_FALSE; + + if (HAS_ERROR(st)) + return (B_FALSE); + + DEMDEBUG("%s: str='%.*s'", __func__, SV_PRINT(sv)); + + if (sv_remaining(sv) == 0) + return (B_FALSE); + + if (sv_consume_if_c(sv, 'u')) + puny = B_TRUE; + + if (!rust_parse_base10(st, sv, &len)) + return (B_FALSE); + + /* skip optional separator '_' */ + (void) sv_consume_if_c(sv, '_'); + + if (sv_remaining(sv) < len) { + DEMDEBUG("%s: ERROR: identifier length (%" PRIu64 ") " + "> remaining bytes (%zu)", __func__, len, + sv_remaining(sv)); + return (B_FALSE); + } + + /* 0 length identifiers are acceptable */ + if (len == 0) + return (B_TRUE); + + if (puny) { + strview_t ident; + + sv_init_sv_range(&ident, sv, len); + if (!rustv0_puny_decode(st, &ident, repl_underscore)) + return (B_FALSE); + + sv_consume_n(sv, len); + return (B_TRUE); + } + + /* + * rust identifiers do not contain '-'. However ABI identifiers + * are allowed to contain them (e.g. extern "foo-bar" fn ...). + * They are substituted with '_' in the mangled output. If we + * do not need to reverse this, we can just append 'len' bytes + * of sv. Otherwise we need to go through and reverse this + * substitution. + */ + if (!repl_underscore) + return (rust_append_sv(st, len, sv)); + + /* + * We checked earlier that len < sv_remaining(sv); so this loop + * cannot overrun. + */ + for (size_t i = 0; i < len; i++) { + char c = sv_consume_c(sv); + + if (c == '_') + c = '-'; + + if (!rust_appendc(st, c)) + return (B_FALSE); + } + + return (B_TRUE); +} + +/* <backref> = "B" <base-62-number> */ +static boolean_t +rustv0_parse_backref(rust_state_t *restrict st, strview_t *restrict sv, + boolean_t (*fn)(rust_state_t *restrict, strview_t *restrict, boolean_t b), + boolean_t bval) +{ + strview_t backref; + strview_t target; + uint64_t idx = 0; + size_t save_len; + size_t len; + + if (HAS_ERROR(st)) + return (B_FALSE); + + sv_init_sv(&backref, sv); + + if (!sv_consume_if_c(sv, 'B')) + return (B_FALSE); + + DEMDEBUG("%s: str='B%.*s'", __func__, SV_PRINT(sv)); + + if (!rustv0_parse_base62(st, sv, &idx)) { + st->rs_error = EINVAL; + return (B_FALSE); + } + + /* + * Determine how many bytes we've consumed (up to the start of + * the current backref token). + */ + VERIFY3P(backref.sv_first, >=, st->rs_orig.sv_first); + len = (size_t)(uintptr_t)(backref.sv_first - st->rs_orig.sv_first); + + /* + * The backref can only refer to an index prior to the start of + * the current backref token -- that is must always refer back in + * the string, never to the current position or beyond. + */ + if (idx >= len) { + DEMDEBUG("%s: ERROR: backref index (%" PRIu64 ") " + "is out of range [0, %zu)", __func__, idx, len); + st->rs_error = ERANGE; + return (B_FALSE); + } + + /* + * Create a strview_t of the original string (sans prefix) by + * copying from st->rs_orig. The length of the target strview_t is + * capped to end immediately prior to this backref token. Since we + * enforce that backrefs must always refer to already processed + * portions of the string (i.e. must always refer backwards), and the + * length of the strview_t is set to end prior to the start of this + * backref token, we guarantee processing of a backref will always + * terminate before it can possibly encounter this backref token + * and cause a loop -- either the processing terminates normally or + * it reaches the end of the capped strview_t. + */ + sv_init_sv_range(&target, &st->rs_orig, len); + + /* + * Consume all the input in the target strview_t up to the index + */ + sv_consume_n(&target, idx); + + DEMDEBUG("%s: backref starting at %" PRIu64 " str='%.*s'%s", __func__, + idx, SV_PRINT(&target), st->rs_skip ? " (skipping)" : ""); + + /* + * If we're skipping the output, there's no reason to bother reparsing + * the output -- we're not going to save it. We still setup everything + * so that the debug output is still emitted. + */ + if (st->rs_skip) + return (B_TRUE); + + SAVE_LEN(st, save_len); + if (!fn(st, &target, bval)) + return (B_FALSE); + + DEMDEBUG("%s: backref is '%.*s'", __func__, CSTR_END(st, save_len)); + return (B_TRUE); +} + +static boolean_t +rustv0_append_lifetime(rust_state_t *restrict st, uint64_t lifetime) +{ + uint64_t bound_lt; + + if (HAS_ERROR(st)) + return (B_FALSE); + + if (!rust_appendc(st, '\'')) + return (B_FALSE); + + if (lifetime == 0) + return (rust_appendc(st, '_')); + + if (sub_overflow(st->rs_lt_depth, lifetime, &bound_lt)) { + DEMDEBUG("%s: ERROR: lifetime value %" PRIu64 + " > current depth %" PRIu64, __func__, lifetime, + st->rs_lt_depth); + st->rs_lt_depth = ERANGE; + return (B_FALSE); + } + + /* + * Use 'a, 'b, ... + */ + if (bound_lt < 26) { + char c = (char)bound_lt + 'a'; + return (rust_append_printf(st, "%c", c)); + } + + /* + * Otherwise, use '_123, '_456, ... + */ + return (rust_append_printf(st, "_%" PRIu64, bound_lt)); +} + +static boolean_t +rustv0_parse_lifetime(rust_state_t *restrict st, strview_t *restrict sv) +{ + uint64_t lifetime; + + if (!sv_consume_if_c(sv, 'L')) + return (B_FALSE); + + if (!rustv0_parse_base62(st, sv, &lifetime)) + return (B_FALSE); + + return (rustv0_append_lifetime(st, lifetime)); +} + +static boolean_t +rustv0_parse_const_data(rust_state_t *restrict st, + const_type_class_t type_class, strview_t *restrict sv) +{ + uint64_t val = 0; + size_t save_len; + boolean_t neg = B_FALSE; + boolean_t ret = B_FALSE; + + VERIFY3S(type_class, !=, CTC_INVALID); + + if (HAS_ERROR(st)) + return (B_FALSE); + + DEMDEBUG("%s: str='%.*s'", __func__, SV_PRINT(sv)); + SAVE_LEN(st, save_len); + + if (sv_remaining(sv) == 0) + return (B_FALSE); + + if (type_class == CTC_SIGNED && sv_consume_if_c(sv, 'n')) + neg = B_TRUE; + + ret = OPTIONAL(st, rustv0_parse_hex_num(st, sv, &val)) && + sv_consume_if_c(sv, '_'); + if (!ret) + goto done; + + switch (type_class) { + case CTC_SIGNED: + case CTC_UNSIGNED: + ret = rust_append_printf(st, "%s%" PRIu64, neg ? "-" : "", val); + break; + case CTC_BOOL: + if (val > 1) { + DEMDEBUG("%s: invalid bool val %" PRIu64, __func__, + val); + ret = B_FALSE; + break; + } + ret = rust_append_printf(st, "%s", + (val == 0) ? "false" : "true"); + break; + case CTC_CHAR: + if (val > UINT32_MAX) { + DEMDEBUG("%s: char value %" PRIu64 " out of range", + __func__, val); + ret = B_FALSE; + break; + } + + ret = rust_appendc(st, '\'') && rust_append_utf8_c(st, val) && + rust_appendc(st, '\''); + break; + default: + ret = B_FALSE; + } + +done: + DEMDEBUG("%s: const='%.*s' (%s)", __func__, CSTR_END(st, save_len), + ret ? "success" : "fail"); + + return (ret); +} + +static boolean_t +rustv0_parse_const(rust_state_t *restrict st, strview_t *restrict sv, + boolean_t dummy __unused) +{ + strview_t type; + size_t start_len; + const_type_class_t ctype_class; + char ctype; + boolean_t save_skip; + boolean_t ret; + + if (HAS_ERROR(st)) + return (B_FALSE); + + DEMDEBUG("%s: str='%.*s'", __func__, SV_PRINT(sv)); + SAVE_LEN(st, start_len); + + if (sv_remaining(sv) == 0) + return (B_FALSE); + + if (rustv0_parse_backref(st, sv, rustv0_parse_const, B_FALSE)) + return (B_TRUE); + + if (sv_consume_if_c(sv, 'p')) { + ret = rust_appendc(st, '_'); + goto done; + } + + ctype = sv_peek(sv, 0); + ctype_class = rustv0_classify_const_type(ctype); + if (ctype_class == CTC_INVALID) { + DEMDEBUG("%s: const type isn't a valid const generic type", + __func__); + return (B_FALSE); + } + + /* + * This isn't spelled out clearly in Rust RFC 2603, but currently + * only unsigned int types are allowed at this point. However, we + * have a bit of a potential tricky situation. Unlike formatting + * the other tokens, if we want to display the type, we do so + * _after_ the value, even though the type appears first. + * + * This is bit of a hack, but we save off the input position from + * sv before the parse the type. We then parse it without saving + * the resulting value, then parse and output the constant. If + * we wish to then display the type, we can go back and parse + * the type again, this time saving the result. + */ + sv_init_sv(&type, sv); + + SKIP_BEGIN(st, save_skip); + ret = rustv0_parse_type(st, sv, B_FALSE); + SKIP_END(st, save_skip); + + if (!ret) { + DEMDEBUG("%s: const type isn't valid", __func__); + return (B_FALSE); + } + + if (sv_consume_if_c(sv, 'p')) { + ret = rust_appendc(st, '_'); + } else { + ret = rustv0_parse_const_data(st, ctype_class, sv); + } + if (!ret) + goto done; + + if (st->rs_show_const_type) { + ret = rust_append(st, ": ") && + rustv0_parse_uint_type(st, &type); + } + +done: + DEMDEBUG("%s: const='%.*s' (%s)", __func__, CSTR_END(st, start_len), + ret ? "success" : "fail"); + return (ret); +} + +static boolean_t +rustv0_parse_abi(rust_state_t *restrict st, strview_t *restrict sv) +{ + DEMDEBUG("%s: str = '%.*s'", __func__, SV_PRINT(sv)); + + if (sv_consume_if_c(sv, 'C')) + return (rust_appendc(st, 'C')); + + return (rustv0_parse_undisambiguated_identifier(st, sv, B_TRUE)); +} + +static boolean_t +rustv0_parse_binder(rust_state_t *restrict st, strview_t *restrict sv) +{ + uint64_t n, i; + + if (!sv_consume_if_c(sv, 'G')) + return (B_FALSE); + + if (!rustv0_parse_base62(st, sv, &n)) + return (B_FALSE); + n += 1; + + if (!rust_append(st, "for<")) + return (B_FALSE); + + for (i = 0; i < n; i++) { + if (i > 0 && !rust_append(st, ", ")) + return (B_FALSE); + + st->rs_lt_depth++; + if (!rustv0_append_lifetime(st, 1)) + return (B_FALSE); + } + + if (!rust_append(st, "> ")) + return (B_FALSE); + + return (B_TRUE); +} + +/* + * <fn-sig> := [<binder>] ["U"] ["K" <abi>] {type} "E" <type> + * + * Note that while the Rust RFC states the binder is manditory, based on + * actual examples, and comparing with the rust-based demangler, it is in + * fact optional. + */ +static boolean_t +rustv0_parse_fnsig(rust_state_t *restrict st, strview_t *restrict sv) +{ + uint64_t save_lt = st->rs_lt_depth; + + DEMDEBUG("%s: str = '%.*s'", __func__, SV_PRINT(sv)); + + if (!OPTIONAL(st, rustv0_parse_binder(st, sv))) + return (B_FALSE); + + if (sv_consume_if_c(sv, 'U') && !rust_append(st, "unsafe ")) + return (B_FALSE); + + if (sv_consume_if_c(sv, 'K') && + (!rust_append(st, "extern \"") || !rustv0_parse_abi(st, sv) || + !rust_append(st, "\" "))) + return (B_FALSE); + + if (!rust_append(st, "fn(")) + return (B_FALSE); + + if (!rustv0_parse_opt_list(st, sv, rustv0_parse_type, ", ", B_FALSE, + NULL)) { + return (B_FALSE); + } + + if (!rust_appendc(st, ')')) + return (B_FALSE); + + /* If the return type is (), don't print it */ + if (!sv_consume_if_c(sv, 'u')) { + if (!rust_append(st, " -> ")) + return (B_FALSE); + + if (!rustv0_parse_type(st, sv, B_FALSE)) + return (B_FALSE); + } + + st->rs_lt_depth = save_lt; + + return (B_TRUE); +} + +/* + * <dyn-trait-assoc-binding> = "p" <undisambiguated-identifier> <type> + */ +static boolean_t +rustv0_parse_dyn_trait_assoc_binding(rust_state_t *restrict st, + strview_t *restrict sv, boolean_t open) +{ + size_t save_len; + + if (HAS_ERROR(st)) + return (B_FALSE); + + if (sv_remaining(sv) == 0) + return (B_FALSE); + + if (!sv_consume_if_c(sv, 'p')) + return (B_FALSE); + + DEMDEBUG("%s: str='%.*s'", __func__, SV_PRINT(sv)); + SAVE_LEN(st, save_len); + + if (!rust_append(st, open ? ", " : "<")) + return (B_FALSE); + + if (!rustv0_parse_undisambiguated_identifier(st, sv, B_FALSE)) { + st->rs_error = EINVAL; + return (B_FALSE); + } + + if (!rust_append(st, " = ")) + return (B_FALSE); + + if (!rustv0_parse_type(st, sv, B_FALSE)) { + st->rs_error = EINVAL; + return (B_FALSE); + } + + DEMDEBUG("%s: binding='%.*s'", __func__, CSTR_END(st, save_len)); + + return (B_TRUE); +} + +static boolean_t +rustv0_parse_dyn_trait(rust_state_t *restrict st, strview_t *restrict sv, + boolean_t dummy __unused) +{ + boolean_t stay_save = st->rs_args_stay_open; + boolean_t open_save = st->rs_args_is_open; + boolean_t open = B_FALSE; + + if (HAS_ERROR(st)) + return (B_FALSE); + + DEMDEBUG("%s: str='%.*s'", __func__, SV_PRINT(sv)); + + /* + * This is a bit subtle, but when formatting a trait in trait, + * we want something like this: + * + * dyn Trait<T, U, Assoc=X> + * + * instead of + * + * dyn Trait<T, U, <Assoc=X>> + * + * So when parsing the path, if we encounter generic arguments, we want + * the arg list to remain open at the end of processing the path so + * we can append the bindings to it. We set rs_args_stay_open to B_TRUE + * to indidcate to rustv0_parse_path() that a generic argument list + * should not be closed (i.e. don't append a '>' at the end of the + * list). If rustv0_parse_path() encounters a list of generic arguments, + * it will also set rs->args_is_open to indiciate it opened the list. + * We save this in 'open' so that when we process the associated + * bindings, we know if we need to open the list on the first binding + * or not -- we don't want 'dyn Trait<>' if there are no bindings, + * just 'dyn Trait'. + */ + st->rs_args_stay_open = B_TRUE; + st->rs_args_is_open = B_FALSE; + + if (!rustv0_parse_path(st, sv, B_FALSE)) { + st->rs_args_stay_open = stay_save; + st->rs_args_is_open = open_save; + return (B_FALSE); + } + + open = st->rs_args_is_open; + + st->rs_args_stay_open = stay_save; + st->rs_args_is_open = open_save; + + while (rustv0_parse_dyn_trait_assoc_binding(st, sv, open)) { + open = B_TRUE; + } + + if (HAS_ERROR(st)) + return (B_FALSE); + + if (open && !rust_appendc(st, '>')) + return (B_FALSE); + + return (!HAS_ERROR(st)); +} + +static boolean_t +rustv0_parse_dynbounds(rust_state_t *restrict st, strview_t *restrict sv) +{ + uint64_t save_lt = st->rs_lt_depth; + + if (HAS_ERROR(st)) + return (B_FALSE); + + DEMDEBUG("%s: str='%.*s'", __func__, SV_PRINT(sv)); + + /* + * This is another case where Rust RFC2603 seems to disagree with + * the implementation. The RFC implies this is mandatory, while + * the implementations treat it as optional. + */ + if (!OPTIONAL(st, rustv0_parse_binder(st, sv))) + return (B_FALSE); + + if (!rustv0_parse_opt_list(st, sv, rustv0_parse_dyn_trait, " + ", + B_FALSE, NULL)) + return (B_FALSE); + + st->rs_lt_depth = save_lt; + + return (B_TRUE); +} + +static boolean_t +rustv0_parse_generic_arg(rust_state_t *restrict st, strview_t *restrict sv, + boolean_t dummy __unused) +{ + DEMDEBUG("%s: str='%.*s'", __func__, SV_PRINT(sv)); + + if (sv_consume_if_c(sv, 'K')) + return (rustv0_parse_const(st, sv, B_FALSE)); + + if (rustv0_parse_lifetime(st, sv)) + return (B_TRUE); + + return (rustv0_parse_type(st, sv, B_FALSE)); +} + +/* + * Parse a hex value into *valp. Note that rust only uses lower case + * hex values. + */ +static boolean_t +rustv0_parse_hex_num(rust_state_t *restrict st, strview_t *restrict sv, + uint64_t *restrict valp) +{ + uint64_t val = 0; + size_t ndigits = 0; + + if (HAS_ERROR(st)) + return (B_FALSE); + + DEMDEBUG("%s: str='%.*s'", __func__, SV_PRINT(sv)); + + if (sv_remaining(sv) == 0) + return (B_FALSE); + + /* + * Unfortunately, Rust RFC 2603 also doesn't not explicty define + * {hex-digits}. We follow what decimal digits does, and treat a + * leading 0 as a terminator. + */ + while (sv_remaining(sv) > 0) { + char c = sv_peek(sv, 0); + + if (ISDIGIT(c)) { + val *= 16; + val += c - '0'; + } else if (c >= 'a' && c <= 'f') { + val *= 16; + val += c - 'a' + 10; + } else { + break; + } + + sv_consume_n(sv, 1); + + if (++ndigits == 1 && val == 0) + break; + } + + if (ndigits > 0) + *valp = val; + + return ((ndigits > 0) ? B_TRUE : B_FALSE); +} + +/* + * Parse a base62 number into *valp. The number is explicitly terminated + * by a '_'. The values are also offset by 0 -- that is '_' == 0, + * '0_' == 1, ... + */ +static boolean_t +rustv0_parse_base62(rust_state_t *restrict st, strview_t *restrict sv, + uint64_t *restrict valp) +{ + uint64_t val = 0; + char c; + + if (HAS_ERROR(st)) + return (B_FALSE); + + DEMDEBUG("%s: str='%.*s'", __func__, SV_PRINT(sv)); + + if (sv_remaining(sv) == 0) + return (B_FALSE); + + /* A terminating '_' without any digits is 0 */ + if (sv_consume_if_c(sv, '_')) { + *valp = 0; + return (B_TRUE); + } + + /* Need at least one valid digit if > 0 */ + if (!ISALNUM(sv_peek(sv, 0))) + return (B_FALSE); + + while (sv_remaining(sv) > 0) { + c = sv_consume_c(sv); + + if (c == '_') { + /* + * Because a lone '_' was already handled earlier, + * we know we've had at least one other digit and + * can increment the value and return. + */ + *valp = val + 1; + return (B_TRUE); + } else if (ISDIGIT(c)) { + val *= 62; + val += c - '0'; + } else if (ISLOWER(c)) { + val *= 62; + val += c - 'a' + 10; + } else if (ISUPPER(c)) { + val *= 62; + val += c - 'A' + 36; + } else { + return (B_FALSE); + } + } + + /* We reached the end of the string without a terminating _ */ + return (B_FALSE); +} + +static const_type_class_t +rustv0_classify_const_type(char type) +{ + switch (type) { + case 'h': case 't': case 'm': case 'y': case 'o': case 'j': + return (CTC_UNSIGNED); + case 'a': case 'i': case 'l': case 'n': case 's': case 'x': + return (CTC_SIGNED); + case 'b': + return (CTC_BOOL); + case 'c': + return (CTC_CHAR); + default: + return (CTC_INVALID); + } +} + +/* + * Make sure the name is a plausible mangled rust symbol. + * Non-ASCII are never allowed. Rust itself uses [_0-9A-Za-z], however + * some things will add a suffix starting with a '.' (e.g. LLVM thin LTO). + * As such we proceed in two phases. We first only allow [_0-9A-Z-az] until + * we encounter a '.'. At that point, any ASCII character is allowed. + */ +static boolean_t +rustv0_valid_sym(const strview_t *sv) +{ + size_t i; + boolean_t check_rust = B_TRUE; + + for (i = 0; i < sv->sv_rem; i++) { + char c = sv->sv_first[i]; + + if (ISALNUM(c) || c == '_') + continue; + + if (c == '.') { + check_rust = B_FALSE; + continue; + } + + if (check_rust || (c & 0x80) != 0) { + DEMDEBUG("%s: ERROR found invalid character '%c' " + "in '%.*s' at index %zu", + __func__, c, SV_PRINT(sv), i); + return (B_FALSE); + } + } + return (B_TRUE); +} diff --git a/usr/src/lib/libdemangle/common/rust-v0puny.c b/usr/src/lib/libdemangle/common/rust-v0puny.c new file mode 100644 index 0000000000..9659902ac1 --- /dev/null +++ b/usr/src/lib/libdemangle/common/rust-v0puny.c @@ -0,0 +1,264 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2019 Joyent, Inc. + * Copyright 2021 Jason King + */ + +#include <inttypes.h> +#include <libcustr.h> +#include <limits.h> +#include <string.h> +#include <sys/byteorder.h> +#include "rust.h" +#include "strview.h" + +/* + * The rust v0 encoding (rust RFC 2603) uses a slightly modified + * version of punycode to encode characters that are not ASCII. + * The big difference is that '_' is used to separate the ASCII codepoints + * from the non-ASCII code points instead of '-'. + * + * The decoding is taken almost directly from (IETF) RFC 3492 + */ + +#define BASE 36 +#define TMIN 1 +#define TMAX 26 +#define SKEW 38 +#define DAMP 700 +#define INITIAL_BIAS 72 +#define INITIAL_N 0x80 +#define DELIMITER '_' + +static inline uint32_t char_val(char); + +static size_t +rustv0_puny_adapt(size_t delta, size_t npoints, boolean_t first) +{ + size_t k = 0; + + delta = first ? delta / DAMP : delta / 2; + delta += delta / npoints; + while (delta > ((BASE - TMIN) * TMAX) / 2) { + delta /= (BASE - TMIN); + k += BASE; + } + + return (k + (((BASE - TMIN + 1) * delta) / (delta + SKEW))); +} + +boolean_t +rustv0_puny_decode(rust_state_t *restrict st, strview_t *restrict src, + boolean_t repl_underscore) +{ + uint32_t *buf; + size_t bufalloc; /* in units of uint32_t */ + size_t buflen; + size_t nbasic; + size_t i, old_i, k, w; + size_t n = INITIAL_N; + size_t bias = INITIAL_BIAS; + size_t delim_idx = 0; + boolean_t ret = B_FALSE; + char c; + + DEMDEBUG("%s: str='%.*s'", __func__, SV_PRINT(src)); + + /* + * The decoded string should never contain more codepoints than + * the original string, so creating a temporary buffer large + * enought to hold sv_remaining(src) uint32_t's should be + * large enough. + * + * This also serves as a size check -- xcalloc will fail if the + * resulting size of the buf (sizeof (uint32_t) * bufalloc) >= + * SIZE_MAX. If xcalloc succeeds, we therefore know that that + * buflen cannot overflow. + */ + buflen = 0; + bufalloc = sv_remaining(src) + 1; + buf = xcalloc(st->rs_ops, bufalloc, sizeof (uint32_t)); + if (buf == NULL) { + SET_ERROR(st); + return (B_FALSE); + } + + /* + * Find the position of the last delimiter (if any). + * IETF RFC 3492 3.1 states that the delimiter is present if and only + * if there are a non-zero number of basic (ASCII) code points. Since + * the delimiter itself is a basic code point, the last one present + * in the original string is the actual delimiter between the basic + * and non-basic code points. Earlier occurences of the delimiter + * are treated as normal basic code points. For plain punycode, an + * all ASCII string encoded with punycode would terminate with a + * final delimiter, and a name with all non-basic code points would + * not have a delimiter at all. With the rust v0 encoding, punycode + * encoded identifiers have a 'u' prefix prior to the identifier + * length (['u'] <decimal-number> <bytes>), so we should never + * encounter an all ASCII name that's encoded with punycode (we error + * on this). For an all non-basic codepoint identifier, no delimiter + * will be present, and we treat that the same as the delimiter being + * in the first position of the string, and consume it (if present) + * when we transition from copying the basic code points (which there + * will be none in this situation) to non-basic code points. + */ + for (i = 0; i < src->sv_rem; i++) { + if (src->sv_first[i] == DELIMITER) { + delim_idx = i; + } + } + VERIFY3U(delim_idx, <, bufalloc); + + if (delim_idx + 1 == sv_remaining(src)) { + DEMDEBUG("%s: encountered an all-ASCII name encoded with " + "punycode", __func__); + goto done; + } + + /* Copy all the basic characters up to the delimiter into buf */ + for (nbasic = 0; nbasic < delim_idx; nbasic++) { + c = sv_consume_c(src); + + /* The rust prefix check should guarantee this */ + VERIFY3U(c, <, 0x80); + + /* + * Normal rust identifiers do not contain '-' in them. + * However ABI identifiers could contain a dash. Those + * are translated to _, and we need to replace accordingly + * when asked. + */ + if (repl_underscore && c == '_') + c = '-'; + + buf[nbasic] = c; + buflen++; + } + DEMDEBUG("%s: %" PRIu32 " ASCII codepoints copied", __func__, nbasic); + + /* + * Consume delimiter between basic and non-basic code points if present. + * See above for explanation why it may not be present. + */ + (void) sv_consume_if_c(src, DELIMITER); + + DEMDEBUG("%s: non-ASCII codepoints to decode: %.*s", __func__, + SV_PRINT(src)); + + for (i = 0; sv_remaining(src) > 0; i++) { + VERIFY3U(i, <=, buflen); + + /* + * Guarantee we have enough space to insert another codepoint. + * Our buffer sizing above should prevent this from ever + * tripping, but check this out of paranoia. + */ + VERIFY3U(buflen, <, bufalloc - 1); + + /* decode the next codepoint */ + for (old_i = i, k = BASE, w = 1; ; k += BASE) { + size_t t; + uint32_t digit; + + if (sv_remaining(src) == 0) + goto done; + + digit = char_val(sv_consume_c(src)); + if (digit >= BASE) + goto done; + + i = i + digit * w; + + if (k <= bias) + t = TMIN; + else if (k >= bias + TMAX) + t = TMAX; + else + t = k - bias; + + if (digit < t) + break; + + w = w * (BASE - t); + } + buflen++; + + bias = rustv0_puny_adapt(i - old_i, buflen, + (old_i == 0) ? B_TRUE : B_FALSE); + n = n + i / buflen; + i = i % buflen; + + DEMDEBUG("%s: insert \\u%04" PRIx32 " at index %zu (len = %zu)", + __func__, n, i, buflen); + + /* + * At the start of this while loop, we guaranteed + * buflen < bufalloc - 1. Therefore we know there is room + * to move over the contents of buf at i to make room + * for the codepoint. We also just guaranteed that i + * is in the range [0, buflen), so this should always be + * safe. + */ + (void) memmove(buf + i + 1, buf + i, + (buflen - i) * sizeof (uint32_t)); + +#if _LP64 + /* + * This is always false for ILP32 and smatch will also complain, + * so we just omit it for ILP32. + */ + if (n > UINT32_MAX) { + DEMDEBUG("%s: ERROR: utf8 value is out of range", + __func__); + goto done; + } +#endif + + buf[i] = (uint32_t)n; + } + + DEMDEBUG("%s: inserted %zu non-basic code points", __func__, + buflen - nbasic); + + for (i = 0; i < buflen; i++) { + if (!rust_append_utf8_c(st, buf[i])) + goto done; + } + ret = B_TRUE; + +done: + xfree(st->rs_ops, buf, bufalloc * sizeof (uint32_t)); + return (ret); +} + +/* + * Convert [0-9][a-z] to a value [0..35]. Rust's punycode encoding always + * uses lowercase, so we treat uppercase (and any other characters) as + * invalid, and return BASE (36) to indicate a bad value. + */ +static inline uint32_t +char_val(char c) +{ + uint32_t v = c; + + if (ISLOWER(c)) { + return (c - 'a'); + } else if (ISDIGIT(c)) { + return (c - '0' + 26); + } else { + DEMDEBUG("%s: ERROR: invalid character 0x%02x encountered", + __func__, v); + return (BASE); + } +} diff --git a/usr/src/lib/libdemangle/common/rust.c b/usr/src/lib/libdemangle/common/rust.c index 9b145ca841..ce1fca4859 100644 --- a/usr/src/lib/libdemangle/common/rust.c +++ b/usr/src/lib/libdemangle/common/rust.c @@ -10,564 +10,417 @@ */ /* - * Copyright 2019, Joyent, Inc. * Copyright 2021 Jason King + * Copyright 2019 Joyent, Inc. */ #include <errno.h> +#include <langinfo.h> #include <libcustr.h> #include <limits.h> +#include <stdarg.h> #include <string.h> -#include <sys/ctype.h> /* We want the C locale ISXXX() versions */ -#include <sys/debug.h> -#include <stdio.h> -#include <sys/sysmacros.h> -#include "strview.h" #include "demangle_int.h" +#include "rust.h" -/* - * Unfortunately, there is currently no official specification for the rust - * name mangling. This is an attempt to document the understanding of the - * mangling used here. It is based off examination of - * https://docs.rs/rustc-demangle/0.1.13/rustc_demangle/ - * - * A mangled rust name is: - * <prefix> <name> - * - * <prefix> ::= _Z - * __Z - * - * <name> ::= N <name-segment>+ [<hash>] E - * - * <name-segment> ::= <len> <name-chars>{len} - * - * <len> ::= [1-9][0-9]+ - * - * <name-chars> ::= <[A-Za-z]> <[A-Za-z0-9]>* - * <separator> - * <special> - * - * <separator> ::= '..' # '::' - * - * <special> ::= $SP$ # ' ' - * $BP$ # '*' - * $RF$ # '&' - * $LT$ # '<' - * $GT$ # '>' - * $LP$ # '(' - * $RP$ # ')' - * $C$ # ',' - * $u7e$ # '~' - * $u20$ # ' ' - * $u27$ # '\'' - * $u3d$ # '=' - * $u5b$ # '[' - * $u5d$ # ']' - * $u7b$ # '{' - * $u7d$ # '}' - * $u3b$ # ';' - * $u2b$ # '+' - * $u22$ # '"' - * - * <hash> := <len> h <hex-digits>+ - * - * <hex-digits> := <[0-9a-f]> - */ - -typedef struct rustdem_state { - const char *rds_str; - custr_t *rds_demangled; - sysdem_ops_t *rds_ops; - int rds_error; -} rustdem_state_t; - -static const struct rust_charmap { - const char *ruc_seq; - char ruc_ch; -} rust_charmap[] = { - { "$SP$", '@' }, - { "$BP$", '*' }, - { "$RF$", '&' }, - { "$LT$", '<' }, - { "$GT$", '>' }, - { "$LP$", '(' }, - { "$RP$", ')' }, - { "$C$", ',' }, - { "$u7e$", '~' }, - { "$u20$", ' ' }, - { "$u27$", '\'' }, - { "$u3d$", '=' }, - { "$u5b$", '[' }, - { "$u5d$", ']' }, - { "$u7b$", '{' }, - { "$u7d$", '}' }, - { "$u3b$", ';' }, - { "$u2b$", '+' }, - { "$u22$", '"' } -}; -static const size_t rust_charmap_sz = ARRAY_SIZE(rust_charmap); - -static void *rustdem_alloc(custr_alloc_t *, size_t); -static void rustdem_free(custr_alloc_t *, void *, size_t); - -static boolean_t rustdem_append_c(rustdem_state_t *, char); -static boolean_t rustdem_all_ascii(const strview_t *); - -static boolean_t rustdem_parse_prefix(rustdem_state_t *, strview_t *); -static boolean_t rustdem_parse_name(rustdem_state_t *, strview_t *); -static boolean_t rustdem_parse_hash(rustdem_state_t *, strview_t *); -static boolean_t rustdem_parse_num(rustdem_state_t *, strview_t *, uint64_t *); -static boolean_t rustdem_parse_special(rustdem_state_t *, strview_t *); -static boolean_t rustdem_add_sep(rustdem_state_t *); - -char * -rust_demangle(const char *s, size_t slen, sysdem_ops_t *ops) +static void * +rust_cualloc(custr_alloc_t *cua, size_t len) { - rustdem_state_t st = { - .rds_str = s, - .rds_ops = ops, - }; - custr_alloc_ops_t custr_ops = { - .custr_ao_alloc = rustdem_alloc, - .custr_ao_free = rustdem_free - }; - custr_alloc_t custr_alloc = { - .cua_version = CUSTR_VERSION - }; - strview_t sv; - int ret; - - if (custr_alloc_init(&custr_alloc, &custr_ops) != 0) - return (NULL); - custr_alloc.cua_arg = &st; - - sv_init_str(&sv, s, s + slen); - - if (sv_remaining(&sv) < 1 || sv_peek(&sv, -1) != 'E') { - DEMDEBUG("ERROR: string is either too small or does not end " - "with 'E'"); - errno = EINVAL; - return (NULL); - } - - if (!rustdem_parse_prefix(&st, &sv)) { - DEMDEBUG("ERROR: could not parse prefix"); - errno = EINVAL; - return (NULL); - } - DEMDEBUG("parsed prefix; remaining='%.*s'", SV_PRINT(&sv)); - - if (!rustdem_all_ascii(&sv)) { - /* rustdem_all_ascii() provides debug output */ - errno = EINVAL; - return (NULL); - } - - if ((ret = custr_xalloc(&st.rds_demangled, &custr_alloc)) != 0) - return (NULL); - - if (!rustdem_parse_name(&st, &sv)) { - if (st.rds_error == 0) - st.rds_error = EINVAL; - goto fail; - } - - if (sv_remaining(&sv) > 0) { - DEMDEBUG("ERROR: unexpected trailing characters after " - "terminating 'E': '%.*s'", SV_PRINT(&sv)); - st.rds_error = EINVAL; - goto fail; - } - - char *res = xstrdup(ops, custr_cstr(st.rds_demangled)); - if (res == NULL) { - st.rds_error = errno; - goto fail; - } - - custr_free(st.rds_demangled); - DEMDEBUG("result = '%s'", res); - return (res); - -fail: - custr_free(st.rds_demangled); - errno = st.rds_error; - return (NULL); + rust_state_t *st = cua->cua_arg; + return (zalloc(st->rs_ops, len)); } -static boolean_t -rustdem_parse_prefix(rustdem_state_t *st, strview_t *svp) +static void +rust_cufree(custr_alloc_t *cua, void *p, size_t len) { - strview_t pfx; + rust_state_t *st = cua->cua_arg; + xfree(st->rs_ops, p, len); +} - sv_init_sv(&pfx, svp); +static const custr_alloc_ops_t rust_custr_ops = { + .custr_ao_alloc = rust_cualloc, + .custr_ao_free = rust_cufree +}; - DEMDEBUG("checking for '_Z' or '__Z' in '%.*s'", SV_PRINT(&pfx)); +boolean_t +rust_appendc(rust_state_t *st, char c) +{ + custr_t *cus = st->rs_demangled; - if (st->rds_error != 0) + if (HAS_ERROR(st)) return (B_FALSE); - if (!sv_consume_if_c(&pfx, '_')) - return (B_FALSE); + if (st->rs_skip) + return (B_TRUE); - (void) sv_consume_if_c(&pfx, '_'); + switch (c) { + case '\a': + return (rust_append(st, "\\a")); + case '\b': + return (rust_append(st, "\\b")); + case '\f': + return (rust_append(st, "\\f")); + case '\n': + return (rust_append(st, "\\n")); + case '\r': + return (rust_append(st, "\\r")); + case '\t': + return (rust_append(st, "\\t")); + case '\v': + return (rust_append(st, "\\v")); + case '\\': + return (rust_append(st, "\\\\")); + } + + if (c < ' ') + return (rust_append_printf(st, "\\x%02" PRIx8, (uint8_t)c)); - if (!sv_consume_if_c(&pfx, 'Z')) + if (custr_appendc(cus, c) != 0) { + SET_ERROR(st); return (B_FALSE); + } - /* Update svp with new position */ - sv_init_sv(svp, &pfx); return (B_TRUE); } -static boolean_t -rustdem_parse_name_segment(rustdem_state_t *st, strview_t *svp, boolean_t first) +/* + * Append a UTF-8 code point. If we're not in a UTF-8 locale, this gets + * appended as '\u<hex codepoint>' otherwise the character itself is + * added. + */ +boolean_t +rust_append_utf8_c(rust_state_t *st, uint32_t val) { - strview_t sv; - strview_t name; - uint64_t len; - size_t rem; - boolean_t last = B_FALSE; + custr_t *cus = st->rs_demangled; + uint_t n = 0; + uint8_t c[4] = { 0 }; - if (st->rds_error != 0 || sv_remaining(svp) == 0) + if (HAS_ERROR(st)) return (B_FALSE); - sv_init_sv(&sv, svp); - - if (!rustdem_parse_num(st, &sv, &len)) { - DEMDEBUG("ERROR: no leading length"); - st->rds_error = EINVAL; - return (B_FALSE); + if (!st->rs_isutf8) { + if (val < 0x80) + return (rust_appendc(st, (char)val)); + if (val < 0x10000) + return (rust_append_printf(st, "\\u%04" PRIx32, val)); + return (rust_append_printf(st, "\\U%08" PRIx32, val)); } - rem = sv_remaining(&sv); - - if (rem < len) { - st->rds_error = EINVAL; + if (val < 0x80) { + return (rust_appendc(st, (char)val)); + } else if (val < 0x800) { + c[0] = 0xc0 | ((val >> 6) & 0x1f); + c[1] = 0x80 | (val & 0x3f); + n = 2; + } else if (val < 0x10000) { + c[0] = 0xe0 | ((val >> 12) & 0x0f); + c[1] = 0x80 | ((val >> 6) & 0x3f); + c[2] = 0x80 | (val & 0x3f); + n = 3; + } else if (val < 0x110000) { + c[0] = 0xf0 | ((val >> 18) & 0x7); + c[1] = 0x80 | ((val >> 12) & 0x3f); + c[2] = 0x80 | ((val >> 6) & 0x3f); + c[3] = 0x80 | (val & 0x3f); + n = 4; + } else { + DEMDEBUG("%s: invalid unicode character \\u%" PRIx32, __func__, + val); return (B_FALSE); } - /* Is this the last segment before the terminating E? */ - if (rem == len + 1) { - VERIFY3U(sv_peek(&sv, -1), ==, 'E'); - last = B_TRUE; + for (uint_t i = 0; i < n; i++) { + if (custr_appendc(cus, c[i]) != 0) { + SET_ERROR(st); + return (B_FALSE); + } } - if (!first && !rustdem_add_sep(st)) - return (B_FALSE); - - /* Reduce length of seg to the length we parsed */ - (void) sv_init_sv_range(&name, &sv, len); - - DEMDEBUG("%s: segment='%.*s'", __func__, SV_PRINT(&name)); - - /* - * A rust hash starts with 'h', and is the last component of a name - * before the terminating 'E'. It is however not always present - * in every mangled symbol, and a last segment that starts with 'h' - * could be confused for it, so failing to parse it just means - * we don't have a trailing hash. - */ - if (sv_peek(&name, 0) == 'h' && last) { - if (rustdem_parse_hash(st, &name)) - goto done; - - /* - * However any error other than 'not a hash' (e.g. ENOMEM) - * means we should fail. - */ - if (st->rds_error != 0) - goto done; - } + return (B_TRUE); +} - while (sv_remaining(&name) > 0) { - switch (sv_peek(&name, 0)) { - case '$': - if (rustdem_parse_special(st, &name)) - continue; - break; - case '_': - if (sv_peek(&name, 1) == '$') { - /* - * Only consume/ignore '_'. Leave - * $ for next round. - */ - sv_consume_n(&name, 1); - continue; - } - break; - case '.': - /* Convert '..' to '::' */ - if (sv_peek(&name, 1) != '.') - break; +boolean_t +rust_append(rust_state_t *st, const char *s) +{ + custr_t *cus = st->rs_demangled; - if (!rustdem_add_sep(st)) - return (B_FALSE); + if (HAS_ERROR(st)) + return (B_FALSE); - sv_consume_n(&name, 2); - continue; - default: - break; - } + if (st->rs_skip) + return (B_TRUE); - if (custr_appendc(st->rds_demangled, - sv_consume_c(&name)) != 0) { - st->rds_error = ENOMEM; - return (B_FALSE); - } + if (custr_append(cus, s) != 0) { + SET_ERROR(st); + return (B_FALSE); } -done: - sv_consume_n(&sv, len); - VERIFY3P(svp->sv_first, <=, sv.sv_first); - DEMDEBUG("%s: consumed '%.*s'", __func__, - (int)(sv.sv_first - svp->sv_first), svp->sv_first); - sv_init_sv(svp, &sv); return (B_TRUE); } -/* - * Parse N (<num><name>{num})+[<num>h<hex digits>]E - */ -static boolean_t -rustdem_parse_name(rustdem_state_t *st, strview_t *svp) +boolean_t +rust_append_sv(rust_state_t *restrict st, uint64_t n, strview_t *restrict sv) { - strview_t name; - boolean_t first = B_TRUE; - - if (st->rds_error != 0) + if (HAS_ERROR(st)) return (B_FALSE); - sv_init_sv(&name, svp); - - DEMDEBUG("%s: name = '%.*s'", __func__, SV_PRINT(&name)); + if (st->rs_skip) { + sv_consume_n(sv, (size_t)n); + return (B_TRUE); + } - if (sv_remaining(&name) == 0) { - DEMDEBUG("%s: empty name", __func__); + if (n > sv_remaining(sv)) { + DEMDEBUG("%s: ERROR amount to append (%" PRIu64 ") > " + "remaining bytes (%zu)", __func__, n, sv_remaining(sv)); + st->rs_error = ERANGE; return (B_FALSE); } - if (!sv_consume_if_c(&name, 'N')) { - DEMDEBUG("%s: does not start with 'N'", __func__); + if (n > INT_MAX) { + DEMDEBUG("%s: amount (%" PRIu64 ") > INT_MAX", __func__, n); + st->rs_error = ERANGE; return (B_FALSE); } - while (sv_remaining(&name) > 0 && sv_peek(&name, 0) != 'E') { - if (!rustdem_parse_name_segment(st, &name, first)) - return (B_FALSE); - first = B_FALSE; + if (custr_append_printf(st->rs_demangled, "%.*s", + (int)n, sv->sv_first) != 0) { + SET_ERROR(st); + return (B_FALSE); } - VERIFY(sv_consume_if_c(&name, 'E')); - - VERIFY3P(svp->sv_first, <=, name.sv_first); - DEMDEBUG("%s: consumed '%.*s'", __func__, - (int)(name.sv_first - svp->sv_first), svp->sv_first); + sv_consume_n(sv, (size_t)n); - sv_init_sv(svp, &name); return (B_TRUE); } -static boolean_t -rustdem_parse_hash(rustdem_state_t *st, strview_t *svp) +boolean_t +rust_append_printf(rust_state_t *st, const char *fmt, ...) { - strview_t sv; - - sv_init_sv(&sv, svp); + va_list ap; + int ret; - VERIFY(sv_consume_if_c(&sv, 'h')); - if (!rustdem_append_c(st, 'h')) + if (HAS_ERROR(st)) return (B_FALSE); - while (sv_remaining(&sv) > 0) { - char c = sv_consume_c(&sv); + if (st->rs_skip) + return (B_TRUE); - switch (c) { - /* - * The upper-case hex digits (A-F) are excluded as valid - * hash values for several reasons: - * - * 1. It would result in two different possible names for - * the same function, leading to ambiguity in linking (among - * other things). - * - * 2. It would cause potential ambiguity in parsing -- is a - * trailing 'E' part of the hash, or the terminating character - * in the mangled name? - * - * 3. No examples were able to be found in the wild where - * uppercase digits are used, and other rust demanglers all - * seem to assume the hash must contain lower-case hex digits. - */ - case '0': case '1': case '2': case '3': - case '4': case '5': case '6': case '7': - case '8': case '9': case 'a': case 'b': - case 'c': case 'd': case 'e': case 'f': - if (!rustdem_append_c(st, c)) - return (B_FALSE); - break; - default: - return (B_FALSE); - } - } + va_start(ap, fmt); + ret = custr_append_vprintf(st->rs_demangled, fmt, ap); + va_end(ap); - sv_init_sv(svp, &sv); - return (B_TRUE); + if (ret == 0) + return (B_TRUE); + SET_ERROR(st); + return (B_FALSE); } -/* - * We have to pick an arbitrary limit here; 999,999,999 fits comfortably - * within an int32_t, so let's go with that, as it seems unlikely we'd - * ever see a larger value in context. - */ -#define MAX_DIGITS 9 - -static boolean_t -rustdem_parse_num(rustdem_state_t *restrict st, strview_t *restrict svp, +boolean_t +rust_parse_base10(rust_state_t *restrict st, strview_t *restrict sv, uint64_t *restrict valp) { - strview_t snum; uint64_t v = 0; - size_t ndigits = 0; char c; - if (st->rds_error != 0) + if (HAS_ERROR(st) || sv_remaining(sv) == 0) return (B_FALSE); - sv_init_sv(&snum, svp); - - DEMDEBUG("%s: str='%.*s'", __func__, SV_PRINT(&snum)); - - c = sv_peek(&snum, 0); - if (!ISDIGIT(c)) { - DEMDEBUG("%s: ERROR no digits in str\n", __func__); - st->rds_error = EINVAL; - return (B_FALSE); - } + c = sv_peek(sv, 0); /* - * Since there is currently no official specification on rust name - * mangling, only that it has been stated that rust follows what - * C++ mangling does. In the Itanium C++ ABI (what practically - * every non-Windows C++ implementation uses these days), it - * explicitly disallows leading 0s in numeric values (except for - * substition and template indexes, which aren't relevant here). - * We enforce the same restriction -- if a rust implementation allowed - * leading zeros in numbers (basically segment lengths) it'd - * cause all sorts of ambiguity problems with names that likely lead - * to much bigger problems with linking and such, so this seems - * reasonable. + * Since the legacy rust encoding states that it follows the + * Itanium C++ mangling format, we match the behavior of the + * Itanium C++ ABI in disallowing leading 0s in decimal numbers. + * + * For Rust encoding v0, RFC2603 currently has omitted the + * actual definition of <decimal-number>. However examination of + * other implementations written in tandem with the mangling + * implementation suggest that <decimal-number> can be expressed + * by the eregex: 0|[1-9][0-9]* -- that is a '0' is allowed and + * terminates the token, while any other leading digit allows + * parsing to continue until a non-digit is encountered, the + * end of the string is encountered, or overflow is encountered. */ if (c == '0') { - DEMDEBUG("%s: ERROR number starts with leading 0\n", __func__); - st->rds_error = EINVAL; + if (st->rs_encver == RUSTENC_V0) { + sv_consume_n(sv, 1); + *valp = 0; + return (B_TRUE); + } + + DEMDEBUG("%s: ERROR number starts with leading 0\n", + __func__); + st->rs_error = EINVAL; + return (B_FALSE); + } else if (!ISDIGIT(c)) { return (B_FALSE); } - while (sv_remaining(&snum) > 0 && ndigits <= MAX_DIGITS) { - c = sv_consume_c(&snum); + while (sv_remaining(sv) > 0) { + uint64_t cval; + c = sv_peek(sv, 0); if (!ISDIGIT(c)) break; + sv_consume_n(sv, 1); - v *= 10; - v += c - '0'; - ndigits++; - } + cval = c - '0'; - if (ndigits > MAX_DIGITS) { - DEMDEBUG("%s: value %llu is too large\n", __func__, v); - st->rds_error = ERANGE; - return (B_FALSE); - } + if (mul_overflow(v, 10, &v)) { + DEMDEBUG("%s: multiplication overflowed\n", __func__); + st->rs_error = EOVERFLOW; + return (B_FALSE); + } - DEMDEBUG("%s: num=%llu", __func__, v); + if (add_overflow(v, cval, &v)) { + DEMDEBUG("%s: addition overflowed\n", __func__); + st->rs_error = EOVERFLOW; + return (B_FALSE); + } + } *valp = v; - sv_consume_n(svp, ndigits); return (B_TRUE); } static boolean_t -rustdem_parse_special(rustdem_state_t *restrict st, strview_t *restrict svp) +rust_parse_prefix(rust_state_t *restrict st, strview_t *restrict sv) { - if (st->rds_error != 0) + DEMDEBUG("checking prefix in '%.*s'", SV_PRINT(sv)); + + if (HAS_ERROR(st)) return (B_FALSE); - if (sv_peek(svp, 0) != '$') + if (!sv_consume_if_c(sv, '_')) return (B_FALSE); - for (size_t i = 0; i < rust_charmap_sz; i++) { - if (sv_consume_if(svp, rust_charmap[i].ruc_seq)) { - if (!rustdem_append_c(st, rust_charmap[i].ruc_ch)) - return (B_FALSE); - return (B_TRUE); + /* + * MacOS prepends an additional '_' -- allow that in case + * we're given symbols from a MacOS object. + */ + (void) sv_consume_if_c(sv, '_'); + + if (sv_consume_if_c(sv, 'Z')) { + /* + * Legacy names must start with '[_]_Z' + */ + st->rs_encver = RUSTENC_LEGACY; + DEMDEBUG("name is encoded using the rust legacy mangling " + "scheme"); + } else if (sv_consume_if_c(sv, 'R')) { + uint64_t ver = 0; + + /* + * The non-legacy encoding is versioned. After the initial + * 'R' is the version. This isn't spelled out clearly in the + * RFC, but many numeric values encoded take an approach of + * a value of 0 is omitted, and any digits represent the + * value - 1. In other words, in this case, no digits means + * version 0, '_R0...' would be version 1, 'R1...' would + * be version 2, etc. Currently only version 0 is defined, + * but we try to provide a (hopefully) useful message + * when debugging, even if we can't use the version value + * beyond that. + */ + if (rust_parse_base10(st, sv, &ver)) { + DEMDEBUG("%s: ERROR: an unsupported encoding version " + "(%" PRIu64 ") was encountered", ver + 1); + st->rs_error = ENOTSUP; + return (B_FALSE); } + + st->rs_encver = RUSTENC_V0; + DEMDEBUG("name is encoded using the v0 mangling scheme"); + } else { + DEMDEBUG("did not find a valid rust prefix"); + return (B_FALSE); } - return (B_FALSE); + + sv_init_sv(&st->rs_orig, sv); + return (B_TRUE); +} + +static void +rust_fini_state(rust_state_t *st) +{ + custr_free(st->rs_demangled); + custr_alloc_fini(&st->rs_cualloc); } static boolean_t -rustdem_add_sep(rustdem_state_t *st) +rust_init_state(rust_state_t *restrict st, const char *s, sysdem_ops_t *ops) { - if (st->rds_error != 0) + const char *codeset; + + (void) memset(st, 0, sizeof (*st)); + + st->rs_str = s; + st->rs_ops = ops; + + st->rs_cualloc.cua_version = CUSTR_VERSION; + if (custr_alloc_init(&st->rs_cualloc, &rust_custr_ops) != 0) return (B_FALSE); + st->rs_cualloc.cua_arg = st; - if (!rustdem_append_c(st, ':') || - !rustdem_append_c(st, ':')) + if (custr_xalloc(&st->rs_demangled, &st->rs_cualloc) != 0) { + custr_alloc_fini(&st->rs_cualloc); return (B_FALSE); + } + + codeset = nl_langinfo(CODESET); + if (codeset != NULL && strcmp(codeset, "UTF-8") == 0) + st->rs_isutf8 = B_TRUE; return (B_TRUE); } -static boolean_t -rustdem_append_c(rustdem_state_t *st, char c) +char * +rust_demangle(const char *s, size_t len, sysdem_ops_t *ops) { - if (st->rds_error != 0) - return (B_FALSE); + rust_state_t st; + strview_t sv = { 0 }; + boolean_t success = B_FALSE; + int e = 0; + char *out = NULL; - if (custr_appendc(st->rds_demangled, c) == 0) - return (B_TRUE); + if (!rust_init_state(&st, s, ops)) + return (NULL); - st->rds_error = errno; - return (B_FALSE); -} + sv_init_str(&sv, s, s + len); -static boolean_t -rustdem_all_ascii(const strview_t *svp) -{ - strview_t p; + if (!rust_parse_prefix(&st, &sv)) { + if (st.rs_error == 0) + st.rs_error = EINVAL; + goto done; + } - sv_init_sv(&p, svp); + DEMDEBUG("parsed prefix; remaining string='%.*s'", SV_PRINT(&sv)); - while (sv_remaining(&p) > 0) { - char c = sv_consume_c(&p); + switch (st.rs_encver) { + case RUSTENC_LEGACY: + success = rust_demangle_legacy(&st, &sv); + break; + case RUSTENC_V0: + success = rust_demangle_v0(&st, &sv); + break; + } - /* - * #including <sys/ctype.h> conflicts with <ctype.h>. Since - * we want the C locale macros (ISDIGIT, etc), it also means - * we can't use isascii(3C). - */ - if ((c & 0x80) != 0) { - DEMDEBUG("%s: found non-ascii character 0x%02hhx at " - "offset %tu", __func__, c, - (ptrdiff_t)(p.sv_first - svp->sv_first)); - return (B_FALSE); - } +done: + if (success) { + out = xstrdup(ops, custr_cstr(st.rs_demangled)); + if (out == NULL) + SET_ERROR(&st); + } else { + DEMDEBUG("%s: failed, str='%s'", __func__, + custr_cstr(st.rs_demangled)); + + st.rs_error = EINVAL; } - return (B_TRUE); -} -static void * -rustdem_alloc(custr_alloc_t *cao, size_t len) -{ - rustdem_state_t *st = cao->cua_arg; - return (zalloc(st->rds_ops, len)); -} + e = st.rs_error; + rust_fini_state(&st); + if (e > 0) + errno = e; -static void -rustdem_free(custr_alloc_t *cao, void *p, size_t len) -{ - rustdem_state_t *st = cao->cua_arg; - xfree(st->rds_ops, p, len); + return (out); } diff --git a/usr/src/lib/libdemangle/common/rust.h b/usr/src/lib/libdemangle/common/rust.h new file mode 100644 index 0000000000..fbe609ab9d --- /dev/null +++ b/usr/src/lib/libdemangle/common/rust.h @@ -0,0 +1,87 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2019 Joyent, Inc. + * Copyright 2021 Jason King + */ + +#ifndef _RUST_H +#define _RUST_H + +#include <errno.h> +#include <sys/types.h> +#include "demangle-sys.h" +#include "demangle_int.h" +#include "strview.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum rustenc_version { + RUSTENC_LEGACY = -1, + RUSTENC_V0 = 0 +} rustenc_version_t; + +typedef struct rust_state { + const char *rs_str; /* The original string */ + custr_t *rs_demangled; + sysdem_ops_t *rs_ops; + custr_alloc_t rs_cualloc; + strview_t rs_orig; /* strview of original string, sans prefix */ + int rs_error; + rustenc_version_t rs_encver; + uint64_t rs_lt_depth; + boolean_t rs_skip; + boolean_t rs_args_stay_open; + boolean_t rs_args_is_open; + boolean_t rs_verbose; + boolean_t rs_show_const_type; + boolean_t rs_isutf8; +} rust_state_t; +#define HAS_ERROR(_st) ((_st)->rs_error != 0) +#define SET_ERROR(_st) ((_st)->rs_error = errno) + +/* + * In certain circumstances, we need to parse an item, but not emit any + * output. These macros assist in that. To use: + * + * rust_state_t *st; + * boolean_t saved_state; + * ... + * SKIP_BEGIN(st, saved_state); + * ... stuff to no emit + * SKIP_END(st, saved_state); + */ +#define SKIP_BEGIN(_st, _save) \ + (_save) = (_st)->rs_skip, \ + (_st)->rs_skip = B_TRUE +#define SKIP_END(_st, _n) (_st)->rs_skip = (_n) + +boolean_t rust_appendc(rust_state_t *, char); +boolean_t rust_append(rust_state_t *, const char *); +boolean_t rust_append_printf(rust_state_t *, const char *, ...) __PRINTFLIKE(2); +boolean_t rust_append_sv(rust_state_t *restrict, uint64_t, strview_t *restrict); +boolean_t rust_append_utf8_c(rust_state_t *, uint32_t); +boolean_t rust_parse_base10(rust_state_t *restrict, strview_t *restrict, + uint64_t *restrict); +boolean_t rust_demangle_legacy(rust_state_t *restrict, strview_t *restrict); +boolean_t rust_demangle_v0(rust_state_t *restrict, strview_t *restrict); + +boolean_t rustv0_puny_decode(rust_state_t *restrict, strview_t *restrict, + boolean_t); + +#ifdef __cplusplus +} +#endif + +#endif /* _RUST_H */ diff --git a/usr/src/lib/libdemangle/common/str.c b/usr/src/lib/libdemangle/common/str.c index 014ce8a737..8608a17b5f 100644 --- a/usr/src/lib/libdemangle/common/str.c +++ b/usr/src/lib/libdemangle/common/str.c @@ -12,8 +12,6 @@ /* * Copyright 2017 Jason King */ -#include <sys/debug.h> -#include <sys/sysmacros.h> #include <string.h> #include "str.h" #include "demangle_int.h" diff --git a/usr/src/lib/libdemangle/common/strview.c b/usr/src/lib/libdemangle/common/strview.c index e4576ee17a..1653484172 100644 --- a/usr/src/lib/libdemangle/common/strview.c +++ b/usr/src/lib/libdemangle/common/strview.c @@ -10,10 +10,11 @@ */ /* - * Copyright 2019, Joyent, Inc. + * Copyright 2019 Joyent, Inc. */ #include <string.h> +#include <sys/types.h> #include <sys/debug.h> #include "strview.h" diff --git a/usr/src/lib/libdemangle/common/util.c b/usr/src/lib/libdemangle/common/util.c index 739c554826..17eefe82d7 100644 --- a/usr/src/lib/libdemangle/common/util.c +++ b/usr/src/lib/libdemangle/common/util.c @@ -14,7 +14,8 @@ * Copyright 2019, Joyent, Inc. */ -#include <sys/debug.h> +#include <errno.h> +#include <limits.h> #include <stdlib.h> #include <string.h> #include "demangle-sys.h" @@ -42,6 +43,25 @@ zalloc(sysdem_ops_t *ops, size_t len) return (p); } +void * +xcalloc(sysdem_ops_t *ops, size_t n, size_t elsize) +{ + uint64_t sz; + + if (mul_overflow(n, elsize, &sz)) { + errno = ENOMEM; + return (NULL); + } + +#ifndef _LP64 + if (sz > SIZE_MAX) { + errno = ENOMEM; + return (NULL); + } +#endif + + return (zalloc(ops, sz)); +} void xfree(sysdem_ops_t *ops, void *p, size_t len) { diff --git a/usr/src/man/man1m/arcstat.1m b/usr/src/man/man1m/arcstat.1m index f65095fe57..6fc18f250a 100644 --- a/usr/src/man/man1m/arcstat.1m +++ b/usr/src/man/man1m/arcstat.1m @@ -16,14 +16,12 @@ .SH NAME arcstat \- report ZFS ARC and L2ARC statistics .SH SYNOPSIS -.LP .nf -\fBarcstat\fR [\fB-hvxr\fR] [\fB-f field[,field]...\fR] [\fB-o file\fR] [\fB-s string\fR] +\fBarcstat\fR [\fB-hvxr\fR] [\fB-f field[,field]...\fR] [\fB-o file\fR] [\fB-s string\fR] [\fBinterval\fR [\fBcount\fR]] .fi .SH DESCRIPTION -.LP The \fBarcstat\fR utility print various ZFS ARC and L2ARC statistics in vmstat-like fashion. .sp @@ -36,7 +34,7 @@ The \fBarcstat\fR command reports the following information: .\" .sp -.ne 1 +.ne 1 .na \fBc \fR .ad @@ -326,6 +324,96 @@ Total L2ARC accesses per second .sp .ne 2 .na +\fBl2pref \fR +.ad +.RS 14n +L2ARC prefetch allocated size per second +.RE + +.sp +.ne 2 +.na +\fBl2pref% \fR +.ad +.RS 14n +L2ARC prefetch allocated size percentage +.RE + +.sp +.ne 2 +.na +\fBl2mfu \fR +.ad +.RS 14n +L2ARC MFU allocated size per second +.RE + +.sp +.ne 2 +.na +\fBl2mfu% \fR +.ad +.RS 14n +L2ARC MFU allocated size percentage +.RE + +.sp +.ne 2 +.na +\fBl2mru \fR +.ad +.RS 14n +L2ARC MRU allocated size per second +.RE + +.sp +.ne 2 +.na +\fBl2mru% \fR +.ad +.RS 14n +L2ARC MRU allocated size percentage +.RE + +.sp +.ne 2 +.na +\fBl2data \fR +.ad +.RS 14n +L2ARC data (buf content) allocated size per second +.RE + +.sp +.ne 2 +.na +\fBl2data% \fR +.ad +.RS 14n +L2ARC data (buf content) allocated size percentage +.RE + +.sp +.ne 2 +.na +\fBl2meta \fR +.ad +.RS 14n +L2ARC metadata (buf content) allocated size per second +.RE + +.sp +.ne 2 +.na +\fBl2meta% \fR +.ad +.RS 14n +L2ARC metadata (buf content) allocated size percentage +.RE + +.sp +.ne 2 +.na \fBl2size \fR .ad .RS 14n @@ -370,7 +458,6 @@ Actual (compressed) size of the L2ARC .\" .SH OPTIONS -.LP The following options are supported: .sp @@ -429,7 +516,6 @@ Show field headers and definitions .RE .SH OPERANDS -.LP The following operands are supported: .sp .ne 2 @@ -450,6 +536,5 @@ Specify the sampling interval in seconds. .RE .SH AUTHORS -.LP arcstat was originally written by Neelakanth Nadgir and supported only ZFS ARC statistics. Mike Harsch updated it to support L2ARC statistics. diff --git a/usr/src/pkg/manifests/system-test-zfstest.mf b/usr/src/pkg/manifests/system-test-zfstest.mf index 2280313dd8..686e5498d1 100644 --- a/usr/src/pkg/manifests/system-test-zfstest.mf +++ b/usr/src/pkg/manifests/system-test-zfstest.mf @@ -121,6 +121,7 @@ dir path=opt/zfs-tests/tests/functional/history dir path=opt/zfs-tests/tests/functional/holes dir path=opt/zfs-tests/tests/functional/inheritance dir path=opt/zfs-tests/tests/functional/inuse +dir path=opt/zfs-tests/tests/functional/l2arc dir path=opt/zfs-tests/tests/functional/large_dnode dir path=opt/zfs-tests/tests/functional/large_files dir path=opt/zfs-tests/tests/functional/largest_pool @@ -137,7 +138,6 @@ dir path=opt/zfs-tests/tests/functional/nestedfs dir path=opt/zfs-tests/tests/functional/no_space dir path=opt/zfs-tests/tests/functional/nopwrite dir path=opt/zfs-tests/tests/functional/online_offline -dir path=opt/zfs-tests/tests/functional/persist_l2arc dir path=opt/zfs-tests/tests/functional/pool_checkpoint dir path=opt/zfs-tests/tests/functional/pool_names dir path=opt/zfs-tests/tests/functional/poolversion @@ -2433,6 +2433,11 @@ file path=opt/zfs-tests/tests/functional/compression/compress_003_pos \ mode=0555 file path=opt/zfs-tests/tests/functional/compression/compress_004_pos \ mode=0555 +file path=opt/zfs-tests/tests/functional/compression/l2arc_compressed_arc \ + mode=0555 +file \ + path=opt/zfs-tests/tests/functional/compression/l2arc_compressed_arc_disabled \ + mode=0555 file path=opt/zfs-tests/tests/functional/compression/setup mode=0555 file path=opt/zfs-tests/tests/functional/ctime/cleanup mode=0555 file path=opt/zfs-tests/tests/functional/ctime/ctime_001_pos mode=0555 @@ -2591,6 +2596,19 @@ file path=opt/zfs-tests/tests/functional/inuse/inuse_007_pos mode=0555 file path=opt/zfs-tests/tests/functional/inuse/inuse_008_pos mode=0555 file path=opt/zfs-tests/tests/functional/inuse/inuse_009_pos mode=0555 file path=opt/zfs-tests/tests/functional/inuse/setup mode=0555 +file path=opt/zfs-tests/tests/functional/l2arc/cleanup mode=0555 +file path=opt/zfs-tests/tests/functional/l2arc/l2arc.cfg mode=0444 +file path=opt/zfs-tests/tests/functional/l2arc/l2arc_arcstats_pos mode=0555 +file path=opt/zfs-tests/tests/functional/l2arc/l2arc_mfuonly_pos mode=0555 +file path=opt/zfs-tests/tests/functional/l2arc/persist_l2arc_001_pos mode=0555 +file path=opt/zfs-tests/tests/functional/l2arc/persist_l2arc_002_pos mode=0555 +file path=opt/zfs-tests/tests/functional/l2arc/persist_l2arc_003_neg mode=0555 +file path=opt/zfs-tests/tests/functional/l2arc/persist_l2arc_004_pos mode=0555 +file path=opt/zfs-tests/tests/functional/l2arc/persist_l2arc_005_pos mode=0555 +file path=opt/zfs-tests/tests/functional/l2arc/persist_l2arc_006_pos mode=0555 +file path=opt/zfs-tests/tests/functional/l2arc/persist_l2arc_007_pos mode=0555 +file path=opt/zfs-tests/tests/functional/l2arc/persist_l2arc_008_pos mode=0555 +file path=opt/zfs-tests/tests/functional/l2arc/setup mode=0555 file path=opt/zfs-tests/tests/functional/large_dnode/cleanup mode=0555 file path=opt/zfs-tests/tests/functional/large_dnode/large_dnode_001_pos \ mode=0555 @@ -2709,26 +2727,6 @@ file path=opt/zfs-tests/tests/functional/online_offline/online_offline_002_neg \ file path=opt/zfs-tests/tests/functional/online_offline/online_offline_003_neg \ mode=0555 file path=opt/zfs-tests/tests/functional/online_offline/setup mode=0555 -file path=opt/zfs-tests/tests/functional/persist_l2arc/cleanup mode=0555 -file path=opt/zfs-tests/tests/functional/persist_l2arc/persist_l2arc.cfg \ - mode=0444 -file path=opt/zfs-tests/tests/functional/persist_l2arc/persist_l2arc_001_pos \ - mode=0555 -file path=opt/zfs-tests/tests/functional/persist_l2arc/persist_l2arc_002_pos \ - mode=0555 -file path=opt/zfs-tests/tests/functional/persist_l2arc/persist_l2arc_003_neg \ - mode=0555 -file path=opt/zfs-tests/tests/functional/persist_l2arc/persist_l2arc_004_pos \ - mode=0555 -file path=opt/zfs-tests/tests/functional/persist_l2arc/persist_l2arc_005_pos \ - mode=0555 -file path=opt/zfs-tests/tests/functional/persist_l2arc/persist_l2arc_006_pos \ - mode=0555 -file path=opt/zfs-tests/tests/functional/persist_l2arc/persist_l2arc_007_pos \ - mode=0555 -file path=opt/zfs-tests/tests/functional/persist_l2arc/persist_l2arc_008_pos \ - mode=0555 -file path=opt/zfs-tests/tests/functional/persist_l2arc/setup mode=0555 file \ path=opt/zfs-tests/tests/functional/pool_checkpoint/checkpoint_after_rewind \ mode=0555 diff --git a/usr/src/test/util-tests/tests/demangle/rust.c b/usr/src/test/util-tests/tests/demangle/rust.c index db2fae28e4..051bf2456b 100644 --- a/usr/src/test/util-tests/tests/demangle/rust.c +++ b/usr/src/test/util-tests/tests/demangle/rust.c @@ -26,7 +26,7 @@ * DEALINGS IN THE SOFTWARE. */ /* - * Copyright 2019, Joyent, Inc. + * Copyright 2019 Joyent, Inc. * Copyright 2021 Jason King */ @@ -34,12 +34,16 @@ * Test cases taken from rustc-demangle 0.1.9 */ #include <errno.h> +#include <err.h> +#include <locale.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/sysmacros.h> #include <demangle-sys.h> +#define TEST_LOCALE "C.UTF-8" + typedef struct rust_test_case { const char *mangled; const char *demangled; @@ -51,6 +55,7 @@ typedef struct rust_test_grp { const char *name; rust_test_case_t tests[]; } rust_test_grp_t; + #define GROUP(_n, ...) \ static rust_test_grp_t _n = { \ .name = #_n, \ @@ -60,6 +65,8 @@ typedef struct rust_test_grp { } \ } +/* BEGIN CSTYLED */ + GROUP(demangle, T_ERR("test"), T("_ZN4testE", "test"), @@ -77,7 +84,6 @@ GROUP(demangle_many_dollars, T("_ZN13test$u20$test4foobE", "test test::foob"), T("_ZN12test$BP$test4foobE", "test*test::foob")); -/* BEGIN CSTYLED */ GROUP(demangle_osx, T("__ZN5alloc9allocator6Layout9for_value17h02a996811f781011E", "alloc::allocator::Layout::for_value::h02a996811f781011"), @@ -104,6 +110,143 @@ GROUP(handle_assoc_types, /* C++ mangled names that aren't valid rust names */ GROUP(cplusplus_as_rust, T_ERR("_ZN7mozilla3dom13BrowserParent22RecvUpdateContentCacheERKNS_12ContentCacheE")); +GROUP(v0_crate_with_leading_digit, + T("_RNvC6_123foo3bar", "123foo::bar")); + +GROUP(v0_utf8_idents, + T("_RNqCs4fqI2P2rA04_11utf8_identsu30____7hkackfecea1cbdathfdh9hlq6y", + "utf8_idents::საჭმელად_გემრიელი_სადილი")); + +GROUP(v0_closure, + T("_RNCNCNgCs6DXkGYLi8lr_2cc5spawn00B5_", + "cc::spawn::{closure#0}::{closure#0}"), + T("_RNCINkXs25_NgCsbmNqQUJIY6D_4core5sliceINyB9_4IterhENuNgNoBb_4iter8iterator8Iterator9rpositionNCNgNpB9_6memchr7memrchrs_0E0Bb_", + "<core::slice::Iter<u8> as core::iter::iterator::Iterator>::rposition::<core::slice::memchr::memrchr::{closure#1}>::{closure#0}")); + +GROUP(v0_dyn_trait, + T("_RINbNbCskIICzLVDPPb_5alloc5alloc8box_freeDINbNiB4_5boxed5FnBoxuEp6OutputuEL_ECs1iopQbuBiw2_3std", + "alloc::alloc::box_free::<dyn alloc::boxed::FnBox<(), Output = ()>>")); + +GROUP(v0_const_generics, + T("_RMC0INtC8arrayvec8ArrayVechKj7b_E", "<arrayvec::ArrayVec<u8, 123>>"), + T("_RMCs4fqI2P2rA04_13const_genericINtB0_8UnsignedKhb_E", "<const_generic::Unsigned<11>>"), + T("_RMCs4fqI2P2rA04_13const_genericINtB0_6SignedKs98_E", "<const_generic::Signed<152>>"), + T("_RMCs4fqI2P2rA04_13const_genericINtB0_6SignedKanb_E", "<const_generic::Signed<-11>>"), + T("_RMCs4fqI2P2rA04_13const_genericINtB0_4BoolKb0_E", "<const_generic::Bool<false>>"), + T("_RMCs4fqI2P2rA04_13const_genericINtB0_4BoolKb1_E", "<const_generic::Bool<true>>"), + T("_RMCs4fqI2P2rA04_13const_genericINtB0_4CharKc76_E", "<const_generic::Char<'v'>>"), + T("_RMCs4fqI2P2rA04_13const_genericINtB0_4CharKca_E", "<const_generic::Char<'\\n'>>"), + T("_RMCs4fqI2P2rA04_13const_genericINtB0_4CharKc2202_E", "<const_generic::Char<'∂'>>")); + +GROUP(v0_exponential_explosion, + T("_RMC0TTTTTTpB8_EB7_EB6_EB5_EB4_EB3_E", + "<((((((_, _), (_, _)), ((_, _), (_, _))), (((_, _), (_, _)), ((_, _), (_, _)))), " + "((((_, _), (_, _)), ((_, _), (_, _))), (((_, _), (_, _)), ((_, _), (_, _))))), " + "(((((_, _), (_, _)), ((_, _), (_, _))), (((_, _), (_, _)), ((_, _), (_, _)))), " + "((((_, _), (_, _)), ((_, _), (_, _))), (((_, _), (_, _)), ((_, _), (_, _))))))>")); + +GROUP(v0_thinlto, + T("_RC3foo.llvm.9D1C9369", "foo"), + T("_RC3foo.llvm.9D1C9369@@16", "foo"), + T("_RNvC9backtrace3foo.llvm.A5310EB9", "backtrace::foo")); + +GROUP(v0_demangle_extra_suffix, + T("_RNvNtNtNtNtCs92dm3009vxr_4rand4rngs7adapter9reseeding4fork23FORK_HANDLER_REGISTERED.0.0", + "rand::rngs::adapter::reseeding::fork::FORK_HANDLER_REGISTERED.0.0")); + +/* + * From Rust RFC2603 + */ +GROUP(v0_generic_func, + T("_RINvNtC3std3mem8align_ofdE", "std::mem::align_of::<f64>"), + T("_RINvNtC3std3mem8align_ofNtNtC3std3mem12DiscriminantE", + "std::mem::align_of::<std::mem::Discriminant>"), + T("_RINvNtC3std3mem8align_ofQTReuEE", + "std::mem::align_of::<&mut (&str, ())>")); + +GROUP(v0_eddyb, + T("_RNvXsa_NtNtCs7hxHya3g3Sg_4core3ptr6uniqueINtB5_6UniqueNtNtNtCshRVCqTKO4VO_5cargo4util4toml10TomlTargetEINtNtB9_7convert4FromINtNtB7_8non_null7NonNullBQ_EE4fromBW_", + "<core::ptr::unique::Unique<cargo::util::toml::TomlTarget> as core::convert::From<core::ptr::non_null::NonNull<cargo::util::toml::TomlTarget>>>::from"), + T("_RNvXsG_NtNtCs2ZCqZGLqlfc_3std3ffi6os_strNtB5_5OsStrINtNtCs7hxHya3g3Sg_4core7convert5AsRefBC_E6as_ref", + "<std::ffi::os_str::OsStr as core::convert::AsRef<std::ffi::os_str::OsStr>>::as_ref"), + T("_RNvMs_NtCs7hxHya3g3Sg_4core6resultINtB4_6ResultNtNtB6_5alloc6LayoutNtBL_9LayoutErrE6unwrapCsdJWFNQ9j01_12aho_corasick", + "<core::result::Result<core::alloc::Layout, core::alloc::LayoutErr>>::unwrap"), + T("_RINvNtCs7hxHya3g3Sg_4core3mem7size_ofFUKCEPaECs2ZCqZGLqlfc_3std", + "core::mem::size_of::<unsafe extern \"C\" fn() -> *const i8>"), + T("_RINvCsc1o8JKpgQAm_4test28___rust_begin_short_backtraceFEuEB2_", + "test::__rust_begin_short_backtrace::<fn()>"), + T("_ZN4core5array104_$LT$impl$u20$core..iter..traits..collect..IntoIterator$u20$for$u20$$RF$$u5b$$RF$str$u3b$$u20$_$u5d$$GT$9into_iter17hc066f1a15f41761dE", + "core::array::<impl core::iter::traits::collect::IntoIterator for &[&str; _]>::into_iter::hc066f1a15f41761d")); + +GROUP(v0_afl_fast, + T_ERR("_RMC0TTTTTTPB8_yB7_EB6_EB5_EB4_EB3_E"), + T_ERR("_RMC0TTTTTTpB8_yB7_eB6_EB5_EB4_EB3_E"), + T_ERR("_RMC0TTTTTTpB4_yB7_EB6_EB5_EB4_EB3_E"), + T_ERR("_RMC0TTTTTTpB4_yB7_EB6_EB5_EB4_EB3_E"), + T_ERR("_RMC0TTTTTTTB8_yB7_EB6_EB5_EB4_EB3_E"), + T_ERR("_RMC0TTTTTTSB8_yB7_EB6_EB5_EB4_EB3_E"), + T_ERR("_RMC0TTTTTTRB8_yB7_EB6_EB5_EB4_EB3_E"), + T_ERR("_RMC0TTTTTTQB8_yB7_EB6_EB5_EB4_EB3_E"), + T_ERR("_RMC0TTTTTTOB8_yB7_EB6_EB5_EB4_EB3_E"), + T_ERR("_RMC0TTTTTTpB8_yB7_hB6_EB5_EB4_EB3_E"), + T_ERR("_RMC0TTTTTTpB8_yB7_llvmEB5_EB4_EB3_E"), + T_ERR("_RMC0TTTTTTpB1_yB7_eB6_EB5_EB4_EB3_E"), + T_ERR("_RMC0TTTTTTpB1_tB7_fB6_EB5_EB4_EB3_E"), + T_ERR("_RMC0TTTC0TTTTTPpB0_SB7_llvmTPpB8_SB7_EB6_EB5_EB4_EB3_E"), + T_ERR("_RMC3TTTTTtpB_yB7_EB6_EB5_EB4_EB3_E"), + T_ERR("_RMC0TTTTRLpB8_llvB_vmEB_EB5FEB4EB5FEB4_EB3_E"), + T_ERR("_RMC0TTTTQLp.B_llvmEB6_EB5_EB4_EB3_E"), + T_ERR("_RMC0TRMC0TTTTQLp.B_YBTTTQLp.B_YB7_EBd_EB5_EB4_EB3_E"), + T_ERR("_RMC0TTTTQLp.B_llvmEB6_EB5_EB4_E!3_E"), + T_ERR("_RMC0TRMC0TTTTQLp.B_bB7_EB6_EB5_EB4_E"), + T_ERR("_RMC0TTTTRLp.B_llvmEB6_EB5_EB4_EB3_E"), + T_ERR("_RMC0TTTTQLpC0TTTfQLp.B_B_EB84_EB3_E"), + T_ERR("_RMC0TTTTQLp.TfQLp.B_jC0TTTfQLp.B_llvT_EB3_E"), + T_ERR("_RMC0TTTTQLpB8_TTTTTQLp_B_llvmEB6_E3_E"), + T_ERR("_RIC0TRLpB8B8_B8_llvmEB6_EB5_llvmEB6_EB5_EB4_EL3_E"), + T_ERR("_RNCINkXs25NNNNNNNNNNNNNNNNNNNNNNNN_INyB9_4IterhENuNgNoBb_4iter8iteraionNCN1_6hr7m0E0Bb_"), + T_ERR("_RNCXNkXs25_NgCsbmNqQUJIY6D_4core5sliceINyB4_4IterhENuNgNoBb_4iter8iterator8Iterator9rpositionNCNgNpB2_6hr7m0E0Bb_"), + T_ERR("_RNCXNkXs25_NgCsbmNqQUJIY6D_4core5sliceINyB9_4IterhENuNgNoBZ_4iter8iterator8Iterator9rpositionNCNgNpB2_6hr7m0E0Bb_"), + T_ERR("_RYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYyYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYyYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYNfYB_YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYNfYB_"), + T_ERR("_RNCXNkXs25_NSCsbmNqQUJIY6D_4core5sliceINyB2_4IterhENuNgNoBb_4iter8iterator8Iterator9rpositionNCNgNpB2_6D_4core5sliReINyB1_4IterhENu6D_4core5sliceINyB1_4IterhENuNgNoBb_4iter8iterator8Iterator9rpositionNCNgNpBNgNoBb_4iter8iterr9rpo25_NgCsbmNqQUJIY6D_4core5sliceIN4IterhENuNgNoBb_4iter8iterator8Iterator9rpositionNCNgNpB2_6NqQUJIY6D_4core5sliReINyB1_4IterhENu6D_4core5sliceINyB1_4IterhENuNgNoBb_4iter8iterator8Iterator9rpositionNCNgNpBNgNoBb_4iter8iterr9rpositionNCNgNpB2_6NqQUJIY6B2_6hr7m0E7m0EsitionNCNgNpB2_6NqQUJIY6B2_6hr7m0E7m0E0Bb_"), + T_ERR("_RNCXNkXs25_NCCsbmNqQUJIY6D_4core5sliceINyB2_4IterhENuNgNoBb_4iter8iterator8Iterator9rpositionNCNgNpB2_6D_4core5sliReINyB1_4IterhENu6D_4core5sliceINyB1_4IterhENuNgNoBb_4iter8iterator8Iterator9rpositionNCNgNpBNgNoBb_4iter8iterr9rpo25_NgCsbmNqQUJIY6D_4core5sliceIN4IterhENuNgNoBb_4iter8iterator8Iterator9rpositionNCNgNpB2_6NqQUJIY6D_4core5sliReINyB1_4IterhENu6D_4core5sliceINyB1_4IterhENuNgNoBb_4iter8iterator8Iterator9rpositionNCNgNpBNgNoBb_4iter8iterr9rpositionNCNgNpB2_6NqQUJIY6B2_6hr7m0E7m0EsitionNCNgNpB2_6NqQUJIY6B2_6hr7m0E7m0E0Bb_"), + T_ERR("_RMC0TTTTRL_B4_llvmEB6_EB5_EB4_EB3_E"), + T_ERR("_RMC0TTTTRRMC0TB7_llvmEB6_EB5_EB4_EB3_EL_B7_llvmEB6_EB5_EB4_EB3_E"), + T_ERR("_RIC0TTTTQIC0L_B7_llvmEB6_E75_EB4_EB3_E"), + T_ERR("_RNCINkXs25_NSCsbmNqQUJIY6D_4core53liceINyBK_4IterhDNCINkXs25_NSCsbmD_4core5sRNCINkXs25_NSCsbmNqQUJIY6D_4core5sliceINyB9_4IterhDNCINkXs25_NSCsbmJIY6D_4core5sliceINyB9_4IterhENuNgNoBN_4iter8iterator8Iterato29rposillvmtionNCXs25_NSCsbUJIY6D_4core5sliceINyB9_4IterhDNuNgNCINkXs25_NSCsbmJIY6D_4core5sliceINyB9_4IterhDNuNgNoBN_4iter8iterator8IliceINyB1_4IterhENuNgNoBN_4iter8iterator8Iterator9rposillvmtionNCXs25_NSCsbUJIY6D_4core5sliceINyB9_4IterhDNuNgNCINkXs25_NSCsbmJIY6D_4core5sliceINyB9_4IterhDNuNgNoBN_4iter8iterator8Iter9rposillvmtionNCNgNpB1_Bb_"), + T_ERR("_RNCINkXs25_NSCsbmNqQUJIY6D_4core93liceINyBK_4IterhDNCINkXs25_NSCsbmD_4core5sRNCINkXs25_NSCsbmNqQUJIY6D_4core5sliceINyB9_4IterhDNCINkXs25_NSCsbmJIY6D_4core5sliceINyB9_4IterhENuNgNoBN_4iter8iterator8Iterato29rposillvmtionNCXs25_NSCsbUJIY6D_4core5sliceINyB9_4IterhDNuNgNCINkXs25_NSCsbmJIY6D_4core5sliceINyB9_4IterhDNuNgNoBN_4iter8iterator8IliceINyB1_4IterhENuNgNoBN_4iter8iterator8Iterator9rposillvmtionNCXs25_NSCsbUJIY6D_4core5sliceINyB9_4IterhDNuNgNCINkXs25_NSCsbmJIY6D_4core5sliceINyB9_4IterhDNuNgNoBN_4iter8iterator8Iter9rposillvmtionNCNgNpB1_Bb_"), + T_ERR("_RNCINkXs25_NSCsbmNqQUJIY6D_4core5sliceINyB9_4IterhDNCINkXs25_NSCsbmD_4core5sRNCINkXs25_NSCsbmNqQUJIY6D_4core5sliceINyB9_4IterhDNCINkXs25_NSCsbmJIY6D_4core5sliceINyB9_4IterhENuNgNoBN_4iter8iterator8Iterato29rposillvmtiB_NCXs25_NSCsbUJIY6D_4core5sliceINyB9_4IterhDNuNgNCINkXs25_NSCsbmJIY6D_4core5sliceINyB9_4IterhDNuNgNoBN_4iter8iterator8IliceINyB1_4IterhENuNgNoBN_4iter8iterator8Iterator9rposillvmtionNCXs25_NSCsbUJIY6D_4core5sliceINyB9_4IterhDNuNgNCINkXs25_NSCsbmJIY6D_4core5sliceINyB9_4IterhDNuNgNoBN_4iter8iterator8Iter9rposillvmtionNCNgNpB1_Bb_"), + T_ERR("_RNCINkXs25_NSCsbmNqQUJIY6D_4coreu425_NSNgNoBN_4iter8iteratotliceINyB9_4IterhDNCINkXs25_NSCsbmD_4core5sRNCINkXs25_NSCsbeNqQUJIY6D_4core5sliceINyB9_4IterhLNCINkXs25_NSCsbmJIY6D_4core5sliceINyB9_48888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888eeeeeeeeeeeeeee88888888888888888888888888888888888888G88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888NSCsbmJIY6D_4core5sliceINyB9_4IterhDNuNgNoBN_4iter8iteravmtionNCNgNpB1_Bb_"), + T_ERR("_ZN9EB0_EB3_E"), + T_ERR("_ZN9INTTB7_$B6_SB5_E"), + T_ERR("_ZN9INTTB7 E.6_SBEEEEEEEEEEEEEEEEB7_EB6_EB5_EB0EB6_EB5_EB0_EB3_EEB0_EB3_E"), + T_ERR("_ZN9I3TTB7_$B8_C0TTT9I3TTB7_$B8_$$5$B_E"), + T_ERR("_ZN9$C$TB7_$B8_C0TTT9I3TB7_$B8_$$5$B_E"), + T_ERR("_ZN9......=E"), + T_ERR("_RMC0TTTTQLpfQNp.B_aaaaaTOTfQL_aaaaaB_"), + T_ERR("_RMC0TTTTRLpB8_lRMC0B_aaB5_EB4_B5_EEB3_E"), + T_ERR("_RMC0TTTTRLp_aalRMC0B_aaB5_EB4_B5_EEB3_E"), + T_ERR("_RMC0TTTTRLp_C0TaalRMC0B6_EB_aaB4_B5_EEB3_E"), + T_ERR("_RMC0TTTTRL0_aalRMC0B_aaB5_EB4_B5_EEB3_E"), + T_ERR("_RMC6aEB8_XB4_YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYAly_IYB_lYYYYYYYAly_HYB_"), + T_ERR("_RMC6aEB8_XB4_YYYYYYYYYYYYYYYYYYYYYYYMC6aEB8ZXB4_YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYAlypHYYYYYYYYYYYYYYYYYYAlyNHYB_"), + T_ERR("_RMC6aEB8_NB4_YYNYYYNYYYYYYYYYYYxYYYYYYYRAC6aEB8_NBV_YYNYYYNYYYYYYYYYYYxYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYlYNHYB_YYY"), + T_ERR("_ZN9A7$TB7_$B8_$B$TT9I3TB7m$__ZN98C$T_aa$B8_$C$TT9I3_ZN9$C$TB7_$B8_$B$TT9I3TB7m$__ZN9$C$TB_$BZN9A7$TB7_$B8_$B$TT6$C$$B$$B$$__ZN98C$T_aa$B8_$K$TT9I7_ZN9$C$TB7_$B8T_aa$B8_$C$TT9I3_ZN9$C$TB7_$BP$B$TT9I3TB7m$__ZN9$ $TB_$B8_$A$TT9I3TB7m$8_$A$TT9I3TB7m$__ZN2UE"), + T_ERR("_ZN9A7$TB7_$B8_$B$TT9I3TB7m$__ZN98C$T_aa$B8_$C$TT9I3_ZN9$C$TB7_$B8_$B$TT9I3TB7m$__ZN9$C$TB_$BZN9A7$TB7_$B8_$B$TT6$C$$B$$B$$__ZN98C$T_aa$B8_$K$TT9I7_ZN9$C$TB7_$B8T_aa$B8_$C$TT9I3_ZN9$C$TB7_$SP$B$TT9I3TB7m$__ZN9$ $TB_$B8_$A$TT9I3TB7m$8_$A$TT9I3TB7m$__ZN2UE"), + T_ERR("__ZN9?@EEEEEJE"), + T_ERR("_RMC0TTTATjpB8_EB7_TB_aaB5_EB4_EB3_E"), + T_ERR("_ZN949$TE7_llv4C$TE$C$7_llvm$C$TT9'3TB_$__................................................................................................................................................................................................................................................................................................................................................................................................$B$.E..........................................:.........................................................................................................................................P...............................................@..................................................................................................................................................................................................................................................................TTB7_E.6_SB_E.6_S65__ZQCI<_EB=E"), + T_ERR("_RMC6aEB8_XB4_YYYYYYYYYYYYYYYYYYYYYYYYNSCsbmJIY6D_4core0MC6aEB8_XB4_YYYYYYYYYYYYYYYYYYsliceINyB9_rhDNuNgNoBN_4iter8iteravmt}ore5sliceINyB9_4IterhDNuNgNCINOXs25_NSCsbmJIY6D_4core5sliceI_yB9_4IterhDNuNgNoB__4llvmwionNB9_4INkXs25_NSCrhDYYYYYYYYYYYYYYYYYYYYYYYYYYYaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)))))_aa)))))))))))))))))))))"), + T_ERR("_ZN9A7$TB7_$B8_$B$TT9I3TB7m$__ZN98C$T_aa$B8_$C$TT9I3_ZN9$C$TB7_$B8_$B$TT9I3TB7m$__ZN9$C$TB_$BZN9A7$TB7_$B8_$B$TT6$C$$B$$B$$__ZN98C$T_aa$B8_$K$TT9I7_ZN9$C$TB7_$B8T_aa$B8_$C$TT9I3_ZN9$C$TB7_$LP$B$TT9I3TB7m$__ZN9$ $TB_$B8_$A$TT9I3TB7m$8_$A$TT9I3TB7m$__ZN2UE"), + T_ERR("_ZN9A7$TB7_$B8_$B$TT9I3TB7m$__ZN98C$T_aa$B8_$C$TT9I3_ZN9$C$TB7_$B8_$B$TT9I3TB7m$__ZN9$C$TB_$BZN9A7$TB7_$B8_$B$TT6$C$$B$$B$$__ZN98C$T_aa$B8_$K$TT9I7_ZN9$C$TB7_$B8T_aa$B8_$C$TT9I3_ZN9$C$TB7_$LT$B$TT9I3TB7m$__ZN9$ $TB_$B8_$A$TT9I3TB7m$8_$A$TT9I3TB7m$__ZN2UE"), + T_ERR("_ZN9A7$TB7_$B8_$B$TT9I3TB7m$__ZN98C$T_aa$B8_$C$TT9I3_ZN9$C$TB7_$B8_$B$TT9I3TB7m$__ZN9$C$TB_$BZN9A7$TB7_$B8_$B$TT6$C$$B$$B$$__ZN98C$T_aa$B8_$K$TT9I7_ZN9$C$TB7_$B8T_aa$B8_$C$TT9I3_ZN9$C$TB7_$GT$B$TT9I3TB7m$__ZN9$ $TB_$B8_$A$TT9I3TB7m$8_$A$TT9I3TB7m$__ZN2UE"), + T_ERR("_ZN9A7$TB7_$B8_$B$TT9I3TB7m$__ZN98C$T_aa$B8_$C$TT9I3_ZN9$C$TB7_$B8_$B$TT9I3TB7m$__ZN9$C$TB_$BZN9A7$TB7_$B8_$B$TT6$C$$B$$B$$__ZN98C$T_aa$B8_$K$TT9I7_ZN9$C$TB7_$B8T_aa$B8_$C$TT9I3_ZN9$C$TB7_$RP$B$TT9I3TB7m$__ZN9$ $TB_$B8_$A$TT9I3TB7m$8_$A$TT9I3TB7m$__ZN2UE"), + T_ERR("_ZN9A7$TB7_$B8_$B$TT9I3TB7m$__ZN98C$T_aa$B8_$C$TT9I3_ZN9$C$TB7_$B8_$B$TT9I3TB7m$__ZN9$C$TB_$BZN9A7$TB7_$B8_$B$TT6$C$$B$$B$$__ZN98C$T_aa$B8_$K$TT9I7_ZN9$C$TB7_$B8T_aa$B8_$C$TT9I3_ZN9$C$TB7_$RF$B$TT9I3TB7m$__ZN9$ $TB_$B8_$A$TT9I3TB7m$8_$A$TT9I3TB7m$__ZN2UE"), + T_ERR("_RNCXNkXs25_NgCsbmNqQUJIY6D_4core5sliceINyBK_4IterhENuNGNoBb_4iter8iterator8Iterator9rpositionNCNgNpB2_6D_4core5sliReINyB1_4IterhENu6D_4core5sliceINyB1_4IterhENuNgNoBb_4iter8iterator8Iterator9rpositionNCNgNpBNgNoBb_4iter8iterr9rpo25_NgCsbmNqQUJIY6D_4core5sliceIN4IterhENuNgNoBb_4iter8iterator8Iterator9rpositionNCNgNpB2_6NqQUJIY6D_4core5sliReINyB1_4IterhENu6D_4core5sliceINyB1_4IterhENuNgNoBb_4iter8iterator8Iterator9rpositionNCNgNpBNgNoBb_4iter8iterr9rpositionNCNgNpB2_6NqQUJIY6B2_6hr7m0E7m0EsitionNCNgNpB2_6NqQUJIY6B2_6hr7m0E7m0E0Bb_"), + T_ERR("_RIC6kIIIIIB4_lB5_EB4NEB3_A"), + T_ERR("_ZN9I3TTB7_$B8_$B$TT9I398C$T$B8_$B$TT9I398C$T_aa$B8_$C$TT9I3_ZN9$C$TB7_$B8_$B$TB$$B$$__ZN98C$T_aa$B8_$K$TT9I7_ZN9$C$TB7_$B8T_aa$B8_$C$TT9ITB7$LT$B$TT9I3TB7m$__ZB8T_aa$B8K$C$TT9I3_ZN9$C$TB7_$LT$B$TT9I3TB7m$__ZN9$ $TB7m$__ZN98C$T_aa$B8_$C$TT9I3_ZN:$C$TB7_$B8_$B$TT9I3TB7m$__ZN$RF$9$C$TB_$BZN9A7$TB7_8B8_$B$TT6$C$$B$%B$$__ZN98C$T_aa$B8_$K$TT9I7_ZN9$C$TB7_$B8T_aa$B8_$C$TT9I3_ZN9$C$TB7_$LT$B$TT9I3TB7m$__ZN9$ UE"), + T_ERR("_RIC6aOB_aaB4_RIC6aOB_aaB8_gB._NaEB5_gB8_gB4_NaEB5_))))))))))))))))))))))da)))))))C6aEB8_XB4_DC6aXB4_DC6aEJ8_gB_NaEB5_gB8_gB4_NaEB5_))))))))))))))))))))))_a))))))))sitUonNCNgNpB1_6hr7m0E0Bb_)))sitionNCNgNpB1_6hr7m0E0Bb_")); + /* END CSTYLED */ static rust_test_grp_t *rust_tests[] = { @@ -116,8 +259,18 @@ static rust_test_grp_t *rust_tests[] = { &invalid_no_chop, &handle_assoc_types, &cplusplus_as_rust, + &v0_crate_with_leading_digit, + &v0_utf8_idents, + &v0_closure, + &v0_dyn_trait, + &v0_const_generics, + &v0_exponential_explosion, + &v0_thinlto, + &v0_demangle_extra_suffix, + &v0_generic_func, + &v0_eddyb, + &v0_afl_fast, }; - static const size_t n_rust_tests = ARRAY_SIZE(rust_tests); static boolean_t @@ -195,8 +348,13 @@ run_test(rust_test_grp_t *test) int main(int argc, char **argv) { + const char *l; boolean_t ok = B_TRUE; + l = setlocale(LC_CTYPE, TEST_LOCALE); + if (l == NULL || strcmp(l, TEST_LOCALE) != 0) + errx(EXIT_FAILURE, "failed to set locale to %s", TEST_LOCALE); + for (size_t i = 0; i < n_rust_tests; i++) ok &= run_test(rust_tests[i]); diff --git a/usr/src/test/zfs-tests/include/libtest.shlib b/usr/src/test/zfs-tests/include/libtest.shlib index 2806f31027..4bcfb60efc 100644 --- a/usr/src/test/zfs-tests/include/libtest.shlib +++ b/usr/src/test/zfs-tests/include/libtest.shlib @@ -2889,6 +2889,45 @@ function get_tunable_impl } # +# Wait for the specified arcstat to reach non-zero quiescence. +# If echo is 1 echo the value after reaching quiescence, otherwise +# if echo is 0 print the arcstat we are waiting on. +# +function arcstat_quiescence # stat echo +{ + typeset stat=$1 + typeset echo=$2 + typeset do_once=true + + if [[ $echo -eq 0 ]]; then + echo "Waiting for arcstat $1 quiescence." + fi + + while $do_once || [ $stat1 -ne $stat2 ] || [ $stat2 -eq 0 ]; do + typeset stat1=$(get_arcstat $stat) + sleep 2 + typeset stat2=$(get_arcstat $stat) + do_once=false + done + + if [[ $echo -eq 1 ]]; then + echo $stat2 + fi +} + +function arcstat_quiescence_noecho # stat +{ + typeset stat=$1 + arcstat_quiescence $stat 0 +} + +function arcstat_quiescence_echo # stat +{ + typeset stat=$1 + arcstat_quiescence $stat 1 +} + +# # Compute SHA256 digest for given file or stdin if no file given. # Note: file path must not contain spaces # diff --git a/usr/src/test/zfs-tests/runfiles/omnios.run b/usr/src/test/zfs-tests/runfiles/omnios.run index c691593fba..a5e2099739 100644 --- a/usr/src/test/zfs-tests/runfiles/omnios.run +++ b/usr/src/test/zfs-tests/runfiles/omnios.run @@ -445,7 +445,7 @@ user = [/opt/zfs-tests/tests/functional/compression] tests = ['compress_001_pos', 'compress_002_pos', 'compress_003_pos', - 'compress_004_pos'] + 'compress_004_pos', 'l2arc_compressed_arc', 'l2arc_compressed_arc_disabled'] [/opt/zfs-tests/tests/functional/ctime] tests = ['ctime_001_pos' ] @@ -703,12 +703,6 @@ tests = [ 'userspace_001_pos', 'userspace_002_pos', 'userspace_003_pos', 'groupspace_001_pos', 'groupspace_002_pos', 'groupspace_003_pos' ] -[/opt/zfs-tests/tests/functional/persist_l2arc] -tests = ['persist_l2arc_001_pos', 'persist_l2arc_002_pos', - 'persist_l2arc_003_neg', 'persist_l2arc_004_pos', 'persist_l2arc_005_pos', - 'persist_l2arc_006_pos', 'persist_l2arc_007_pos', 'persist_l2arc_008_pos'] -tags = ['functional', 'persist_l2arc'] - [/opt/zfs-tests/tests/functional/utils_test] tests = ['utils_test_001_pos', 'utils_test_002_pos', 'utils_test_003_pos', 'utils_test_004_pos', 'utils_test_005_pos', 'utils_test_006_pos', @@ -755,3 +749,11 @@ tests = ['log_spacemap_import_logs'] pre = post = tags = ['functional', 'log_spacemap'] + + +[/opt/zfs-tests/tests/functional/l2arc] +tests = ['l2arc_arcstats_pos', 'l2arc_mfuonly_pos', + 'persist_l2arc_001_pos', 'persist_l2arc_002_pos', + 'persist_l2arc_003_neg', 'persist_l2arc_004_pos', 'persist_l2arc_005_pos', + 'persist_l2arc_006_pos', 'persist_l2arc_007_pos', 'persist_l2arc_008_pos'] +tags = ['functional', 'l2arc'] diff --git a/usr/src/test/zfs-tests/runfiles/openindiana.run b/usr/src/test/zfs-tests/runfiles/openindiana.run index dfe5b337a4..a5ce7d8bc4 100644 --- a/usr/src/test/zfs-tests/runfiles/openindiana.run +++ b/usr/src/test/zfs-tests/runfiles/openindiana.run @@ -445,7 +445,7 @@ user = [/opt/zfs-tests/tests/functional/compression] tests = ['compress_001_pos', 'compress_002_pos', 'compress_003_pos', - 'compress_004_pos'] + 'compress_004_pos', 'l2arc_compressed_arc', 'l2arc_compressed_arc_disabled'] [/opt/zfs-tests/tests/functional/ctime] tests = ['ctime_001_pos' ] @@ -703,12 +703,6 @@ tests = [ 'userspace_001_pos', 'userspace_002_pos', 'userspace_003_pos', 'groupspace_001_pos', 'groupspace_002_pos', 'groupspace_003_pos' ] -[/opt/zfs-tests/tests/functional/persist_l2arc] -tests = ['persist_l2arc_001_pos', 'persist_l2arc_002_pos', - 'persist_l2arc_003_neg', 'persist_l2arc_004_pos', 'persist_l2arc_005_pos', - 'persist_l2arc_006_pos', 'persist_l2arc_007_pos', 'persist_l2arc_008_pos'] -tags = ['functional', 'persist_l2arc'] - [/opt/zfs-tests/tests/functional/utils_test] tests = ['utils_test_001_pos', 'utils_test_002_pos', 'utils_test_003_pos', 'utils_test_004_pos', 'utils_test_005_pos', 'utils_test_006_pos', @@ -755,3 +749,10 @@ tests = ['log_spacemap_import_logs'] pre = post = tags = ['functional', 'log_spacemap'] + +[/opt/zfs-tests/tests/functional/l2arc] +tests = ['l2arc_arcstats_pos', 'l2arc_mfuonly_pos', + 'persist_l2arc_001_pos', 'persist_l2arc_002_pos', + 'persist_l2arc_003_neg', 'persist_l2arc_004_pos', 'persist_l2arc_005_pos', + 'persist_l2arc_006_pos', 'persist_l2arc_007_pos', 'persist_l2arc_008_pos'] +tags = ['functional', 'l2arc'] diff --git a/usr/src/test/zfs-tests/runfiles/smartos.run b/usr/src/test/zfs-tests/runfiles/smartos.run index f3355c90c9..a633ec274e 100644 --- a/usr/src/test/zfs-tests/runfiles/smartos.run +++ b/usr/src/test/zfs-tests/runfiles/smartos.run @@ -394,7 +394,7 @@ user = [/opt/zfs-tests/tests/functional/compression] tests = ['compress_001_pos', 'compress_002_pos', 'compress_003_pos', - 'compress_004_pos'] + 'compress_004_pos', 'l2arc_compressed_arc', 'l2arc_compressed_arc_disabled'] [/opt/zfs-tests/tests/functional/ctime] tests = ['ctime_001_pos' ] @@ -525,13 +525,6 @@ tests = ['refreserv_001_pos', 'refreserv_002_pos', 'refreserv_003_pos', 'refreserv_005_pos', 'refreserv_raidz', 'refreserv_multi_raidz'] -[/opt/zfs-tests/tests/functional/persist_l2arc] -tests = ['persist_l2arc_001_pos', 'persist_l2arc_002_pos', - 'persist_l2arc_003_neg', 'persist_l2arc_004_pos', 'persist_l2arc_005_pos', - 'persist_l2arc_006_pos', 'persist_l2arc_007_pos', 'persist_l2arc_008_pos'] -tags = ['functional', 'persist_l2arc'] - - [/opt/zfs-tests/tests/functional/rename_dirs] tests = ['rename_dirs_001_pos'] @@ -654,3 +647,10 @@ tests = ['log_spacemap_import_logs'] pre = post = tags = ['functional', 'log_spacemap'] + +[/opt/zfs-tests/tests/functional/l2arc] +tests = ['l2arc_arcstats_pos', 'l2arc_mfuonly_pos', + 'persist_l2arc_001_pos', 'persist_l2arc_002_pos', + 'persist_l2arc_003_neg', 'persist_l2arc_004_pos', 'persist_l2arc_005_pos', + 'persist_l2arc_006_pos', 'persist_l2arc_007_pos', 'persist_l2arc_008_pos'] +tags = ['functional', 'l2arc'] diff --git a/usr/src/test/zfs-tests/tests/functional/compression/l2arc_compressed_arc.ksh b/usr/src/test/zfs-tests/tests/functional/compression/l2arc_compressed_arc.ksh new file mode 100755 index 0000000000..f1a912294f --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/compression/l2arc_compressed_arc.ksh @@ -0,0 +1,98 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2020 The FreeBSD Foundation [1] +# +# [1] Portions of this software were developed by Allan Jude +# under sponsorship from the FreeBSD Foundation. + +. $STF_SUITE/include/libtest.shlib + +export SIZE=1G +export VDIR=$TESTDIR/disk.persist_l2arc +export VDEV="$VDIR/a" +export VDEV_CACHE="$VDIR/b" + +# fio options +export DIRECTORY=/$TESTPOOL-l2arc +export NUMJOBS=4 +export RUNTIME=30 +export PERF_RANDSEED=1234 +export PERF_COMPPERCENT=66 +export PERF_COMPCHUNK=0 +export BLOCKSIZE=128K +export SYNC_TYPE=0 +export DIRECT=1 + +# +# DESCRIPTION: +# System with compressed_arc disabled succeeds at reading from L2ARC +# +# STRATEGY: +# 1. Enable compressed_arc. +# 2. Create pool with a cache device and compression enabled. +# 3. Read the number of L2ARC checksum failures. +# 4. Create a random file in that pool and random read for 30 sec. +# 5. Read the number of L2ARC checksum failures. +# + +verify_runnable "global" + +log_assert "L2ARC with compressed_arc enabled succeeds." + +origin_carc_setting=$(get_tunable zfs_compressed_arc_enabled) + +function cleanup +{ + if poolexists $TESTPOOL-l2arc ; then + destroy_pool $TESTPOOL-l2arc + fi + + log_must set_tunable64 zfs_compressed_arc_enabled $origin_carc_setting +} +log_onexit cleanup + +# Enable Compressed ARC so that in-ARC and on-disk will match +log_must set_tunable64 zfs_compressed_arc_enabled 1 + +log_must rm -rf $VDIR +log_must mkdir -p $VDIR +log_must mkfile $SIZE $VDEV + +typeset fill_mb=800 +typeset cache_sz=$(( floor($fill_mb / 2) )) +export FILE_SIZE=$(( floor($fill_mb / $NUMJOBS) ))M + +log_must truncate -s ${cache_sz}M $VDEV_CACHE + +log_must zpool create -O compression=lz4 -f $TESTPOOL-l2arc $VDEV \ + cache $VDEV_CACHE + +l2_cksum_bad_start=$(get_arcstat l2_cksum_bad) + +log_must fio $FIO_SCRIPTS/mkfiles.fio +log_must fio $FIO_SCRIPTS/random_reads.fio + +l2_cksum_bad_end=$(get_arcstat l2_cksum_bad) + +log_note "L2ARC Failed Checksums before: $l2_cksum_bad_start After:"\ + "$l2_cksum_bad_end" +log_must test $(( $l2_cksum_bad_end - $l2_cksum_bad_start )) -eq 0 + +log_must zpool destroy -f $TESTPOOL-l2arc + +log_pass "L2ARC with compressed_arc enabled does not result in checksum errors." diff --git a/usr/src/test/zfs-tests/tests/functional/compression/l2arc_compressed_arc_disabled.ksh b/usr/src/test/zfs-tests/tests/functional/compression/l2arc_compressed_arc_disabled.ksh new file mode 100755 index 0000000000..04e764490e --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/compression/l2arc_compressed_arc_disabled.ksh @@ -0,0 +1,99 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2020 The FreeBSD Foundation [1] +# +# [1] Portions of this software were developed by Allan Jude +# under sponsorship from the FreeBSD Foundation. + +. $STF_SUITE/include/libtest.shlib + +export SIZE=1G +export VDIR=$TESTDIR/disk.persist_l2arc +export VDEV="$VDIR/a" +export VDEV_CACHE="$VDIR/b" + +# fio options +export DIRECTORY=/$TESTPOOL-l2arc +export NUMJOBS=4 +export RUNTIME=30 +export PERF_RANDSEED=1234 +export PERF_COMPPERCENT=66 +export PERF_COMPCHUNK=0 +export BLOCKSIZE=128K +export SYNC_TYPE=0 +export DIRECT=1 + +# +# DESCRIPTION: +# System with compressed_arc disabled succeeds at reading from L2ARC +# +# STRATEGY: +# 1. Disable compressed_arc. +# 2. Create pool with a cache device and compression enabled. +# 3. Read the number of L2ARC checksum failures. +# 4. Create a random file in that pool and random read for 30 sec. +# 5. Read the number of L2ARC checksum failures. +# + +verify_runnable "global" + +log_assert "L2ARC with compressed_arc disabled succeeds." + +origin_carc_setting=$(get_tunable zfs_compressed_arc_enabled) + +function cleanup +{ + if poolexists $TESTPOOL-l2arc ; then + destroy_pool $TESTPOOL-l2arc + fi + + log_must set_tunable64 zfs_compressed_arc_enabled $origin_carc_setting +} +log_onexit cleanup + +log_must rm -rf $VDIR +log_must mkdir -p $VDIR +log_must mkfile $SIZE $VDEV + +# Disable Compressed ARC so that in-ARC and on-disk will not match +log_must set_tunable64 zfs_compressed_arc_enabled 0 + +typeset fill_mb=800 +typeset cache_sz=$(( floor($fill_mb / 2) )) +export FILE_SIZE=$(( floor($fill_mb / $NUMJOBS) ))M + +log_must truncate -s ${cache_sz}M $VDEV_CACHE + +log_must zpool create -O compression=lz4 -f $TESTPOOL-l2arc $VDEV \ + cache $VDEV_CACHE + +l2_cksum_bad_start=$(get_arcstat l2_cksum_bad) + +log_must fio $FIO_SCRIPTS/mkfiles.fio +log_must fio $FIO_SCRIPTS/random_reads.fio + +l2_cksum_bad_end=$(get_arcstat l2_cksum_bad) + +log_note "L2ARC Failed Checksums before: $l2_cksum_bad_start After:"\ + "$l2_cksum_bad_end" +log_must test $(( $l2_cksum_bad_end - $l2_cksum_bad_start )) -eq 0 + +log_must zpool destroy -f $TESTPOOL-l2arc + +log_pass "L2ARC with compressed_arc disabled does not result in checksum"\ + "errors." diff --git a/usr/src/test/zfs-tests/tests/functional/persist_l2arc/Makefile b/usr/src/test/zfs-tests/tests/functional/l2arc/Makefile index f8b7917182..a9ee43dd11 100644 --- a/usr/src/test/zfs-tests/tests/functional/persist_l2arc/Makefile +++ b/usr/src/test/zfs-tests/tests/functional/l2arc/Makefile @@ -16,6 +16,6 @@ include $(SRC)/Makefile.master ROOTOPTPKG = $(ROOT)/opt/zfs-tests -TARGETDIR = $(ROOTOPTPKG)/tests/functional/persist_l2arc +TARGETDIR = $(ROOTOPTPKG)/tests/functional/l2arc include $(SRC)/test/zfs-tests/Makefile.com diff --git a/usr/src/test/zfs-tests/tests/functional/persist_l2arc/cleanup.ksh b/usr/src/test/zfs-tests/tests/functional/l2arc/cleanup.ksh index 828de38625..c3d88e3ffc 100755 --- a/usr/src/test/zfs-tests/tests/functional/persist_l2arc/cleanup.ksh +++ b/usr/src/test/zfs-tests/tests/functional/l2arc/cleanup.ksh @@ -18,12 +18,12 @@ # Copyright (c) 2020, George Amanakis. All rights reserved. # -. $STF_SUITE/tests/functional/persist_l2arc/persist_l2arc.cfg +. $STF_SUITE/tests/functional/l2arc/l2arc.cfg verify_runnable "global" -if datasetexists $TESTPOOL ; then - log_must zpool destroy -f $TESTPOOL +if poolexists $TESTPOOL ; then + log_must destroy_pool $TESTPOOL fi log_must rm -rf $VDIR diff --git a/usr/src/test/zfs-tests/tests/functional/persist_l2arc/persist_l2arc.cfg b/usr/src/test/zfs-tests/tests/functional/l2arc/l2arc.cfg index 60bb246376..cd79af034a 100644 --- a/usr/src/test/zfs-tests/tests/functional/persist_l2arc/persist_l2arc.cfg +++ b/usr/src/test/zfs-tests/tests/functional/l2arc/l2arc.cfg @@ -21,14 +21,14 @@ . $STF_SUITE/include/libtest.shlib export SIZE=1G -export VDIR=$TESTDIR/disk.persist_l2arc +export VDIR=$TESTDIR/disk.l2arc export VDEV="$VDIR/a" export VDEV_CACHE="$VDIR/b" # fio options export DIRECTORY=/$TESTPOOL export NUMJOBS=4 -export RUNTIME=30 +export RUNTIME=10 export PERF_RANDSEED=1234 export PERF_COMPPERCENT=66 export PERF_COMPCHUNK=0 diff --git a/usr/src/test/zfs-tests/tests/functional/l2arc/l2arc_arcstats_pos.ksh b/usr/src/test/zfs-tests/tests/functional/l2arc/l2arc_arcstats_pos.ksh new file mode 100755 index 0000000000..c4f2496992 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/l2arc/l2arc_arcstats_pos.ksh @@ -0,0 +1,107 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2020, George Amanakis. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/l2arc/l2arc.cfg + +# +# DESCRIPTION: +# L2ARC MFU/MRU arcstats do not leak +# +# STRATEGY: +# 1. Create pool with a cache device. +# 2. Create a random file in that pool, smaller than the cache device +# and random read for 10 sec. +# 3. Read l2arc_mfu_asize and l2arc_mru_asize +# 4. Export pool. +# 5. Verify l2arc_mfu_asize and l2arc_mru_asize are 0. +# 6. Import pool. +# 7. Read random read for 10 sec. +# 8. Read l2arc_mfu_asize and l2arc_mru_asize +# 9. Verify that L2ARC MFU increased and MFU+MRU = L2_asize. +# + +verify_runnable "global" + +log_assert "L2ARC MFU/MRU arcstats do not leak." + +function cleanup +{ + if poolexists $TESTPOOL ; then + destroy_pool $TESTPOOL + fi + + log_must set_tunable32 l2arc_noprefetch $noprefetch +} +log_onexit cleanup + +# l2arc_noprefetch is set to 0 to let L2ARC handle prefetches +typeset noprefetch=$(get_tunable l2arc_noprefetch) +log_must set_tunable32 l2arc_noprefetch 0 + +typeset fill_mb=800 +typeset cache_sz=$(( 1.4 * $fill_mb )) +export FILE_SIZE=$(( floor($fill_mb / $NUMJOBS) ))M + +log_must truncate -s ${cache_sz}M $VDEV_CACHE + +log_must zpool create -f $TESTPOOL $VDEV cache $VDEV_CACHE + +log_must fio $FIO_SCRIPTS/mkfiles.fio +log_must fio $FIO_SCRIPTS/random_reads.fio + +arcstat_quiescence_noecho l2_size +log_must zpool offline $TESTPOOL $VDEV_CACHE +arcstat_quiescence_noecho l2_size + +typeset l2_mfu_init=$(get_arcstat l2_mfu_asize) +typeset l2_mru_init=$(get_arcstat l2_mru_asize) +typeset l2_prefetch_init=$(get_arcstat l2_prefetch_asize) +typeset l2_asize_init=$(get_arcstat l2_asize) + +log_must zpool online $TESTPOOL $VDEV_CACHE +arcstat_quiescence_noecho l2_size +log_must zpool export $TESTPOOL +arcstat_quiescence_noecho l2_feeds + +log_must test $(get_arcstat l2_mfu_asize) -eq 0 +log_must test $(get_arcstat l2_mru_asize) -eq 0 +log_must zpool import -d $VDIR $TESTPOOL +arcstat_quiescence_noecho l2_size + +log_must fio $FIO_SCRIPTS/random_reads.fio +arcstat_quiescence_noecho l2_size +log_must zpool offline $TESTPOOL $VDEV_CACHE +arcstat_quiescence_noecho l2_size + +typeset l2_mfu_end=$(get_arcstat l2_mfu_asize) +typeset l2_mru_end=$(get_arcstat l2_mru_asize) +typeset l2_prefetch_end=$(get_arcstat l2_prefetch_asize) +typeset l2_asize_end=$(get_arcstat l2_asize) + +log_must test $(( $l2_mfu_end - $l2_mfu_init )) -gt 0 +log_must test $(( $l2_mru_end + $l2_mfu_end + $l2_prefetch_end - \ + $l2_asize_end )) -eq 0 +log_must test $(( $l2_mru_init + $l2_mfu_init + $l2_prefetch_init - \ + $l2_asize_init )) -eq 0 + +log_must zpool destroy -f $TESTPOOL + +log_pass "L2ARC MFU/MRU arcstats do not leak." diff --git a/usr/src/test/zfs-tests/tests/functional/l2arc/l2arc_mfuonly_pos.ksh b/usr/src/test/zfs-tests/tests/functional/l2arc/l2arc_mfuonly_pos.ksh new file mode 100755 index 0000000000..4fb1c9a6d1 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/l2arc/l2arc_mfuonly_pos.ksh @@ -0,0 +1,94 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2020, George Amanakis. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/l2arc/l2arc.cfg + +# +# DESCRIPTION: +# l2arc_mfuonly does not cache MRU buffers +# +# STRATEGY: +# 1. Set l2arc_mfuonly=yes +# 2. Create pool with a cache device. +# 3. Create a random file in that pool, smaller than the cache device +# and random read for 10 sec. +# 4. Export and re-import the pool. This is necessary as some MFU ghost +# buffers with prefetch status may transition to MRU eventually. +# By re-importing the pool the l2 arcstats reflect the ARC state +# of L2ARC buffers upon their caching in L2ARC. +# 5. Verify l2arc_mru_asize is 0. +# + +verify_runnable "global" + +log_assert "l2arc_mfuonly does not cache MRU buffers." + +function cleanup +{ + if poolexists $TESTPOOL ; then + destroy_pool $TESTPOOL + fi + + log_must set_tunable32 l2arc_noprefetch $noprefetch + log_must set_tunable32 l2arc_mfuonly $mfuonly + log_must set_tunable32 zfs_prefetch_disable $zfsprefetch +} +log_onexit cleanup + +# l2arc_noprefetch is set to 1 as some prefetched buffers may +# transition to MRU. +typeset noprefetch=$(get_tunable l2arc_noprefetch) +log_must set_tunable32 l2arc_noprefetch 1 + +typeset mfuonly=$(get_tunable l2arc_mfuonly) +log_must set_tunable32 l2arc_mfuonly 1 + +typeset zfsprefetch=$(get_tunable zfs_prefetch_disable) +log_must set_tunable32 zfs_prefetch_disable 1 + +typeset fill_mb=800 +typeset cache_sz=$(( 1.4 * $fill_mb )) +export FILE_SIZE=$(( floor($fill_mb / $NUMJOBS) ))M + +log_must truncate -s ${cache_sz}M $VDEV_CACHE + +typeset log_blk_start=$(get_arcstat l2_log_blk_writes) + +log_must zpool create -f $TESTPOOL $VDEV cache $VDEV_CACHE + +log_must fio $FIO_SCRIPTS/mkfiles.fio +log_must fio $FIO_SCRIPTS/random_reads.fio + +log_must zpool export $TESTPOOL +log_must zpool import -d $VDIR $TESTPOOL + +# Regardless of l2arc_noprefetch, some MFU buffers might be evicted +# from ARC, accessed later on as prefetches and transition to MRU as +# prefetches. +# If accessed again they are counted as MRU and the l2arc_mru_asize arcstat +# will not be 0 (mentioned also in zfs-module-parameters.5) +# For the purposes of this test we mitigate this by disabling (predictive) +# ZFS prefetches with zfs_prefetch_disable=1. +log_must test $(get_arcstat l2_mru_asize) -eq 0 + +log_must zpool destroy -f $TESTPOOL + +log_pass "l2arc_mfuonly does not cache MRU buffers." diff --git a/usr/src/test/zfs-tests/tests/functional/persist_l2arc/persist_l2arc_001_pos.ksh b/usr/src/test/zfs-tests/tests/functional/l2arc/persist_l2arc_001_pos.ksh index f69ead3753..595bd41a8c 100755 --- a/usr/src/test/zfs-tests/tests/functional/persist_l2arc/persist_l2arc_001_pos.ksh +++ b/usr/src/test/zfs-tests/tests/functional/l2arc/persist_l2arc_001_pos.ksh @@ -19,7 +19,7 @@ # . $STF_SUITE/include/libtest.shlib -. $STF_SUITE/tests/functional/persist_l2arc/persist_l2arc.cfg +. $STF_SUITE/tests/functional/l2arc/l2arc.cfg # # DESCRIPTION: @@ -28,13 +28,13 @@ # STRATEGY: # 1. Create pool with a cache device. # 2. Export and re-import pool without writing any data. -# 3. Create a random file in that pool and random read for 30 sec. +# 3. Create a random file in that pool and random read for 10 sec. # 4. Export pool. # 5. Read the amount of log blocks written from the header of the # L2ARC device. # 6. Import pool. # 7. Read the amount of log blocks rebuilt in arcstats and compare to -# (4). +# (5). # 8. Check if the labels of the L2ARC device are intact. # # * We can predict the minimum bytes of L2ARC restored if we subtract @@ -83,7 +83,9 @@ log_must zpool import -d $VDIR $TESTPOOL log_must fio $FIO_SCRIPTS/mkfiles.fio log_must fio $FIO_SCRIPTS/random_reads.fio +arcstat_quiescence_noecho l2_size log_must zpool export $TESTPOOL +arcstat_quiescence_noecho l2_feeds typeset l2_dh_log_blk=$(zdb -l $VDEV_CACHE | grep log_blk_count | \ awk '{print $2}') @@ -91,15 +93,18 @@ typeset l2_dh_log_blk=$(zdb -l $VDEV_CACHE | grep log_blk_count | \ typeset l2_rebuild_log_blk_start=$(get_arcstat l2_rebuild_log_blks) log_must zpool import -d $VDIR $TESTPOOL +arcstat_quiescence_noecho l2_size -sleep 2 +typeset l2_rebuild_log_blk_end=$(arcstat_quiescence_echo l2_rebuild_log_blks) -typeset l2_rebuild_log_blk_end=$(get_arcstat l2_rebuild_log_blks) - -log_must test $l2_dh_log_blk -eq $(( $l2_rebuild_log_blk_end - $l2_rebuild_log_blk_start )) +log_must test $l2_dh_log_blk -eq $(( $l2_rebuild_log_blk_end - + $l2_rebuild_log_blk_start )) log_must test $l2_dh_log_blk -gt 0 -log_must zdb -lll $VDEV_CACHE +log_must zpool offline $TESTPOOL $VDEV_CACHE +arcstat_quiescence_noecho l2_size + +log_must zdb -lllq $VDEV_CACHE log_must zpool destroy -f $TESTPOOL diff --git a/usr/src/test/zfs-tests/tests/functional/persist_l2arc/persist_l2arc_002_pos.ksh b/usr/src/test/zfs-tests/tests/functional/l2arc/persist_l2arc_002_pos.ksh index 79cefd8af4..d863249a78 100755 --- a/usr/src/test/zfs-tests/tests/functional/persist_l2arc/persist_l2arc_002_pos.ksh +++ b/usr/src/test/zfs-tests/tests/functional/l2arc/persist_l2arc_002_pos.ksh @@ -19,7 +19,7 @@ # . $STF_SUITE/include/libtest.shlib -. $STF_SUITE/tests/functional/persist_l2arc/persist_l2arc.cfg +. $STF_SUITE/tests/functional/l2arc/l2arc.cfg . $STF_SUITE/tests/functional/cli_root/zfs_load-key/zfs_load-key_common.kshlib # @@ -30,7 +30,7 @@ # 1. Create pool with a cache device. # 2. Create a an encrypted ZFS file system. # 3. Create a random file in the encyrpted file system and random -# read for 30 sec. +# read for 10 sec. # 4. Export pool. # 5. Read the amount of log blocks written from the header of the # L2ARC device. @@ -86,9 +86,9 @@ log_must eval "echo $PASSPHRASE | zfs create -o encryption=on" \ log_must fio $FIO_SCRIPTS/mkfiles.fio log_must fio $FIO_SCRIPTS/random_reads.fio +arcstat_quiescence_noecho l2_size log_must zpool export $TESTPOOL - -sleep 2 +arcstat_quiescence_noecho l2_feeds typeset l2_dh_log_blk=$(zdb -l $VDEV_CACHE | grep log_blk_count | \ awk '{print $2}') @@ -97,14 +97,17 @@ typeset l2_rebuild_log_blk_start=$(get_arcstat l2_rebuild_log_blks) log_must zpool import -d $VDIR $TESTPOOL log_must eval "echo $PASSPHRASE | zfs mount -l $TESTPOOL/$TESTFS1" +arcstat_quiescence_noecho l2_size -sleep 2 - -typeset l2_rebuild_log_blk_end=$(get_arcstat l2_rebuild_log_blks) +typeset l2_rebuild_log_blk_end=$(arcstat_quiescence_echo l2_rebuild_log_blks) -log_must test $l2_dh_log_blk -eq $(( $l2_rebuild_log_blk_end - $l2_rebuild_log_blk_start )) +log_must test $l2_dh_log_blk -eq $(( $l2_rebuild_log_blk_end - \ + $l2_rebuild_log_blk_start )) log_must test $l2_dh_log_blk -gt 0 +log_must zpool offline $TESTPOOL $VDEV_CACHE +arcstat_quiescence_noecho l2_size + log_must zdb -lq $VDEV_CACHE log_must zpool destroy -f $TESTPOOL diff --git a/usr/src/test/zfs-tests/tests/functional/persist_l2arc/persist_l2arc_003_neg.ksh b/usr/src/test/zfs-tests/tests/functional/l2arc/persist_l2arc_003_neg.ksh index 7fe3d9ca21..1ad6b27244 100755 --- a/usr/src/test/zfs-tests/tests/functional/persist_l2arc/persist_l2arc_003_neg.ksh +++ b/usr/src/test/zfs-tests/tests/functional/l2arc/persist_l2arc_003_neg.ksh @@ -19,7 +19,7 @@ # . $STF_SUITE/include/libtest.shlib -. $STF_SUITE/tests/functional/persist_l2arc/persist_l2arc.cfg +. $STF_SUITE/tests/functional/l2arc/l2arc.cfg # # DESCRIPTION: @@ -28,11 +28,11 @@ # STRATEGY: # 1. Set l2arc_rebuild_enabled = 0 # 2. Create pool with a cache device. -# 3. Create a random file in that pool and random read for 30 sec. +# 3. Create a random file in that pool and random read for 10 sec. # 4. Export pool. # 5. Import pool. # 6. Check in zpool iostat if the cache device has space allocated. -# 7. Read the file written in (2) and check if l2_hits in +# 7. Read the file written in (3) and check if l2_hits in # /proc/spl/kstat/zfs/arcstats increased. # diff --git a/usr/src/test/zfs-tests/tests/functional/persist_l2arc/persist_l2arc_004_pos.ksh b/usr/src/test/zfs-tests/tests/functional/l2arc/persist_l2arc_004_pos.ksh index b0529dccae..6a353bd3a4 100755 --- a/usr/src/test/zfs-tests/tests/functional/persist_l2arc/persist_l2arc_004_pos.ksh +++ b/usr/src/test/zfs-tests/tests/functional/l2arc/persist_l2arc_004_pos.ksh @@ -19,7 +19,7 @@ # . $STF_SUITE/include/libtest.shlib -. $STF_SUITE/tests/functional/persist_l2arc/persist_l2arc.cfg +. $STF_SUITE/tests/functional/l2arc/l2arc.cfg # # DESCRIPTION: @@ -28,12 +28,12 @@ # STRATEGY: # 1. Create pool with a cache device. # 2. Create a random file in that pool, smaller than the cache device -# and random read for 30 sec. +# and random read for 10 sec. # 3. Export pool. # 4. Read amount of log blocks written. # 5. Import pool. # 6. Read amount of log blocks built. -# 7. Compare the two amounts +# 7. Compare the two amounts. # 8. Read the file written in (2) and check if l2_hits in # /proc/spl/kstat/zfs/arcstats increased. # 9. Check if the labels of the L2ARC device are intact. @@ -70,30 +70,31 @@ log_must zpool create -f $TESTPOOL $VDEV cache $VDEV_CACHE log_must fio $FIO_SCRIPTS/mkfiles.fio log_must fio $FIO_SCRIPTS/random_reads.fio +arcstat_quiescence_noecho l2_size log_must zpool export $TESTPOOL - -sleep 2 +arcstat_quiescence_noecho l2_feeds typeset log_blk_end=$(get_arcstat l2_log_blk_writes) - typeset log_blk_rebuild_start=$(get_arcstat l2_rebuild_log_blks) log_must zpool import -d $VDIR $TESTPOOL typeset l2_hits_start=$(get_arcstat l2_hits) -export RUNTIME=10 log_must fio $FIO_SCRIPTS/random_reads.fio +arcstat_quiescence_noecho l2_size +typeset log_blk_rebuild_end=$(arcstat_quiescence_echo l2_rebuild_log_blks) typeset l2_hits_end=$(get_arcstat l2_hits) -typeset log_blk_rebuild_end=$(get_arcstat l2_rebuild_log_blks) - log_must test $(( $log_blk_rebuild_end - $log_blk_rebuild_start )) -eq \ $(( $log_blk_end - $log_blk_start )) log_must test $l2_hits_end -gt $l2_hits_start +log_must zpool offline $TESTPOOL $VDEV_CACHE +arcstat_quiescence_noecho l2_size + log_must zdb -lq $VDEV_CACHE log_must zpool destroy -f $TESTPOOL diff --git a/usr/src/test/zfs-tests/tests/functional/persist_l2arc/persist_l2arc_005_pos.ksh b/usr/src/test/zfs-tests/tests/functional/l2arc/persist_l2arc_005_pos.ksh index 4a9a8a114c..c926da0be8 100755 --- a/usr/src/test/zfs-tests/tests/functional/persist_l2arc/persist_l2arc_005_pos.ksh +++ b/usr/src/test/zfs-tests/tests/functional/l2arc/persist_l2arc_005_pos.ksh @@ -19,7 +19,7 @@ # . $STF_SUITE/include/libtest.shlib -. $STF_SUITE/tests/functional/persist_l2arc/persist_l2arc.cfg +. $STF_SUITE/tests/functional/l2arc/l2arc.cfg . $STF_SUITE/tests/functional/cli_root/zfs_load-key/zfs_load-key_common.kshlib # @@ -30,13 +30,13 @@ # 1. Create pool with a cache device. # 2. Create a an encrypted ZFS file system. # 3. Create a random file in the entrypted file system, -# smaller than the cache device, and random read for 30 sec. +# smaller than the cache device, and random read for 10 sec. # 4. Export pool. # 5. Read amount of log blocks written. # 6. Import pool. # 7. Mount the encypted ZFS file system. # 8. Read amount of log blocks built. -# 9. Compare the two amounts +# 9. Compare the two amounts. # 10. Read the file written in (3) and check if l2_hits in # /proc/spl/kstat/zfs/arcstats increased. # 11. Check if the labels of the L2ARC device are intact. @@ -76,12 +76,11 @@ log_must eval "echo $PASSPHRASE | zfs create -o encryption=on" \ log_must fio $FIO_SCRIPTS/mkfiles.fio log_must fio $FIO_SCRIPTS/random_reads.fio +arcstat_quiescence_noecho l2_size log_must zpool export $TESTPOOL - -sleep 2 +arcstat_quiescence_noecho l2_feeds typeset log_blk_end=$(get_arcstat l2_log_blk_writes) - typeset log_blk_rebuild_start=$(get_arcstat l2_rebuild_log_blks) log_must zpool import -d $VDIR $TESTPOOL @@ -89,18 +88,20 @@ log_must eval "echo $PASSPHRASE | zfs mount -l $TESTPOOL/$TESTFS1" typeset l2_hits_start=$(get_arcstat l2_hits) -export RUNTIME=10 log_must fio $FIO_SCRIPTS/random_reads.fio +arcstat_quiescence_noecho l2_size +typeset log_blk_rebuild_end=$(arcstat_quiescence_echo l2_rebuild_log_blks) typeset l2_hits_end=$(get_arcstat l2_hits) -typeset log_blk_rebuild_end=$(get_arcstat l2_rebuild_log_blks) - log_must test $(( $log_blk_rebuild_end - $log_blk_rebuild_start )) -eq \ $(( $log_blk_end - $log_blk_start )) log_must test $l2_hits_end -gt $l2_hits_start +log_must zpool offline $TESTPOOL $VDEV_CACHE +arcstat_quiescence_noecho l2_size + log_must zdb -lq $VDEV_CACHE log_must zpool destroy -f $TESTPOOL diff --git a/usr/src/test/zfs-tests/tests/functional/persist_l2arc/persist_l2arc_006_pos.ksh b/usr/src/test/zfs-tests/tests/functional/l2arc/persist_l2arc_006_pos.ksh index b7de5050c0..b7f4ad6d49 100755 --- a/usr/src/test/zfs-tests/tests/functional/persist_l2arc/persist_l2arc_006_pos.ksh +++ b/usr/src/test/zfs-tests/tests/functional/l2arc/persist_l2arc_006_pos.ksh @@ -19,7 +19,7 @@ # . $STF_SUITE/include/libtest.shlib -. $STF_SUITE/tests/functional/persist_l2arc/persist_l2arc.cfg +. $STF_SUITE/tests/functional/l2arc/l2arc.cfg # # DESCRIPTION: @@ -28,7 +28,7 @@ # # STRATEGY: # 1. Create pool with a cache device. -# 2. Create a random file in that pool and random read for 30 sec. +# 2. Create a random file in that pool and random read for 10 sec. # 3. Read the amount of log blocks written from the header of the # L2ARC device. # 4. Offline the L2ARC device and export pool. @@ -71,26 +71,29 @@ log_must zpool create -f $TESTPOOL $VDEV cache $VDEV_CACHE log_must fio $FIO_SCRIPTS/mkfiles.fio log_must fio $FIO_SCRIPTS/random_reads.fio +arcstat_quiescence_noecho l2_size log_must zpool offline $TESTPOOL $VDEV_CACHE +arcstat_quiescence_noecho l2_size log_must zpool export $TESTPOOL - -sleep 5 +arcstat_quiescence_noecho l2_feeds typeset l2_rebuild_log_blk_start=$(get_arcstat l2_rebuild_log_blks) - typeset l2_dh_log_blk=$(zdb -l $VDEV_CACHE | grep log_blk_count | \ awk '{print $2}') log_must zpool import -d $VDIR $TESTPOOL log_must zpool online $TESTPOOL $VDEV_CACHE +arcstat_quiescence_noecho l2_size -sleep 5 +typeset l2_rebuild_log_blk_end=$(arcstat_quiescence_echo l2_rebuild_log_blks) -typeset l2_rebuild_log_blk_end=$(get_arcstat l2_rebuild_log_blks) - -log_must test $l2_dh_log_blk -eq $(( $l2_rebuild_log_blk_end - $l2_rebuild_log_blk_start )) +log_must test $l2_dh_log_blk -eq $(( $l2_rebuild_log_blk_end - \ + $l2_rebuild_log_blk_start )) log_must test $l2_dh_log_blk -gt 0 +log must zpool offline $TESTPOOL $VDEV_CACHE +arcstat_quiescence_noecho l2_size + log_must zdb -lq $VDEV_CACHE log_must zpool destroy -f $TESTPOOL diff --git a/usr/src/test/zfs-tests/tests/functional/persist_l2arc/persist_l2arc_007_pos.ksh b/usr/src/test/zfs-tests/tests/functional/l2arc/persist_l2arc_007_pos.ksh index 3c28d7a5fb..d45a8d471d 100755 --- a/usr/src/test/zfs-tests/tests/functional/persist_l2arc/persist_l2arc_007_pos.ksh +++ b/usr/src/test/zfs-tests/tests/functional/l2arc/persist_l2arc_007_pos.ksh @@ -19,7 +19,7 @@ # . $STF_SUITE/include/libtest.shlib -. $STF_SUITE/tests/functional/persist_l2arc/persist_l2arc.cfg +. $STF_SUITE/tests/functional/l2arc/l2arc.cfg # # DESCRIPTION: @@ -27,13 +27,13 @@ # # STRATEGY: # 1. Create pool with a cache device. -# 2. Create a random file in that pool and random read for 30 sec. -# 3. Read the amount of log blocks written from the header of the +# 2. Create a random file in that pool and random read for 10 sec. +# 3. Offline the L2ARC device. +# 4. Read the amount of log blocks written from the header of the # L2ARC device. -# 4. Offline the L2ARC device. # 5. Online the L2ARC device. # 6. Read the amount of log blocks rebuilt in arcstats and compare to -# (3). +# (4). # 7. Check if the labels of the L2ARC device are intact. # @@ -70,24 +70,26 @@ log_must zpool create -f $TESTPOOL $VDEV cache $VDEV_CACHE log_must fio $FIO_SCRIPTS/mkfiles.fio log_must fio $FIO_SCRIPTS/random_reads.fio +arcstat_quiescence_noecho l2_size log_must zpool offline $TESTPOOL $VDEV_CACHE - -sleep 10 +arcstat_quiescence_noecho l2_size typeset l2_rebuild_log_blk_start=$(get_arcstat l2_rebuild_log_blks) - typeset l2_dh_log_blk=$(zdb -l $VDEV_CACHE | grep log_blk_count | \ awk '{print $2}') log_must zpool online $TESTPOOL $VDEV_CACHE +arcstat_quiescence_noecho l2_size -sleep 10 - -typeset l2_rebuild_log_blk_end=$(get_arcstat l2_rebuild_log_blks) +typeset l2_rebuild_log_blk_end=$(arcstat_quiescence_echo l2_rebuild_log_blks) -log_must test $l2_dh_log_blk -eq $(( $l2_rebuild_log_blk_end - $l2_rebuild_log_blk_start )) +log_must test $l2_dh_log_blk -eq $(( $l2_rebuild_log_blk_end - \ + $l2_rebuild_log_blk_start )) log_must test $l2_dh_log_blk -gt 0 +log_must zpool offline $TESTPOOL $VDEV_CACHE +arcstat_quiescence_noecho l2_size + log_must zdb -lq $VDEV_CACHE log_must zpool destroy -f $TESTPOOL diff --git a/usr/src/test/zfs-tests/tests/functional/persist_l2arc/persist_l2arc_008_pos.ksh b/usr/src/test/zfs-tests/tests/functional/l2arc/persist_l2arc_008_pos.ksh index c94b7ad9fe..ef600aca4f 100755 --- a/usr/src/test/zfs-tests/tests/functional/persist_l2arc/persist_l2arc_008_pos.ksh +++ b/usr/src/test/zfs-tests/tests/functional/l2arc/persist_l2arc_008_pos.ksh @@ -19,7 +19,7 @@ # . $STF_SUITE/include/libtest.shlib -. $STF_SUITE/tests/functional/persist_l2arc/persist_l2arc.cfg +. $STF_SUITE/tests/functional/l2arc/l2arc.cfg # # DESCRIPTION: @@ -27,20 +27,20 @@ # # STRATEGY: # 1. Create pool with a cache device. -# 2. Create a random file in that pool and random read for 30 sec. +# 2. Create a random file in that pool and random read for 10 sec. # 3. Read the amount of log blocks written from the header of the # L2ARC device. # 4. Offline the L2ARC device. # 5. Online the L2ARC device. # 6. Read the amount of log blocks rebuilt in arcstats and compare to # (3). -# 7. Create another random file in that pool and random read for 30 sec. +# 7. Create another random file in that pool and random read for 10 sec. # 8. Read the amount of log blocks written from the header of the # L2ARC device. # 9. Offline the L2ARC device. # 10. Online the L2ARC device. # 11. Read the amount of log blocks rebuilt in arcstats and compare to -# (7). +# (8). # 12. Check if the amount of log blocks on the cache device has # increased. # 13. Export the pool. @@ -80,62 +80,62 @@ log_must zpool create -f $TESTPOOL $VDEV cache $VDEV_CACHE log_must fio $FIO_SCRIPTS/mkfiles.fio log_must fio $FIO_SCRIPTS/random_reads.fio +arcstat_quiescence_noecho l2_size log_must zpool offline $TESTPOOL $VDEV_CACHE - -sleep 2 +arcstat_quiescence_noecho l2_size typeset l2_dh_log_blk1=$(zdb -l $VDEV_CACHE | grep log_blk_count | \ awk '{print $2}') - typeset l2_rebuild_log_blk_start=$(get_arcstat l2_rebuild_log_blks) log_must zpool online $TESTPOOL $VDEV_CACHE +arcstat_quiescence_noecho l2_size -sleep 5 - -typeset l2_rebuild_log_blk_end=$(get_arcstat l2_rebuild_log_blks) +typeset l2_rebuild_log_blk_end=$(arcstat_quiescence_echo l2_rebuild_log_blks) -log_must test $l2_dh_log_blk1 -eq $(( $l2_rebuild_log_blk_end - $l2_rebuild_log_blk_start )) +log_must test $l2_dh_log_blk1 -eq $(( $l2_rebuild_log_blk_end - \ + $l2_rebuild_log_blk_start )) log_must test $l2_dh_log_blk1 -gt 0 log_must fio $FIO_SCRIPTS/mkfiles.fio log_must fio $FIO_SCRIPTS/random_reads.fio +arcstat_quiescence_noecho l2_size log_must zpool offline $TESTPOOL $VDEV_CACHE - -sleep 2 +arcstat_quiescence_noecho l2_size typeset l2_dh_log_blk2=$(zdb -l $VDEV_CACHE | grep log_blk_count | \ awk '{print $2}') - typeset l2_rebuild_log_blk_start=$(get_arcstat l2_rebuild_log_blks) log_must zpool online $TESTPOOL $VDEV_CACHE +arcstat_quiescence_noecho l2_size -sleep 5 - -typeset l2_rebuild_log_blk_end=$(get_arcstat l2_rebuild_log_blks) - -log_must test $l2_dh_log_blk2 -eq $(( $l2_rebuild_log_blk_end - $l2_rebuild_log_blk_start )) +typeset l2_rebuild_log_blk_end=$(arcstat_quiescence_echo l2_rebuild_log_blks) +log_must test $l2_dh_log_blk2 -eq $(( $l2_rebuild_log_blk_end - \ + $l2_rebuild_log_blk_start )) log_must test $l2_dh_log_blk2 -gt $l2_dh_log_blk1 log_must zpool export $TESTPOOL +arcstat_quiescence_noecho l2_feeds typeset l2_dh_log_blk3=$(zdb -l $VDEV_CACHE | grep log_blk_count | \ awk '{print $2}') - typeset l2_rebuild_log_blk_start=$(get_arcstat l2_rebuild_log_blks) log_must zpool import -d $VDIR $TESTPOOL +arcstat_quiescence_noecho l2_size -sleep 5 +typeset l2_rebuild_log_blk_end=$(arcstat_quiescence_echo l2_rebuild_log_blks) -typeset l2_rebuild_log_blk_end=$(get_arcstat l2_rebuild_log_blks) - -log_must test $l2_dh_log_blk3 -eq $(( $l2_rebuild_log_blk_end - $l2_rebuild_log_blk_start )) +log_must test $l2_dh_log_blk3 -eq $(( $l2_rebuild_log_blk_end - \ + $l2_rebuild_log_blk_start )) log_must test $l2_dh_log_blk3 -gt 0 +log must zpool offline $TESTPOOL $VDEV_CACHE +arcstat_quiescence_noecho l2_size + log_must zdb -lq $VDEV_CACHE log_must zpool destroy -f $TESTPOOL diff --git a/usr/src/test/zfs-tests/tests/functional/persist_l2arc/setup.ksh b/usr/src/test/zfs-tests/tests/functional/l2arc/setup.ksh index ef95c84cdd..b7a68c1345 100755 --- a/usr/src/test/zfs-tests/tests/functional/persist_l2arc/setup.ksh +++ b/usr/src/test/zfs-tests/tests/functional/l2arc/setup.ksh @@ -18,7 +18,7 @@ # Copyright (c) 2020, George Amanakis. All rights reserved. # -. $STF_SUITE/tests/functional/persist_l2arc/persist_l2arc.cfg +. $STF_SUITE/tests/functional/l2arc/l2arc.cfg verify_runnable "global" diff --git a/usr/src/uts/common/fs/zfs/arc.c b/usr/src/uts/common/fs/zfs/arc.c index 1034481117..37ec80e4b3 100644 --- a/usr/src/uts/common/fs/zfs/arc.c +++ b/usr/src/uts/common/fs/zfs/arc.c @@ -26,6 +26,10 @@ * Copyright 2017 Nexenta Systems, Inc. All rights reserved. * Copyright (c) 2011, 2019, Delphix. All rights reserved. * Copyright (c) 2020, George Amanakis. All rights reserved. + * Copyright (c) 2020, The FreeBSD Foundation [1] + * + * [1] Portions of this software were developed by Allan Jude + * under sponsorship from the FreeBSD Foundation. */ /* @@ -441,6 +445,8 @@ arc_stats_t arc_stats = { { "evict_not_enough", KSTAT_DATA_UINT64 }, { "evict_l2_cached", KSTAT_DATA_UINT64 }, { "evict_l2_eligible", KSTAT_DATA_UINT64 }, + { "evict_l2_eligible_mfu", KSTAT_DATA_UINT64 }, + { "evict_l2_eligible_mru", KSTAT_DATA_UINT64 }, { "evict_l2_ineligible", KSTAT_DATA_UINT64 }, { "evict_l2_skip", KSTAT_DATA_UINT64 }, { "hash_elements", KSTAT_DATA_UINT64 }, @@ -477,6 +483,11 @@ arc_stats_t arc_stats = { { "mfu_ghost_evictable_metadata", KSTAT_DATA_UINT64 }, { "l2_hits", KSTAT_DATA_UINT64 }, { "l2_misses", KSTAT_DATA_UINT64 }, + { "l2_prefetch_asize", KSTAT_DATA_UINT64 }, + { "l2_mru_asize", KSTAT_DATA_UINT64 }, + { "l2_mfu_asize", KSTAT_DATA_UINT64 }, + { "l2_bufc_data_asize", KSTAT_DATA_UINT64 }, + { "l2_bufc_metadata_asize", KSTAT_DATA_UINT64 }, { "l2_feeds", KSTAT_DATA_UINT64 }, { "l2_rw_clash", KSTAT_DATA_UINT64 }, { "l2_read_bytes", KSTAT_DATA_UINT64 }, @@ -702,6 +713,13 @@ uint64_t zfs_crc64_table[256]; #define L2ARC_FEED_SECS 1 /* caching interval secs */ #define L2ARC_FEED_MIN_MS 200 /* min caching interval ms */ +/* + * We can feed L2ARC from two states of ARC buffers, mru and mfu, + * and each of the state has two types: data and metadata. + */ +#define L2ARC_FEED_TYPES 4 + + #define l2arc_writes_sent ARCSTAT(arcstat_l2_writes_sent) #define l2arc_writes_done ARCSTAT(arcstat_l2_writes_done) @@ -715,6 +733,7 @@ uint64_t l2arc_feed_min_ms = L2ARC_FEED_MIN_MS; /* min interval milliseconds */ boolean_t l2arc_noprefetch = B_TRUE; /* don't cache prefetch bufs */ boolean_t l2arc_feed_again = B_TRUE; /* turbo warmup */ boolean_t l2arc_norw = B_TRUE; /* no reads during writes */ +int l2arc_meta_percent = 33; /* limit on headers size */ /* * L2ARC Internals @@ -785,6 +804,18 @@ static inline void arc_hdr_clear_flags(arc_buf_hdr_t *hdr, arc_flags_t flags); static boolean_t l2arc_write_eligible(uint64_t, arc_buf_hdr_t *); static void l2arc_read_done(zio_t *); +static void l2arc_do_free_on_write(void); +static void l2arc_hdr_arcstats_update(arc_buf_hdr_t *hdr, boolean_t incr, + boolean_t state_only); + +#define l2arc_hdr_arcstats_increment(hdr) \ + l2arc_hdr_arcstats_update((hdr), B_TRUE, B_FALSE) +#define l2arc_hdr_arcstats_decrement(hdr) \ + l2arc_hdr_arcstats_update((hdr), B_FALSE, B_FALSE) +#define l2arc_hdr_arcstats_increment_state(hdr) \ + l2arc_hdr_arcstats_update((hdr), B_TRUE, B_TRUE) +#define l2arc_hdr_arcstats_decrement_state(hdr) \ + l2arc_hdr_arcstats_update((hdr), B_FALSE, B_TRUE) /* * The arc_all_memory function is a ZoL enhancement that lives in their OSL @@ -931,6 +962,12 @@ buf_hash_remove(arc_buf_hdr_t *hdr) } /* + * l2arc_mfuonly : A ZFS module parameter that controls whether only MFU + * metadata and data are cached from ARC into L2ARC. + */ +int l2arc_mfuonly = 0; + +/* * Global data structures and functions for the buf kmem cache. */ @@ -2171,7 +2208,11 @@ add_reference(arc_buf_hdr_t *hdr, void *tag) arc_evictable_space_decrement(hdr, state); } /* remove the prefetch flag if we get a reference */ + if (HDR_HAS_L2HDR(hdr)) + l2arc_hdr_arcstats_decrement_state(hdr); arc_hdr_clear_flags(hdr, ARC_FLAG_PREFETCH); + if (HDR_HAS_L2HDR(hdr)) + l2arc_hdr_arcstats_increment_state(hdr); } } @@ -2407,9 +2448,16 @@ arc_change_state(arc_state_t *new_state, arc_buf_hdr_t *hdr, } } - if (HDR_HAS_L1HDR(hdr)) + if (HDR_HAS_L1HDR(hdr)) { hdr->b_l1hdr.b_state = new_state; + if (HDR_HAS_L2HDR(hdr) && new_state != arc_l2c_only) { + l2arc_hdr_arcstats_decrement_state(hdr); + hdr->b_l2hdr.b_arcs_state = new_state->arcs_state; + l2arc_hdr_arcstats_increment_state(hdr); + } + } + /* * L2 headers should never be on the L2 state list since they don't * have L1 headers allocated. @@ -2712,7 +2760,7 @@ static void l2arc_log_blk_fetch_abort(zio_t *zio); /* L2ARC persistence block restoration routines. */ static void l2arc_log_blk_restore(l2arc_dev_t *dev, - const l2arc_log_blk_phys_t *lb, uint64_t lb_asize, uint64_t lb_daddr); + const l2arc_log_blk_phys_t *lb, uint64_t lb_asize); static void l2arc_hdr_restore(const l2arc_log_ent_phys_t *le, l2arc_dev_t *dev); @@ -3437,7 +3485,8 @@ arc_alloc_buf(spa_t *spa, void *tag, arc_buf_contents_t type, int32_t size) arc_buf_hdr_t * arc_buf_alloc_l2only(size_t size, arc_buf_contents_t type, l2arc_dev_t *dev, dva_t dva, uint64_t daddr, int32_t psize, uint64_t birth, - enum zio_compress compress, boolean_t protected, boolean_t prefetch) + enum zio_compress compress, boolean_t protected, + boolean_t prefetch, arc_state_type_t arcs_state) { arc_buf_hdr_t *hdr; @@ -3460,6 +3509,7 @@ arc_buf_alloc_l2only(size_t size, arc_buf_contents_t type, l2arc_dev_t *dev, hdr->b_l2hdr.b_dev = dev; hdr->b_l2hdr.b_daddr = daddr; + hdr->b_l2hdr.b_arcs_state = arcs_state; return (hdr); } @@ -3543,6 +3593,76 @@ arc_alloc_raw_buf(spa_t *spa, void *tag, uint64_t dsobj, boolean_t byteorder, } static void +l2arc_hdr_arcstats_update(arc_buf_hdr_t *hdr, boolean_t incr, + boolean_t state_only) +{ + l2arc_buf_hdr_t *l2hdr = &hdr->b_l2hdr; + l2arc_dev_t *dev = l2hdr->b_dev; + uint64_t lsize = HDR_GET_LSIZE(hdr); + uint64_t psize = HDR_GET_PSIZE(hdr); + uint64_t asize = vdev_psize_to_asize(dev->l2ad_vdev, psize); + arc_buf_contents_t type = hdr->b_type; + int64_t lsize_s; + int64_t psize_s; + int64_t asize_s; + + if (incr) { + lsize_s = lsize; + psize_s = psize; + asize_s = asize; + } else { + lsize_s = -lsize; + psize_s = -psize; + asize_s = -asize; + } + + /* If the buffer is a prefetch, count it as such. */ + if (HDR_PREFETCH(hdr)) { + ARCSTAT_INCR(arcstat_l2_prefetch_asize, asize_s); + } else { + /* + * We use the value stored in the L2 header upon initial + * caching in L2ARC. This value will be updated in case + * an MRU/MRU_ghost buffer transitions to MFU but the L2ARC + * metadata (log entry) cannot currently be updated. Having + * the ARC state in the L2 header solves the problem of a + * possibly absent L1 header (apparent in buffers restored + * from persistent L2ARC). + */ + switch (hdr->b_l2hdr.b_arcs_state) { + case ARC_STATE_MRU_GHOST: + case ARC_STATE_MRU: + ARCSTAT_INCR(arcstat_l2_mru_asize, asize_s); + break; + case ARC_STATE_MFU_GHOST: + case ARC_STATE_MFU: + ARCSTAT_INCR(arcstat_l2_mfu_asize, asize_s); + break; + default: + break; + } + } + + if (state_only) + return; + + ARCSTAT_INCR(arcstat_l2_psize, psize_s); + ARCSTAT_INCR(arcstat_l2_lsize, lsize_s); + + switch (type) { + case ARC_BUFC_DATA: + ARCSTAT_INCR(arcstat_l2_bufc_data_asize, asize_s); + break; + case ARC_BUFC_METADATA: + ARCSTAT_INCR(arcstat_l2_bufc_metadata_asize, asize_s); + break; + default: + break; + } +} + + +static void arc_hdr_l2hdr_destroy(arc_buf_hdr_t *hdr) { l2arc_buf_hdr_t *l2hdr = &hdr->b_l2hdr; @@ -3555,9 +3675,7 @@ arc_hdr_l2hdr_destroy(arc_buf_hdr_t *hdr) list_remove(&dev->l2ad_buflist, hdr); - ARCSTAT_INCR(arcstat_l2_psize, -psize); - ARCSTAT_INCR(arcstat_l2_lsize, -HDR_GET_LSIZE(hdr)); - + l2arc_hdr_arcstats_decrement(hdr); vdev_space_update(dev->l2ad_vdev, -asize, 0, 0); (void) zfs_refcount_remove_many(&dev->l2ad_alloc, arc_hdr_size(hdr), @@ -3767,6 +3885,21 @@ arc_evict_hdr(arc_buf_hdr_t *hdr, kmutex_t *hash_lock) if (l2arc_write_eligible(hdr->b_spa, hdr)) { ARCSTAT_INCR(arcstat_evict_l2_eligible, HDR_GET_LSIZE(hdr)); + + switch (state->arcs_state) { + case ARC_STATE_MRU: + ARCSTAT_INCR( + arcstat_evict_l2_eligible_mru, + HDR_GET_LSIZE(hdr)); + break; + case ARC_STATE_MFU: + ARCSTAT_INCR( + arcstat_evict_l2_eligible_mfu, + HDR_GET_LSIZE(hdr)); + break; + default: + break; + } } else { ARCSTAT_INCR(arcstat_evict_l2_ineligible, HDR_GET_LSIZE(hdr)); @@ -4824,9 +4957,6 @@ arc_adapt(int bytes, arc_state_t *state) int64_t mrug_size = zfs_refcount_count(&arc_mru_ghost->arcs_size); int64_t mfug_size = zfs_refcount_count(&arc_mfu_ghost->arcs_size); - if (state == arc_l2c_only) - return; - ASSERT(bytes > 0); /* * Adapt the target size of the MRU list: @@ -5121,10 +5251,14 @@ arc_access(arc_buf_hdr_t *hdr, kmutex_t *hash_lock) ASSERT(multilist_link_active( &hdr->b_l1hdr.b_arc_node)); } else { + if (HDR_HAS_L2HDR(hdr)) + l2arc_hdr_arcstats_decrement_state(hdr); arc_hdr_clear_flags(hdr, ARC_FLAG_PREFETCH | ARC_FLAG_PRESCIENT_PREFETCH); ARCSTAT_BUMP(arcstat_mru_hits); + if (HDR_HAS_L2HDR(hdr)) + l2arc_hdr_arcstats_increment_state(hdr); } hdr->b_l1hdr.b_arc_access = now; return; @@ -5153,13 +5287,16 @@ arc_access(arc_buf_hdr_t *hdr, kmutex_t *hash_lock) * was evicted from the cache. Move it to the * MFU state. */ - if (HDR_PREFETCH(hdr) || HDR_PRESCIENT_PREFETCH(hdr)) { new_state = arc_mru; if (zfs_refcount_count(&hdr->b_l1hdr.b_refcnt) > 0) { + if (HDR_HAS_L2HDR(hdr)) + l2arc_hdr_arcstats_decrement_state(hdr); arc_hdr_clear_flags(hdr, ARC_FLAG_PREFETCH | ARC_FLAG_PRESCIENT_PREFETCH); + if (HDR_HAS_L2HDR(hdr)) + l2arc_hdr_arcstats_increment_state(hdr); } DTRACE_PROBE1(new_state__mru, arc_buf_hdr_t *, hdr); } else { @@ -5420,8 +5557,6 @@ arc_read_done(zio_t *zio) } arc_hdr_clear_flags(hdr, ARC_FLAG_L2_EVICTED); - if (l2arc_noprefetch && HDR_PREFETCH(hdr)) - arc_hdr_clear_flags(hdr, ARC_FLAG_L2CACHE); callback_list = hdr->b_l1hdr.b_acb; ASSERT3P(callback_list, !=, NULL); @@ -5747,8 +5882,12 @@ top: ASSERT((zio_flags & ZIO_FLAG_SPECULATIVE) || rc != EACCES); } else if (*arc_flags & ARC_FLAG_PREFETCH && - zfs_refcount_count(&hdr->b_l1hdr.b_refcnt) == 0) { + zfs_refcount_is_zero(&hdr->b_l1hdr.b_refcnt)) { + if (HDR_HAS_L2HDR(hdr)) + l2arc_hdr_arcstats_decrement_state(hdr); arc_hdr_set_flags(hdr, ARC_FLAG_PREFETCH); + if (HDR_HAS_L2HDR(hdr)) + l2arc_hdr_arcstats_increment_state(hdr); } DTRACE_PROBE1(arc__hit, arc_buf_hdr_t *, hdr); arc_access(hdr, hash_lock); @@ -5871,8 +6010,13 @@ top: } if (*arc_flags & ARC_FLAG_PREFETCH && - zfs_refcount_is_zero(&hdr->b_l1hdr.b_refcnt)) + zfs_refcount_is_zero(&hdr->b_l1hdr.b_refcnt)) { + if (HDR_HAS_L2HDR(hdr)) + l2arc_hdr_arcstats_decrement_state(hdr); arc_hdr_set_flags(hdr, ARC_FLAG_PREFETCH); + if (HDR_HAS_L2HDR(hdr)) + l2arc_hdr_arcstats_increment_state(hdr); + } if (*arc_flags & ARC_FLAG_PRESCIENT_PREFETCH) arc_hdr_set_flags(hdr, ARC_FLAG_PRESCIENT_PREFETCH); @@ -5942,7 +6086,7 @@ top: * 3. This buffer isn't currently writing to the L2ARC. * 4. The L2ARC entry wasn't evicted, which may * also have invalidated the vdev. - * 5. This isn't prefetch and l2arc_noprefetch is set. + * 5. This isn't prefetch or l2arc_noprefetch is 0. */ if (HDR_HAS_L2HDR(hdr) && !HDR_L2_WRITING(hdr) && !HDR_L2_EVICTED(hdr) && @@ -5961,6 +6105,17 @@ top: cb->l2rcb_zb = *zb; cb->l2rcb_flags = zio_flags; + /* + * When Compressed ARC is disabled, but the + * L2ARC block is compressed, arc_hdr_size() + * will have returned LSIZE rather than PSIZE. + */ + if (HDR_GET_COMPRESS(hdr) != ZIO_COMPRESS_OFF && + !HDR_COMPRESSION_ENABLED(hdr) && + HDR_GET_PSIZE(hdr) != 0) { + size = HDR_GET_PSIZE(hdr); + } + asize = vdev_psize_to_asize(vd, size); if (asize != size) { abd = abd_alloc_for_io(asize, @@ -6980,6 +7135,13 @@ arc_state_init(void) aggsum_init(&astat_hdr_size, 0); aggsum_init(&astat_other_size, 0); aggsum_init(&astat_l2_hdr_size, 0); + + arc_anon->arcs_state = ARC_STATE_ANON; + arc_mru->arcs_state = ARC_STATE_MRU; + arc_mru_ghost->arcs_state = ARC_STATE_MRU_GHOST; + arc_mfu->arcs_state = ARC_STATE_MFU; + arc_mfu_ghost->arcs_state = ARC_STATE_MFU_GHOST; + arc_l2c_only->arcs_state = ARC_STATE_L2C_ONLY; } static void @@ -7456,9 +7618,11 @@ l2arc_write_eligible(uint64_t spa_guid, arc_buf_hdr_t *hdr) * 2. is already cached on the L2ARC. * 3. has an I/O in progress (it may be an incomplete read). * 4. is flagged not eligible (zfs property). + * 5. is a prefetch and l2arc_noprefetch is set. */ if (hdr->b_spa != spa_guid || HDR_HAS_L2HDR(hdr) || - HDR_IO_IN_PROGRESS(hdr) || !HDR_L2CACHE(hdr)) + HDR_IO_IN_PROGRESS(hdr) || !HDR_L2CACHE(hdr) || + (l2arc_noprefetch && HDR_PREFETCH(hdr))) return (B_FALSE); return (B_TRUE); @@ -7642,9 +7806,6 @@ l2arc_write_done(zio_t *zio) DTRACE_PROBE2(l2arc__iodone, zio_t *, zio, l2arc_write_callback_t *, cb); - if (zio->io_error != 0) - ARCSTAT_BUMP(arcstat_l2_writes_error); - /* * All writes completed, or an error was hit. */ @@ -7704,8 +7865,7 @@ top: arc_hdr_clear_flags(hdr, ARC_FLAG_HAS_L2HDR); uint64_t psize = HDR_GET_PSIZE(hdr); - ARCSTAT_INCR(arcstat_l2_psize, -psize); - ARCSTAT_INCR(arcstat_l2_lsize, -HDR_GET_LSIZE(hdr)); + l2arc_hdr_arcstats_decrement(hdr); bytes_dropped += vdev_psize_to_asize(dev->l2ad_vdev, psize); @@ -7753,6 +7913,8 @@ top: list_destroy(&cb->l2wcb_abd_list); if (zio->io_error != 0) { + ARCSTAT_BUMP(arcstat_l2_writes_error); + /* * Restore the lbps array in the header to its previous state. * If the list of log block pointers is empty, zero out the @@ -8045,7 +8207,7 @@ l2arc_sublist_lock(int list_num) multilist_t *ml = NULL; unsigned int idx; - ASSERT(list_num >= 0 && list_num <= 3); + ASSERT(list_num >= 0 && list_num < L2ARC_FEED_TYPES); switch (list_num) { case 0: @@ -8060,6 +8222,8 @@ l2arc_sublist_lock(int list_num) case 3: ml = arc_mru->arcs_list[ARC_BUFC_DATA]; break; + default: + return (NULL); } /* @@ -8447,7 +8611,16 @@ l2arc_write_buffers(spa_t *spa, l2arc_dev_t *dev, uint64_t target_sz) /* * Copy buffers for L2ARC writing. */ - for (int try = 0; try <= 3; try++) { + for (int try = 0; try < L2ARC_FEED_TYPES; try++) { + /* + * If try == 1 or 3, we cache MRU metadata and data + * respectively. + */ + if (l2arc_mfuonly) { + if (try == 1 || try == 3) + continue; + } + multilist_sublist_t *mls = l2arc_sublist_lock(try); uint64_t passed_sz = 0; @@ -8599,6 +8772,8 @@ l2arc_write_buffers(spa_t *spa, l2arc_dev_t *dev, uint64_t target_sz) hdr->b_l2hdr.b_dev = dev; hdr->b_l2hdr.b_daddr = dev->l2ad_hand; + hdr->b_l2hdr.b_arcs_state = + hdr->b_l1hdr.b_state->arcs_state; arc_hdr_set_flags(hdr, ARC_FLAG_L2_WRITING | ARC_FLAG_HAS_L2HDR); @@ -8622,6 +8797,7 @@ l2arc_write_buffers(spa_t *spa, l2arc_dev_t *dev, uint64_t target_sz) write_psize += psize; write_asize += asize; dev->l2ad_hand += asize; + l2arc_hdr_arcstats_increment(hdr); vdev_space_update(dev->l2ad_vdev, asize, 0, 0); mutex_exit(hash_lock); @@ -8665,8 +8841,6 @@ l2arc_write_buffers(spa_t *spa, l2arc_dev_t *dev, uint64_t target_sz) ASSERT3U(write_asize, <=, target_sz); ARCSTAT_BUMP(arcstat_l2_writes_sent); ARCSTAT_INCR(arcstat_l2_write_bytes, write_psize); - ARCSTAT_INCR(arcstat_l2_lsize, write_lsize); - ARCSTAT_INCR(arcstat_l2_psize, write_psize); dev->l2ad_writing = B_TRUE; (void) zio_wait(pio); @@ -8682,6 +8856,15 @@ l2arc_write_buffers(spa_t *spa, l2arc_dev_t *dev, uint64_t target_sz) return (write_asize); } +static boolean_t +l2arc_hdr_limit_reached(void) +{ + int64_t s = aggsum_upper_bound(&astat_l2_hdr_size); + + return (arc_reclaim_needed() || (s > arc_meta_limit * 3 / 4) || + (s > (arc_warm ? arc_c : arc_c_max) * l2arc_meta_percent / 100)); +} + /* * This thread feeds the L2ARC at regular intervals. This is the beating * heart of the L2ARC. @@ -8747,7 +8930,7 @@ l2arc_feed_thread(void *unused) /* * Avoid contributing to memory pressure. */ - if (arc_reclaim_needed()) { + if (l2arc_hdr_limit_reached()) { ARCSTAT_BUMP(arcstat_l2_abort_lowmem); spa_config_exit(spa, SCL_L2ARC, dev); continue; @@ -8877,8 +9060,6 @@ l2arc_rebuild_vdev(vdev_t *vd, boolean_t reopen) l2arc_dev_hdr_phys_t *l2dhdr; uint64_t l2dhdr_asize; spa_t *spa; - int err; - boolean_t l2dhdr_valid = B_TRUE; dev = l2arc_vdev_get(vd); ASSERT3P(dev, !=, NULL); @@ -8907,10 +9088,7 @@ l2arc_rebuild_vdev(vdev_t *vd, boolean_t reopen) /* * Read the device header, if an error is returned do not rebuild L2ARC. */ - if ((err = l2arc_dev_hdr_read(dev)) != 0) - l2dhdr_valid = B_FALSE; - - if (l2dhdr_valid && dev->l2ad_log_entries > 0) { + if (l2arc_dev_hdr_read(dev) == 0 && dev->l2ad_log_entries > 0) { /* * If we are onlining a cache device (vdev_reopen) that was * still present (l2arc_vdev_present()) and rebuild is enabled, @@ -9187,7 +9365,7 @@ l2arc_rebuild(l2arc_dev_t *dev) * online the L2ARC dev at a later time (or re-import the pool) * to reconstruct it (when there's less memory pressure). */ - if (arc_reclaim_needed()) { + if (l2arc_hdr_limit_reached()) { ARCSTAT_BUMP(arcstat_l2_rebuild_abort_lowmem); cmn_err(CE_NOTE, "System running low on memory, " "aborting L2ARC rebuild."); @@ -9204,7 +9382,7 @@ l2arc_rebuild(l2arc_dev_t *dev) * L2BLK_GET_PSIZE returns aligned size for log blocks. */ uint64_t asize = L2BLK_GET_PSIZE((&lbps[0])->lbp_prop); - l2arc_log_blk_restore(dev, this_lb, asize, lbps[0].lbp_daddr); + l2arc_log_blk_restore(dev, this_lb, asize); /* * log block restored, include its pointer in the list of @@ -9286,7 +9464,7 @@ l2arc_rebuild(l2arc_dev_t *dev) PTR_SWAP(this_lb, next_lb); this_io = next_io; next_io = NULL; - } + } if (this_io != NULL) l2arc_log_blk_fetch_abort(this_io); @@ -9314,6 +9492,13 @@ out: "no valid log blocks"); bzero(l2dhdr, dev->l2ad_dev_hdr_asize); l2arc_dev_hdr_update(dev); + } else if (err == ECANCELED) { + /* + * In case the rebuild was canceled do not log to spa history + * log as the pool may be in the process of being removed. + */ + zfs_dbgmsg("L2ARC rebuild aborted, restored %llu blocks", + zfs_refcount_count(&dev->l2ad_lb_count)); } else if (err != 0) { spa_history_log_internal(spa, "L2ARC rebuild", NULL, "aborted, restored %llu blocks", @@ -9346,7 +9531,7 @@ l2arc_dev_hdr_read(l2arc_dev_t *dev) err = zio_wait(zio_read_phys(NULL, dev->l2ad_vdev, VDEV_LABEL_START_SIZE, l2dhdr_asize, abd, - ZIO_CHECKSUM_LABEL, NULL, NULL, ZIO_PRIORITY_ASYNC_READ, + ZIO_CHECKSUM_LABEL, NULL, NULL, ZIO_PRIORITY_SYNC_READ, ZIO_FLAG_DONT_CACHE | ZIO_FLAG_CANFAIL | ZIO_FLAG_DONT_PROPAGATE | ZIO_FLAG_DONT_RETRY | ZIO_FLAG_SPECULATIVE, B_FALSE)); @@ -9515,11 +9700,18 @@ cleanup: */ static void l2arc_log_blk_restore(l2arc_dev_t *dev, const l2arc_log_blk_phys_t *lb, - uint64_t lb_asize, uint64_t lb_daddr) + uint64_t lb_asize) { uint64_t size = 0, asize = 0; uint64_t log_entries = dev->l2ad_log_entries; + /* + * Usually arc_adapt() is called only for data, not headers, but + * since we may allocate significant amount of memory here, let ARC + * grow its arc_c. + */ + arc_adapt(log_entries * HDR_L2ONLY_SIZE, arc_l2c_only); + for (int i = log_entries - 1; i >= 0; i--) { /* * Restore goes in the reverse temporal direction to preserve @@ -9582,19 +9774,18 @@ l2arc_hdr_restore(const l2arc_log_ent_phys_t *le, l2arc_dev_t *dev) L2BLK_GET_PSIZE((le)->le_prop), le->le_birth, L2BLK_GET_COMPRESS((le)->le_prop), L2BLK_GET_PROTECTED((le)->le_prop), - L2BLK_GET_PREFETCH((le)->le_prop)); + L2BLK_GET_PREFETCH((le)->le_prop), + L2BLK_GET_STATE((le)->le_prop)); asize = vdev_psize_to_asize(dev->l2ad_vdev, L2BLK_GET_PSIZE((le)->le_prop)); /* * vdev_space_update() has to be called before arc_hdr_destroy() to - * avoid underflow since the latter also calls the former. + * avoid underflow since the latter also calls vdev_space_update(). */ + l2arc_hdr_arcstats_increment(hdr); vdev_space_update(dev->l2ad_vdev, asize, 0, 0); - ARCSTAT_INCR(arcstat_l2_lsize, HDR_GET_LSIZE(hdr)); - ARCSTAT_INCR(arcstat_l2_psize, HDR_GET_PSIZE(hdr)); - mutex_enter(&dev->l2ad_mtx); list_insert_tail(&dev->l2ad_buflist, hdr); (void) zfs_refcount_add_many(&dev->l2ad_alloc, arc_hdr_size(hdr), hdr); @@ -9614,14 +9805,15 @@ l2arc_hdr_restore(const l2arc_log_ent_phys_t *le, l2arc_dev_t *dev) arc_hdr_set_flags(exists, ARC_FLAG_HAS_L2HDR); exists->b_l2hdr.b_dev = dev; exists->b_l2hdr.b_daddr = le->le_daddr; + exists->b_l2hdr.b_arcs_state = + L2BLK_GET_STATE((le)->le_prop); mutex_enter(&dev->l2ad_mtx); list_insert_tail(&dev->l2ad_buflist, exists); (void) zfs_refcount_add_many(&dev->l2ad_alloc, arc_hdr_size(exists), exists); mutex_exit(&dev->l2ad_mtx); + l2arc_hdr_arcstats_increment(exists); vdev_space_update(dev->l2ad_vdev, asize, 0, 0); - ARCSTAT_INCR(arcstat_l2_lsize, HDR_GET_LSIZE(exists)); - ARCSTAT_INCR(arcstat_l2_psize, HDR_GET_PSIZE(exists)); } ARCSTAT_BUMP(arcstat_l2_rebuild_bufs_precached); } @@ -9915,6 +10107,7 @@ l2arc_log_blk_insert(l2arc_dev_t *dev, const arc_buf_hdr_t *hdr) L2BLK_SET_TYPE((le)->le_prop, hdr->b_type); L2BLK_SET_PROTECTED((le)->le_prop, !!(HDR_PROTECTED(hdr))); L2BLK_SET_PREFETCH((le)->le_prop, !!(HDR_PREFETCH(hdr))); + L2BLK_SET_STATE((le)->le_prop, hdr->b_l1hdr.b_state->arcs_state); dev->l2ad_log_blk_payload_asize += vdev_psize_to_asize(dev->l2ad_vdev, HDR_GET_PSIZE(hdr)); diff --git a/usr/src/uts/common/fs/zfs/dbuf.c b/usr/src/uts/common/fs/zfs/dbuf.c index a8569e9a88..3d1cf0c53a 100644 --- a/usr/src/uts/common/fs/zfs/dbuf.c +++ b/usr/src/uts/common/fs/zfs/dbuf.c @@ -2609,8 +2609,29 @@ typedef struct dbuf_prefetch_arg { zio_priority_t dpa_prio; /* The priority I/Os should be issued at. */ zio_t *dpa_zio; /* The parent zio_t for all prefetches. */ arc_flags_t dpa_aflags; /* Flags to pass to the final prefetch. */ + dbuf_prefetch_fn dpa_cb; /* prefetch completion callback */ + void *dpa_arg; /* prefetch completion arg */ } dbuf_prefetch_arg_t; +static void +dbuf_prefetch_fini(dbuf_prefetch_arg_t *dpa, boolean_t io_done) +{ + if (dpa->dpa_cb != NULL) + dpa->dpa_cb(dpa->dpa_arg, io_done); + kmem_free(dpa, sizeof (*dpa)); +} + +static void +dbuf_issue_final_prefetch_done(zio_t *zio, const zbookmark_phys_t *zb, + const blkptr_t *iobp, arc_buf_t *abuf, void *private) +{ + dbuf_prefetch_arg_t *dpa = private; + + dbuf_prefetch_fini(dpa, B_TRUE); + if (abuf != NULL) + arc_buf_destroy(abuf, private); +} + /* * Actually issue the prefetch read for the block given. */ @@ -2618,7 +2639,7 @@ static void dbuf_issue_final_prefetch(dbuf_prefetch_arg_t *dpa, blkptr_t *bp) { if (BP_IS_HOLE(bp) || BP_IS_EMBEDDED(bp)) - return; + return (dbuf_prefetch_fini(dpa, B_FALSE)); int zio_flags = ZIO_FLAG_CANFAIL | ZIO_FLAG_SPECULATIVE; arc_flags_t aflags = @@ -2632,7 +2653,8 @@ dbuf_issue_final_prefetch(dbuf_prefetch_arg_t *dpa, blkptr_t *bp) ASSERT3U(dpa->dpa_curlevel, ==, BP_GET_LEVEL(bp)); ASSERT3U(dpa->dpa_curlevel, ==, dpa->dpa_zb.zb_level); ASSERT(dpa->dpa_zio != NULL); - (void) arc_read(dpa->dpa_zio, dpa->dpa_spa, bp, NULL, NULL, + (void) arc_read(dpa->dpa_zio, dpa->dpa_spa, bp, + dbuf_issue_final_prefetch_done, dpa, dpa->dpa_prio, zio_flags, &aflags, &dpa->dpa_zb); } @@ -2653,8 +2675,7 @@ dbuf_prefetch_indirect_done(zio_t *zio, const zbookmark_phys_t *zb, if (abuf == NULL) { ASSERT(zio == NULL || zio->io_error != 0); - kmem_free(dpa, sizeof (*dpa)); - return; + return (dbuf_prefetch_fini(dpa, B_TRUE)); } ASSERT(zio == NULL || zio->io_error == 0); @@ -2685,6 +2706,10 @@ dbuf_prefetch_indirect_done(zio_t *zio, const zbookmark_phys_t *zb, dpa->dpa_zb.zb_level)); dmu_buf_impl_t *db = dbuf_hold_level(dpa->dpa_dnode, dpa->dpa_curlevel, curblkid, FTAG); + if (db == NULL) { + arc_buf_destroy(abuf, private); + return (dbuf_prefetch_fini(dpa, B_TRUE)); + } (void) dbuf_read(db, NULL, DB_RF_MUST_SUCCEED | DB_RF_NOPREFETCH | DB_RF_HAVESTRUCT); dbuf_rele(db, FTAG); @@ -2697,11 +2722,10 @@ dbuf_prefetch_indirect_done(zio_t *zio, const zbookmark_phys_t *zb, P2PHASE(nextblkid, 1ULL << dpa->dpa_epbs); if (BP_IS_HOLE(bp)) { - kmem_free(dpa, sizeof (*dpa)); + dbuf_prefetch_fini(dpa, B_TRUE); } else if (dpa->dpa_curlevel == dpa->dpa_zb.zb_level) { ASSERT3U(nextblkid, ==, dpa->dpa_zb.zb_blkid); dbuf_issue_final_prefetch(dpa, bp); - kmem_free(dpa, sizeof (*dpa)); } else { arc_flags_t iter_aflags = ARC_FLAG_NOWAIT; zbookmark_phys_t zb; @@ -2731,9 +2755,10 @@ dbuf_prefetch_indirect_done(zio_t *zio, const zbookmark_phys_t *zb, * complete. Note that the prefetch might fail if the dataset is encrypted and * the encryption key is unmapped before the IO completes. */ -void -dbuf_prefetch(dnode_t *dn, int64_t level, uint64_t blkid, zio_priority_t prio, - arc_flags_t aflags) +int +dbuf_prefetch_impl(dnode_t *dn, int64_t level, uint64_t blkid, + zio_priority_t prio, arc_flags_t aflags, dbuf_prefetch_fn cb, + void *arg) { blkptr_t bp; int epbs, nlevels, curlevel; @@ -2743,10 +2768,10 @@ dbuf_prefetch(dnode_t *dn, int64_t level, uint64_t blkid, zio_priority_t prio, ASSERT(RW_LOCK_HELD(&dn->dn_struct_rwlock)); if (blkid > dn->dn_maxblkid) - return; + goto no_issue; if (level == 0 && dnode_block_freed(dn, blkid)) - return; + goto no_issue; /* * This dnode hasn't been written to disk yet, so there's nothing to @@ -2754,11 +2779,11 @@ dbuf_prefetch(dnode_t *dn, int64_t level, uint64_t blkid, zio_priority_t prio, */ nlevels = dn->dn_phys->dn_nlevels; if (level >= nlevels || dn->dn_phys->dn_nblkptr == 0) - return; + goto no_issue; epbs = dn->dn_phys->dn_indblkshift - SPA_BLKPTRSHIFT; if (dn->dn_phys->dn_maxblkid < blkid << (epbs * level)) - return; + goto no_issue; dmu_buf_impl_t *db = dbuf_find(dn->dn_objset, dn->dn_object, level, blkid); @@ -2768,7 +2793,7 @@ dbuf_prefetch(dnode_t *dn, int64_t level, uint64_t blkid, zio_priority_t prio, * This dbuf already exists. It is either CACHED, or * (we assume) about to be read or filled. */ - return; + goto no_issue; } /* @@ -2801,7 +2826,7 @@ dbuf_prefetch(dnode_t *dn, int64_t level, uint64_t blkid, zio_priority_t prio, bp = dn->dn_phys->dn_blkptr[curblkid]; } if (BP_IS_HOLE(&bp)) - return; + goto no_issue; ASSERT3U(curlevel, ==, BP_GET_LEVEL(&bp)); @@ -2819,6 +2844,8 @@ dbuf_prefetch(dnode_t *dn, int64_t level, uint64_t blkid, zio_priority_t prio, dpa->dpa_dnode = dn; dpa->dpa_epbs = epbs; dpa->dpa_zio = pio; + dpa->dpa_cb = cb; + dpa->dpa_arg = arg; /* flag if L2ARC eligible, l2arc_noprefetch then decides */ if (DNODE_LEVEL_IS_L2CACHEABLE(dn, level)) @@ -2834,7 +2861,6 @@ dbuf_prefetch(dnode_t *dn, int64_t level, uint64_t blkid, zio_priority_t prio, if (curlevel == level) { ASSERT3U(curblkid, ==, blkid); dbuf_issue_final_prefetch(dpa, &bp); - kmem_free(dpa, sizeof (*dpa)); } else { arc_flags_t iter_aflags = ARC_FLAG_NOWAIT; zbookmark_phys_t zb; @@ -2855,6 +2881,19 @@ dbuf_prefetch(dnode_t *dn, int64_t level, uint64_t blkid, zio_priority_t prio, * dpa may have already been freed. */ zio_nowait(pio); + return (1); +no_issue: + if (cb != NULL) + cb(arg, B_FALSE); + return (0); +} + +int +dbuf_prefetch(dnode_t *dn, int64_t level, uint64_t blkid, zio_priority_t prio, + arc_flags_t aflags) +{ + + return (dbuf_prefetch_impl(dn, level, blkid, prio, aflags, NULL, NULL)); } /* diff --git a/usr/src/uts/common/fs/zfs/dmu_zfetch.c b/usr/src/uts/common/fs/zfs/dmu_zfetch.c index 60e0f36a5e..8c3799d8ba 100644 --- a/usr/src/uts/common/fs/zfs/dmu_zfetch.c +++ b/usr/src/uts/common/fs/zfs/dmu_zfetch.c @@ -58,16 +58,29 @@ typedef struct zfetch_stats { kstat_named_t zfetchstat_hits; kstat_named_t zfetchstat_misses; kstat_named_t zfetchstat_max_streams; + kstat_named_t zfetchstat_max_completion_us; + kstat_named_t zfetchstat_last_completion_us; + kstat_named_t zfetchstat_io_issued; } zfetch_stats_t; static zfetch_stats_t zfetch_stats = { { "hits", KSTAT_DATA_UINT64 }, { "misses", KSTAT_DATA_UINT64 }, { "max_streams", KSTAT_DATA_UINT64 }, + { "max_completion_us", KSTAT_DATA_UINT64 }, + { "last_completion_us", KSTAT_DATA_UINT64 }, + { "io_issued", KSTAT_DATA_UINT64 }, }; #define ZFETCHSTAT_BUMP(stat) \ - atomic_inc_64(&zfetch_stats.stat.value.ui64); + atomic_inc_64(&zfetch_stats.stat.value.ui64) +#define ZFETCHSTAT_ADD(stat, val) \ + atomic_add_64(&zfetch_stats.stat.value.ui64, val) +#define ZFETCHSTAT_SET(stat, val) \ + zfetch_stats.stat.value.ui64 = val +#define ZFETCHSTAT_GET(stat) \ + zfetch_stats.stat.value.ui64 + kstat_t *zfetch_ksp; @@ -103,8 +116,8 @@ dmu_zfetch_init(zfetch_t *zf, dnode_t *dno) { if (zf == NULL) return; - zf->zf_dnode = dno; + zf->zf_numstreams = 0; list_create(&zf->zf_stream, sizeof (zstream_t), offsetof(zstream_t, zs_node)); @@ -113,12 +126,28 @@ dmu_zfetch_init(zfetch_t *zf, dnode_t *dno) } static void +dmu_zfetch_stream_fini(zstream_t *zs) +{ + mutex_destroy(&zs->zs_lock); + kmem_free(zs, sizeof (*zs)); +} + +static void dmu_zfetch_stream_remove(zfetch_t *zf, zstream_t *zs) { ASSERT(RW_WRITE_HELD(&zf->zf_rwlock)); list_remove(&zf->zf_stream, zs); - mutex_destroy(&zs->zs_lock); - kmem_free(zs, sizeof (*zs)); + dmu_zfetch_stream_fini(zs); + zf->zf_numstreams--; +} + +static void +dmu_zfetch_stream_orphan(zfetch_t *zf, zstream_t *zs) +{ + ASSERT(RW_WRITE_HELD(&zf->zf_rwlock)); + list_remove(&zf->zf_stream, zs); + zs->zs_fetch = NULL; + zf->zf_numstreams--; } /* @@ -134,7 +163,7 @@ dmu_zfetch_fini(zfetch_t *zf) rw_enter(&zf->zf_rwlock, RW_WRITER); while ((zs = list_head(&zf->zf_stream)) != NULL) - dmu_zfetch_stream_remove(zf, zs); + dmu_zfetch_stream_orphan(zf, zs); rw_exit(&zf->zf_rwlock); list_destroy(&zf->zf_stream); rw_destroy(&zf->zf_rwlock); @@ -152,7 +181,7 @@ static void dmu_zfetch_stream_create(zfetch_t *zf, uint64_t blkid) { zstream_t *zs_next; - int numstreams = 0; + hrtime_t now = gethrtime(); ASSERT(RW_WRITE_HELD(&zf->zf_rwlock)); @@ -162,11 +191,14 @@ dmu_zfetch_stream_create(zfetch_t *zf, uint64_t blkid) for (zstream_t *zs = list_head(&zf->zf_stream); zs != NULL; zs = zs_next) { zs_next = list_next(&zf->zf_stream, zs); - if (((gethrtime() - zs->zs_atime) / NANOSEC) > + /* + * Skip gethrtime() call if there are still references + */ + if (zfs_refcount_count(&zs->zs_blocks) != 0) + continue; + if (((now - zs->zs_atime) / NANOSEC) > zfetch_min_sec_reap) dmu_zfetch_stream_remove(zf, zs); - else - numstreams++; } /* @@ -180,7 +212,7 @@ dmu_zfetch_stream_create(zfetch_t *zf, uint64_t blkid) uint32_t max_streams = MAX(1, MIN(zfetch_max_streams, zf->zf_dnode->dn_maxblkid * zf->zf_dnode->dn_datablksz / zfetch_max_distance)); - if (numstreams >= max_streams) { + if (zf->zf_numstreams >= max_streams) { ZFETCHSTAT_BUMP(zfetchstat_max_streams); return; } @@ -189,12 +221,39 @@ dmu_zfetch_stream_create(zfetch_t *zf, uint64_t blkid) zs->zs_blkid = blkid; zs->zs_pf_blkid = blkid; zs->zs_ipf_blkid = blkid; - zs->zs_atime = gethrtime(); + zs->zs_atime = now; + zs->zs_fetch = zf; + zfs_refcount_create(&zs->zs_blocks); mutex_init(&zs->zs_lock, NULL, MUTEX_DEFAULT, NULL); - + zf->zf_numstreams++; list_insert_head(&zf->zf_stream, zs); } +static void +dmu_zfetch_stream_done(void *arg, boolean_t io_issued) +{ + zstream_t *zs = arg; + + if (zs->zs_start_time && io_issued) { + hrtime_t now = gethrtime(); + hrtime_t delta = NSEC2USEC(now - zs->zs_start_time); + + zs->zs_start_time = 0; + ZFETCHSTAT_SET(zfetchstat_last_completion_us, delta); + if (delta > ZFETCHSTAT_GET(zfetchstat_max_completion_us)) + ZFETCHSTAT_SET(zfetchstat_max_completion_us, delta); + } + + if (zfs_refcount_remove(&zs->zs_blocks, NULL) != 0) + return; + + /* + * The parent fetch structure has gone away + */ + if (zs->zs_fetch == NULL) + dmu_zfetch_stream_fini(zs); +} + /* * This is the predictive prefetch entry point. It associates dnode access * specified with blkid and nblks arguments with prefetch stream, predicts @@ -210,7 +269,7 @@ dmu_zfetch(zfetch_t *zf, uint64_t blkid, uint64_t nblks, boolean_t fetch_data, zstream_t *zs; int64_t pf_start, ipf_start, ipf_istart, ipf_iend; int64_t pf_ahead_blks, max_blks; - int epbs, max_dist_blks, pf_nblks, ipf_nblks; + int epbs, max_dist_blks, pf_nblks, ipf_nblks, issued; uint64_t end_of_access_blkid = blkid + nblks; spa_t *spa = zf->zf_dnode->dn_objset->os_spa; @@ -231,12 +290,22 @@ dmu_zfetch(zfetch_t *zf, uint64_t blkid, uint64_t nblks, boolean_t fetch_data, * As a fast path for small (single-block) files, ignore access * to the first block. */ - if (blkid == 0) + if (!have_lock && blkid == 0) return; if (!have_lock) rw_enter(&zf->zf_dnode->dn_struct_rwlock, RW_READER); + + /* + * A fast path for small files for which no prefetch will + * happen. + */ + if (zf->zf_dnode->dn_maxblkid < 2) { + if (!have_lock) + rw_exit(&zf->zf_dnode->dn_struct_rwlock); + return; + } rw_enter(&zf->zf_rwlock, RW_READER); /* @@ -345,9 +414,15 @@ dmu_zfetch(zfetch_t *zf, uint64_t blkid, uint64_t nblks, boolean_t fetch_data, ipf_iend = P2ROUNDUP(zs->zs_ipf_blkid, 1 << epbs) >> epbs; zs->zs_atime = gethrtime(); + /* no prior reads in progress */ + if (zfs_refcount_count(&zs->zs_blocks) == 0) + zs->zs_start_time = zs->zs_atime; zs->zs_blkid = end_of_access_blkid; + zfs_refcount_add_many(&zs->zs_blocks, pf_nblks + ipf_iend - ipf_istart, + NULL); mutex_exit(&zs->zs_lock); rw_exit(&zf->zf_rwlock); + issued = 0; /* * dbuf_prefetch() is asynchronous (even when it needs to read @@ -356,14 +431,19 @@ dmu_zfetch(zfetch_t *zf, uint64_t blkid, uint64_t nblks, boolean_t fetch_data, */ for (int i = 0; i < pf_nblks; i++) { - dbuf_prefetch(zf->zf_dnode, 0, pf_start + i, - ZIO_PRIORITY_ASYNC_READ, ARC_FLAG_PREDICTIVE_PREFETCH); + issued += dbuf_prefetch_impl(zf->zf_dnode, 0, pf_start + i, + ZIO_PRIORITY_ASYNC_READ, ARC_FLAG_PREDICTIVE_PREFETCH, + dmu_zfetch_stream_done, zs); } for (int64_t iblk = ipf_istart; iblk < ipf_iend; iblk++) { - dbuf_prefetch(zf->zf_dnode, 1, iblk, - ZIO_PRIORITY_ASYNC_READ, ARC_FLAG_PREDICTIVE_PREFETCH); + issued += dbuf_prefetch_impl(zf->zf_dnode, 1, iblk, + ZIO_PRIORITY_ASYNC_READ, ARC_FLAG_PREDICTIVE_PREFETCH, + dmu_zfetch_stream_done, zs); } if (!have_lock) rw_exit(&zf->zf_dnode->dn_struct_rwlock); ZFETCHSTAT_BUMP(zfetchstat_hits); + + if (issued) + ZFETCHSTAT_ADD(zfetchstat_io_issued, issued); } diff --git a/usr/src/uts/common/fs/zfs/sys/arc.h b/usr/src/uts/common/fs/zfs/sys/arc.h index ddcbfa748d..e5c18febe5 100644 --- a/usr/src/uts/common/fs/zfs/sys/arc.h +++ b/usr/src/uts/common/fs/zfs/sys/arc.h @@ -179,6 +179,16 @@ typedef enum arc_space_type { ARC_SPACE_NUMTYPES } arc_space_type_t; +typedef enum arc_state_type { + ARC_STATE_ANON, + ARC_STATE_MRU, + ARC_STATE_MRU_GHOST, + ARC_STATE_MFU, + ARC_STATE_MFU_GHOST, + ARC_STATE_L2C_ONLY, + ARC_STATE_NUMTYPES +} arc_state_type_t; + void arc_space_consume(uint64_t space, arc_space_type_t type); void arc_space_return(uint64_t space, arc_space_type_t type); boolean_t arc_is_metadata(arc_buf_t *buf); diff --git a/usr/src/uts/common/fs/zfs/sys/arc_impl.h b/usr/src/uts/common/fs/zfs/sys/arc_impl.h index 0c18849b59..d35b7eea2d 100644 --- a/usr/src/uts/common/fs/zfs/sys/arc_impl.h +++ b/usr/src/uts/common/fs/zfs/sys/arc_impl.h @@ -83,6 +83,8 @@ typedef struct arc_state { * non-evictable, ARC_BUFC_DATA, and ARC_BUFC_METADATA. */ zfs_refcount_t arcs_size; + + arc_state_type_t arcs_state; } arc_state_t; typedef struct arc_callback arc_callback_t; @@ -338,6 +340,8 @@ typedef struct l2arc_lb_ptr_buf { #define L2BLK_SET_TYPE(field, x) BF64_SET((field), 48, 8, x) #define L2BLK_GET_PROTECTED(field) BF64_GET((field), 56, 1) #define L2BLK_SET_PROTECTED(field, x) BF64_SET((field), 56, 1, x) +#define L2BLK_GET_STATE(field) BF64_GET((field), 57, 4) +#define L2BLK_SET_STATE(field, x) BF64_SET((field), 57, 4, x) #define PTR_SWAP(x, y) \ do { \ @@ -432,6 +436,7 @@ typedef struct l2arc_buf_hdr { l2arc_dev_t *b_dev; /* L2ARC device */ uint64_t b_daddr; /* disk address, offset byte */ + arc_state_type_t b_arcs_state; list_node_t b_l2node; } l2arc_buf_hdr_t; @@ -529,6 +534,8 @@ typedef struct arc_stats { kstat_named_t arcstat_evict_not_enough; kstat_named_t arcstat_evict_l2_cached; kstat_named_t arcstat_evict_l2_eligible; + kstat_named_t arcstat_evict_l2_eligible_mfu; + kstat_named_t arcstat_evict_l2_eligible_mru; kstat_named_t arcstat_evict_l2_ineligible; kstat_named_t arcstat_evict_l2_skip; kstat_named_t arcstat_hash_elements; @@ -714,6 +721,18 @@ typedef struct arc_stats { kstat_named_t arcstat_mfu_ghost_evictable_metadata; kstat_named_t arcstat_l2_hits; kstat_named_t arcstat_l2_misses; + /* + * Allocated size (in bytes) of L2ARC cached buffers by ARC state. + */ + kstat_named_t arcstat_l2_prefetch_asize; + kstat_named_t arcstat_l2_mru_asize; + kstat_named_t arcstat_l2_mfu_asize; + /* + * Allocated size (in bytes) of L2ARC cached buffers by buffer content + * type. + */ + kstat_named_t arcstat_l2_bufc_data_asize; + kstat_named_t arcstat_l2_bufc_metadata_asize; kstat_named_t arcstat_l2_feeds; kstat_named_t arcstat_l2_rw_clash; kstat_named_t arcstat_l2_read_bytes; diff --git a/usr/src/uts/common/fs/zfs/sys/dbuf.h b/usr/src/uts/common/fs/zfs/sys/dbuf.h index 7482006eb1..e543f6ac09 100644 --- a/usr/src/uts/common/fs/zfs/sys/dbuf.h +++ b/usr/src/uts/common/fs/zfs/sys/dbuf.h @@ -308,6 +308,8 @@ typedef struct dbuf_hash_table { kmutex_t hash_mutexes[DBUF_MUTEXES]; } dbuf_hash_table_t; +typedef void (*dbuf_prefetch_fn)(void *, boolean_t); + uint64_t dbuf_whichblock(struct dnode *di, int64_t level, uint64_t offset); dmu_buf_impl_t *dbuf_create_tlib(struct dnode *dn, char *data); @@ -324,7 +326,10 @@ int dbuf_hold_impl(struct dnode *dn, uint8_t level, uint64_t blkid, boolean_t fail_sparse, boolean_t fail_uncached, void *tag, dmu_buf_impl_t **dbp); -void dbuf_prefetch(struct dnode *dn, int64_t level, uint64_t blkid, +int dbuf_prefetch_impl(struct dnode *dn, int64_t level, uint64_t blkid, + zio_priority_t prio, arc_flags_t aflags, dbuf_prefetch_fn cb, + void *arg); +int dbuf_prefetch(struct dnode *dn, int64_t level, uint64_t blkid, zio_priority_t prio, arc_flags_t aflags); void dbuf_add_ref(dmu_buf_impl_t *db, void *tag); diff --git a/usr/src/uts/common/fs/zfs/sys/dmu_zfetch.h b/usr/src/uts/common/fs/zfs/sys/dmu_zfetch.h index d426cc282b..71f76cc88b 100644 --- a/usr/src/uts/common/fs/zfs/sys/dmu_zfetch.h +++ b/usr/src/uts/common/fs/zfs/sys/dmu_zfetch.h @@ -40,6 +40,13 @@ extern uint64_t zfetch_array_rd_sz; struct dnode; /* so we can reference dnode */ +typedef struct zfetch { + krwlock_t zf_rwlock; /* protects zfetch structure */ + list_t zf_stream; /* list of zstream_t's */ + struct dnode *zf_dnode; /* dnode that owns this zfetch */ + int zf_numstreams; /* number of zstream_t's */ +} zfetch_t; + typedef struct zstream { uint64_t zs_blkid; /* expect next access at this blkid */ uint64_t zs_pf_blkid; /* next block to prefetch */ @@ -52,15 +59,12 @@ typedef struct zstream { kmutex_t zs_lock; /* protects stream */ hrtime_t zs_atime; /* time last prefetch issued */ + hrtime_t zs_start_time; /* start of last prefetch */ list_node_t zs_node; /* link for zf_stream */ + zfetch_t *zs_fetch; /* parent fetch */ + zfs_refcount_t zs_blocks; /* number of pending blocks in the stream */ } zstream_t; -typedef struct zfetch { - krwlock_t zf_rwlock; /* protects zfetch structure */ - list_t zf_stream; /* list of zstream_t's */ - struct dnode *zf_dnode; /* dnode that owns this zfetch */ -} zfetch_t; - void zfetch_init(void); void zfetch_fini(void); |
