diff --git a/.gitignore b/.gitignore index 056bbb8f08..4ec6550959 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,6 @@ venv *.so *.so.debug *.so.full + +cscope.out +*,v diff --git a/cmd/zpool/zpool_main.c b/cmd/zpool/zpool_main.c index b93a6196be..3d9b54de2b 100644 --- a/cmd/zpool/zpool_main.c +++ b/cmd/zpool/zpool_main.c @@ -129,6 +129,9 @@ static int zpool_do_wait(int, char **); static zpool_compat_status_t zpool_do_load_compat( const char *, boolean_t *); +/* fmgw - added to allow injection into zfs kernel log */ +static int zpool_do_addlog(int, char **); + /* * These libumem hooks provide a reasonable set of defaults for the allocator's * debugging facilities. @@ -162,6 +165,7 @@ typedef enum { HELP_IOSTAT, HELP_LABELCLEAR, HELP_LIST, + HELP_ADDLOG, HELP_OFFLINE, HELP_ONLINE, HELP_REPLACE, @@ -290,6 +294,7 @@ static zpool_command_t command_table[] = { { "labelclear", zpool_do_labelclear, HELP_LABELCLEAR }, { NULL }, { "checkpoint", zpool_do_checkpoint, HELP_CHECKPOINT }, + { "addlog", zpool_do_addlog, HELP_ADDLOG }, { NULL }, { "list", zpool_do_list, HELP_LIST }, { "iostat", zpool_do_iostat, HELP_IOSTAT }, @@ -380,6 +385,9 @@ get_usage(zpool_help_t idx) return (gettext("\tlist [-gHLpPv] [-o property[,...]] " "[-T d|u] [pool] ... \n" "\t [interval [count]]\n")); + case HELP_ADDLOG: + return (gettext("\taddlog [-m message]\n" + "\tmessage <= 254 characters\n")); case HELP_OFFLINE: return (gettext("\toffline [-f] [-t] ...\n")); case HELP_ONLINE: @@ -3430,6 +3438,46 @@ zpool_do_checkpoint(int argc, char **argv) return (err); } +static int zpool_addlog(char *msg) +{ + int ret; + zfs_cmd_t zc = {"\0"}; + + if (msg == NULL) + msg = (char *)"---EMPTY---"; + (void) strlcpy(zc.zc_name, msg, sizeof (zc.zc_name)); + ret = zfs_ioctl(g_zfs, ZFS_IOC_ADD_LOG, &zc); + return (ret); +} + +int zpool_do_addlog(int argc, char **argv) +{ + int c; + char *msg = NULL; + int err = 0; + + while ((c = getopt(argc, argv, "m:")) != -1) { + switch (c) { + case 'm': + msg = optarg; + break; + case ':': + (void) fprintf(stderr, + gettext("missing argument for " + "'%c' option\n"), optopt); + usage(B_FALSE); + break; + case '?': + (void) fprintf(stderr, + gettext("invalid option '%c'\n"), + optopt); + usage(B_FALSE); + } + } + err = zpool_addlog(msg); + return (err); +} + #define CHECKPOINT_OPT 1024 /* diff --git a/include/sys/jprint.h b/include/sys/jprint.h new file mode 100644 index 0000000000..e2a114ff6c --- /dev/null +++ b/include/sys/jprint.h @@ -0,0 +1,60 @@ +/* + * jprint.h + */ + +/* If in ZFS provides what is needed */ + +/* If standalone */ +/* #include */ +/* #define boolean_t bool */ +/* #define B_TRUE true */ +/* #define B_FALSE false */ +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include */ + +/* maximum stack nesting */ +#define JP_MAX_STACK 32 + +enum jp_type { + JP_OBJECT = 1, + JP_ARRAY +}; + +struct jp_stack { + enum jp_type type; + int nelem; +}; + +typedef struct jprint { + char *buffer; /* pointer to application's buffer */ + size_t buflen; /* length of buffer */ + char *bufp; /* current write position in buffer */ + char tmpbuf[32]; /* local buffer for conversions */ + int error; /* error code */ + int ncall; /* API call number on which error occurred */ + struct jp_stack /* stack of array/object nodes */ + stack[JP_MAX_STACK]; + int stackp; +} jprint_t; + +/* error return codes */ +#define JPRINT_OK 0 /* no error */ +#define JPRINT_BUF_FULL 1 /* output buffer full */ +#define JPRINT_NEST_ERROR 2 /* nesting error */ +#define JPRINT_STACK_FULL 3 /* array/object nesting */ +#define JPRINT_STACK_EMPTY 4 /* stack underflow error */ +#define JPRINT_OPEN 5 /* not all objects closed */ +#define JPRINT_FMT 6 /* format error */ +#define JPRINT_NO_DOUBLE 7 /* %g support not included */ + +const char *jp_errorstring(int err); +int jp_error(jprint_t *jp); +void jp_open(jprint_t *jp, char *buffer, size_t buflen); +int jp_close(jprint_t *jp); +int jp_errorpos(jprint_t *jp); +int jp_printf(jprint_t *jp, const char *fmt, ...); + diff --git a/include/sys/json_stats.h b/include/sys/json_stats.h new file mode 100644 index 0000000000..ae47fe91ba --- /dev/null +++ b/include/sys/json_stats.h @@ -0,0 +1,7 @@ +/* + * json_stats.h + */ + +void json_stats_destroy(spa_t *spa); +void json_stats_init(spa_t *spa); + diff --git a/include/sys/spa.h b/include/sys/spa.h index f168015abf..6513fd5b51 100644 --- a/include/sys/spa.h +++ b/include/sys/spa.h @@ -791,6 +791,9 @@ extern void spa_scan_stat_init(spa_t *spa); extern int spa_scan_get_stats(spa_t *spa, pool_scan_stat_t *ps); extern int bpobj_enqueue_alloc_cb(void *arg, const blkptr_t *bp, dmu_tx_t *tx); extern int bpobj_enqueue_free_cb(void *arg, const blkptr_t *bp, dmu_tx_t *tx); +extern void spa_add_spares(spa_t *spa, nvlist_t *config); +extern void spa_add_l2cache(spa_t *spa, nvlist_t *config); +extern void spa_add_feature_stats(spa_t *spa, nvlist_t *config); #define SPA_ASYNC_CONFIG_UPDATE 0x01 #define SPA_ASYNC_REMOVE 0x02 @@ -921,6 +924,11 @@ typedef struct spa_stats { spa_history_kstat_t iostats; } spa_stats_t; +typedef struct json_stats { + kmutex_t lock; + spa_history_kstat_t kstat; +} json_stats_t; + typedef enum txg_state { TXG_STATE_BIRTH = 0, TXG_STATE_OPEN = 1, diff --git a/include/sys/spa_impl.h b/include/sys/spa_impl.h index c8987e2e67..0ea2c1103f 100644 --- a/include/sys/spa_impl.h +++ b/include/sys/spa_impl.h @@ -406,6 +406,7 @@ struct spa { uint64_t spa_autotrim; /* automatic background trim? */ uint64_t spa_errata; /* errata issues detected */ spa_stats_t spa_stats; /* assorted spa statistics */ + json_stats_t spa_json_stats; /* json stats */ spa_keystore_t spa_keystore; /* loaded crypto keys */ /* arc_memory_throttle() parameters during low memory condition */ diff --git a/lib/libzpool/Makefile.am b/lib/libzpool/Makefile.am index c9a55591e5..a0176d1a1f 100644 --- a/lib/libzpool/Makefile.am +++ b/lib/libzpool/Makefile.am @@ -93,6 +93,8 @@ KERNEL_C = \ hkdf.c \ fm.c \ gzip.c \ + jprint.c \ + json_stats.c \ lzjb.c \ lz4.c \ metaslab.c \ diff --git a/module/Kbuild.in b/module/Kbuild.in index 1507965c57..f3915ad62e 100644 --- a/module/Kbuild.in +++ b/module/Kbuild.in @@ -43,5 +43,4 @@ endif subdir-asflags-y := $(ZFS_MODULE_CFLAGS) $(ZFS_MODULE_CPPFLAGS) subdir-ccflags-y := $(ZFS_MODULE_CFLAGS) $(ZFS_MODULE_CPPFLAGS) - endif diff --git a/module/Makefile.bsd b/module/Makefile.bsd index 6bd78690fa..02d0fd1eff 100644 --- a/module/Makefile.bsd +++ b/module/Makefile.bsd @@ -243,6 +243,8 @@ SRCS+= abd.c \ spa_log_spacemap.c \ spa_misc.c \ spa_stats.c \ + json_stats.c \ + jprint.c \ space_map.c \ space_reftree.c \ txg.c \ diff --git a/module/zfs/dsl_scan.c b/module/zfs/dsl_scan.c index d7050487ff..84db39e2e2 100644 --- a/module/zfs/dsl_scan.c +++ b/module/zfs/dsl_scan.c @@ -722,6 +722,7 @@ dsl_scan_setup_sync(void *arg, dmu_tx_t *tx) dsl_pool_t *dp = scn->scn_dp; spa_t *spa = dp->dp_spa; + ASSERT(!dsl_scan_is_running(scn)); ASSERT(*funcp > POOL_SCAN_NONE && *funcp < POOL_SCAN_FUNCS); bzero(&scn->scn_phys, sizeof (scn->scn_phys)); @@ -1963,11 +1964,13 @@ dsl_scan_visitbp(blkptr_t *bp, const zbookmark_phys_t *zb, dsl_pool_t *dp = scn->scn_dp; blkptr_t *bp_toread = NULL; - if (dsl_scan_check_suspend(scn, zb)) + if (dsl_scan_check_suspend(scn, zb)) { return; + } - if (dsl_scan_check_resume(scn, dnp, zb)) + if (dsl_scan_check_resume(scn, dnp, zb)) { return; + } scn->scn_visited_this_txg++; diff --git a/module/zfs/jprint.c b/module/zfs/jprint.c new file mode 100644 index 0000000000..7136ac0909 --- /dev/null +++ b/module/zfs/jprint.c @@ -0,0 +1,446 @@ +/* + * jprint.c + */ + +/* If in ZFS */ +#include +#include + +/* If standalone */ +/* #include "jprint.h" */ + +/* Do not support %g format. Just %d and %l for integers (if set) */ +#define NO_DOUBLE 1 + +/* Use %g instead of %e for double format */ +#define USE_G 1 + +/* Formats for int64_t and uint64_t */ +#ifndef PRId64 +#define PRId64 "lld" /* %D, int64_t */ +#define PRIu64 "llu" /* %U, uint64_t*/ +#endif + +/* literal key length maximum */ +#define KEYLEN 255 + +/* return error position (call number of jp_printf) */ +int +jp_errorpos(jprint_t *jp) +{ + return (jp->ncall); +} + +/* return string for error code */ +const char * +jp_errorstring(int err) +{ + switch (err) { + case JPRINT_OK: return "jprint ok"; + case JPRINT_BUF_FULL: return "jprint buffer full"; + case JPRINT_NEST_ERROR: return "jprint nest error"; + case JPRINT_STACK_FULL: return "jprint stack full"; + case JPRINT_STACK_EMPTY: return "jprint stack empty"; + case JPRINT_OPEN: return "jprint open"; + case JPRINT_FMT: return "jprint format"; + case JPRINT_NO_DOUBLE: return "jprint no double support"; + default: return "jprint unknown error"; + } + return "jprint unknown error"; +} + +/* return error from jprint_t */ +int +jp_error(jprint_t *jp) +{ + return (jp->error); +} + +/* open json using buffer of length buflen */ +void +jp_open(jprint_t *jp, char *buffer, size_t buflen) +{ + jp->buffer = jp->bufp = buffer; + jp->buflen = buflen; + jp->error = JPRINT_OK; + jp->ncall = 0; + jp->stackp = -1; + *buffer = '\0'; +} + + +/* close json (return out of memory error) */ +int +jp_close(jprint_t *jp) +{ + if (jp->error != JPRINT_OK) + return (jp->error); + if (jp->stackp != -1) + jp->error = JPRINT_OPEN; + return (jp->error); +} + + +/* put character to json */ +static void +jp_putc(jprint_t *jp, char c) +{ + if (jp->error == JPRINT_OK) { + if ((jp->bufp - jp->buffer + 1) >= jp->buflen) + jp->error = JPRINT_BUF_FULL; + else { + *jp->bufp++ = c; + *jp->bufp = '\0'; + } + } +} + + +/* put string to json */ +static void +jp_puts(jprint_t *jp, char *s) +{ + while (*s && (jp->error == JPRINT_OK)) + jp_putc(jp, *s++); +} + + +/* put quoted string to json */ +static void +jp_putsq(jprint_t *jp, char *s) +{ + static const char *hex = "0123456789ABCDEF"; + int c; + + if (s == NULL) { + jp_puts(jp, (char *)"null"); + return; + } + jp_putc(jp, '\"'); + while (*s && (jp->error == JPRINT_OK)) { + c = (int)*s++; + /* formfeed, newline, return, tab, backspace */ + if (c == 12) + jp_puts(jp, (char *)"\\f"); + else if (c == 10) + jp_puts(jp, (char *)"\\n"); + else if (c == 13) + jp_puts(jp, (char *)"\\r"); + else if (c == 9) + jp_puts(jp, (char *)"\\t"); + else if (c == 8) + jp_puts(jp, (char *)"\\b"); + /* + * all characters from 0x00 to 0x1f, and 0x7f are + * escaped as: \u00xx + */ + else if (((0 <= c) && (c <= 0x1f)) || (c == 0x7f)) { + jp_puts(jp, (char *)"\\u00"); + jp_putc(jp, hex[(c >> 4) & 0x0f]); + jp_putc(jp, hex[c & 0x0f]); + /* * " \ / */ + } else if (c == '"') + jp_puts(jp, (char *)"\\\""); + else if (c == '\\') + jp_puts(jp, (char *)"\\\\"); + else if (c == '/') + jp_puts(jp, (char *)"\\/"); + /* + * all other printable characters ' ' to '~', and + * any utf-8 sequences (high bit set): + * 1xxxxxxx 10xxxxxx ... + * is a utf-8 sequence (10xxxxxx may occur 1 to 3 times). + * Note that this is simply distinguished here as high + * bit set. + */ + else + jp_putc(jp, (char)c); + } + jp_putc(jp, '\"'); +} + + +/* put out key if object open. error if nothing open */ +static int +jp_key(jprint_t *jp, char *key) +{ + if (jp->error != JPRINT_OK) + goto err; + /* at top level, no frame exists yet, no error */ + if (jp->stackp == -1) + goto err; + /* stackp has been "popped" too many times */ + if (jp->stackp < -1) { + jp->error = JPRINT_STACK_EMPTY; + goto err; + } + /* put comma separator in (both object and array) */ + if (++jp->stack[jp->stackp].nelem > 1) + jp_putc(jp, ','); + /* if its in an object, put out the key and separator */ + if (jp->stack[jp->stackp].type == JP_OBJECT) { + jp_putsq(jp, key); + jp_putc(jp, ':'); + } +err: + return (jp->error); +} + + +/* printf to json */ +int +jp_printf(jprint_t *jp, const char *fmt, ...) +{ + char key[KEYLEN + 1]; + int k, i; + va_list ap; + int n; + unsigned int u; + int64_t n64; + uint64_t u64; + boolean_t b; + char *s; + char *start = jp->bufp; + + if (jp->error != JPRINT_OK) + return (-1); + ++jp->ncall; + va_start(ap, fmt); + key[k = 0] = '\0'; + while (*fmt && (jp->error == JPRINT_OK)) { + switch (*fmt) { + case '%': + ++fmt; + switch (*fmt) { + case 'k': /* next parameter is key */ + if (jp->stackp < 0) { + jp->error = JPRINT_STACK_EMPTY; + break; + } + s = va_arg(ap, char *); + if (strlen(s) <= KEYLEN) + strcpy(key, s); + else + jp->error = JPRINT_FMT; + break; + case 'd': /* next parameter is int */ + if (jp->stackp < 0) { + jp->error = JPRINT_STACK_EMPTY; + break; + } + n = va_arg(ap, int); + i = snprintf( + jp->tmpbuf, sizeof (jp->tmpbuf), + "%d", n); + if (jp_key(jp, key) == JPRINT_OK) { + if ((i >= sizeof (jp->tmpbuf)) || + (i < 0)) + jp_puts(jp, (char *)"####"); + else + jp_puts(jp, jp->tmpbuf); + } + key[k = 0] = '\0'; + break; + case 'u': /* next parameter is unsigned int */ + if (jp->stackp < 0) { + jp->error = JPRINT_STACK_EMPTY; + break; + } + u = va_arg(ap, unsigned int); + i = snprintf( + jp->tmpbuf, sizeof (jp->tmpbuf), + "%u", u); + if (jp_key(jp, key) == JPRINT_OK) { + if ((i >= sizeof (jp->tmpbuf)) || + (i < 0)) + jp_puts(jp, (char *)"####"); + else + jp_puts(jp, jp->tmpbuf); + } + key[k = 0] = '\0'; + break; + case 'U': /* next parameter is uint64_t */ + if (jp->stackp < 0) { + jp->error = JPRINT_STACK_EMPTY; + break; + } + u64 = va_arg(ap, uint64_t); + i = snprintf( + jp->tmpbuf, sizeof (jp->tmpbuf), + "%" PRIu64, u64); + if (jp_key(jp, key) == JPRINT_OK) { + if ((i >= sizeof (jp->tmpbuf)) || + (i < 0)) + jp_puts(jp, (char *)"####"); + else + jp_puts(jp, jp->tmpbuf); + } + key[k = 0] = '\0'; + break; + case 'D': /* next parameter is int64_t */ + if (jp->stackp < 0) { + jp->error = JPRINT_STACK_EMPTY; + break; + } + n64 = va_arg(ap, int64_t); + i = snprintf( + jp->tmpbuf, sizeof (jp->tmpbuf), + "%" PRId64, n64); + if (jp_key(jp, key) == JPRINT_OK) { + if ((i >= sizeof (jp->tmpbuf)) || + (i < 0)) + jp_puts(jp, (char *)"####"); + else + jp_puts(jp, jp->tmpbuf); + } + key[k = 0] = '\0'; + break; + case 's': /* next parameter is string */ + if (jp->stackp < 0) { + jp->error = JPRINT_STACK_EMPTY; + break; + } + s = va_arg(ap, char *); + if (jp_key(jp, key) == JPRINT_OK) + jp_putsq(jp, s); + key[k = 0] = '\0'; + break; + case 'g': /* next parameter is double */ +#if NO_DOUBLE + jp->error = JPRINT_NO_DOUBLE; +#else + if (jp->stackp < 0) { + jp->error = JPRINT_STACK_EMPTY; + break; + } + double x; + x = va_arg(ap, double); + if (jp_key(jp, key) == JPRINT_OK) { +#if USE_G + /* + * if we have functional %g format, + * use it. + */ + i = snprintf( + jp->tmpbuf, sizeof (jp->tmpbuf), + "%g", x); +#else + /* + * double has 15 places: + * 1.<14 digits>e-308 + */ + i = snprintf( + jp->tmpbuf, sizeof (jp->tmpbuf), + "%21.14e", x); +#endif + if ((i >= sizeof (jp->tmpbuf)) || + (i < 0)) + jp_puts(jp, (char *)"####"); + else + jp_puts(jp, jp->tmpbuf); + } +#endif + key[k = 0] = '\0'; + break; + case 'b': /* next parameter is boolean */ + if (jp->stackp < 0) { + jp->error = JPRINT_STACK_EMPTY; + break; + } + if (jp_key(jp, key) == JPRINT_OK) { + b = (boolean_t)va_arg(ap, int); + s = b ? (char *)"true" : + (char *)"false"; + jp_puts(jp, s); + } + key[k = 0] = '\0'; + break; + case '%': /* literal % */ + if (k < KEYLEN) { + key[k++] = '%'; + key[k] = '\0'; + } else + jp->error = JPRINT_FMT; + break; + default: + jp->error = JPRINT_FMT; + } + break; + case '{': /* open object */ + if (jp->stackp >= (JP_MAX_STACK - 1)) + jp->error = JPRINT_STACK_FULL; + else { + (void) jp_key(jp, key); + ++jp->stackp; + jp->stack[jp->stackp].type = JP_OBJECT; + jp->stack[jp->stackp].nelem = 0; + jp_putc(jp, '{'); + } + break; + case '}': /* close object */ + if (jp->stackp < 0) + jp->error = JPRINT_STACK_EMPTY; + else if (jp->stack[jp->stackp].type != JP_OBJECT) + jp->error = JPRINT_NEST_ERROR; + else { + --jp->stackp; + jp_putc(jp, '}'); + } + break; + case '[': /* open array */ + if (jp->stackp >= (JP_MAX_STACK - 1)) + jp->error = JPRINT_STACK_FULL; + else { + (void) jp_key(jp, key); + ++jp->stackp; + jp->stack[jp->stackp].type = JP_ARRAY; + jp->stack[jp->stackp].nelem = 0; + jp_putc(jp, '['); + } + break; + case ']': /* close array */ + if (jp->stackp < 0) + jp->error = JPRINT_STACK_EMPTY; + else if (jp->stack[jp->stackp].type != JP_ARRAY) + jp->error = JPRINT_NEST_ERROR; + else { + --jp->stackp; + jp_putc(jp, ']'); + } + break; + + case ',': /* ,: space tab are ignored */ + case ':': + case ' ': + case '\t': + break; + case '\\': + /* allow inclusion of ,: space tab to key */ + if (fmt[1] == '\0') + jp->error = JPRINT_FMT; + else { + ++fmt; + if (k < KEYLEN) { + key[k++] = *fmt; + key[k] = '\0'; + } else + jp->error = JPRINT_FMT; + } + break; + default: + if (k < KEYLEN) { + key[k++] = *fmt; + key[k] = '\0'; + } else + jp->error = JPRINT_FMT; + break; + } + ++fmt; + } + va_end(ap); + if (jp->error != JPRINT_OK) + return (-1); + + return (int)(jp->bufp - start); +} + diff --git a/module/zfs/json_stats.c b/module/zfs/json_stats.c new file mode 100644 index 0000000000..bb0229fa23 --- /dev/null +++ b/module/zfs/json_stats.c @@ -0,0 +1,467 @@ +/* + * json_stats.c + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* */ +void json_stats_destroy(spa_t *spa) +{ + spa_history_kstat_t *shk = &spa->spa_json_stats.kstat; + kstat_t *ksp = shk->kstat; + + if (ksp) { + if (ksp->ks_data) + kmem_free(ksp->ks_data, sizeof (spa_iostats_t)); + kstat_delete(ksp); + } + mutex_destroy(&shk->lock); +} + +/* + * Return string for datatype -- this guides us in implementing + * json translation. This is only because I find it easier to read... + */ +static const char *datatype_string(data_type_t t) { + switch (t) { + case DATA_TYPE_UNKNOWN: return "DATA_TYPE_UNKNOWN"; + case DATA_TYPE_BOOLEAN: return "DATA_TYPE_BOOLEAN"; + case DATA_TYPE_BYTE: return "DATA_TYPE_BYTE"; + case DATA_TYPE_INT16: return "DATA_TYPE_INT16"; + case DATA_TYPE_UINT16: return "DATA_TYPE_UINT16"; + case DATA_TYPE_INT32: return "DATA_TYPE_INT32"; + case DATA_TYPE_UINT32: return "DATA_TYPE_UINT32"; + case DATA_TYPE_INT64: return "DATA_TYPE_INT64"; + case DATA_TYPE_UINT64: return "DATA_TYPE_UINT64"; + case DATA_TYPE_STRING: return "DATA_TYPE_STRING"; + case DATA_TYPE_BYTE_ARRAY: return "DATA_TYPE_BYTE_ARRAY"; + case DATA_TYPE_INT16_ARRAY: return "DATA_TYPE_INT16_ARRAY"; + case DATA_TYPE_UINT16_ARRAY: return "DATA_TYPE_UINT16_ARRAY"; + case DATA_TYPE_INT32_ARRAY: return "DATA_TYPE_INT32_ARRAY"; + case DATA_TYPE_UINT32_ARRAY: return "DATA_TYPE_UINT32_ARRAY"; + case DATA_TYPE_INT64_ARRAY: return "DATA_TYPE_INT64_ARRAY"; + case DATA_TYPE_UINT64_ARRAY: return "DATA_TYPE_UINT64_ARRAY"; + case DATA_TYPE_STRING_ARRAY: return "DATA_TYPE_STRING_ARRAY"; + case DATA_TYPE_HRTIME: return "DATA_TYPE_HRTIME"; + case DATA_TYPE_NVLIST: return "DATA_TYPE_NVLIST"; + case DATA_TYPE_NVLIST_ARRAY: return "DATA_TYPE_NVLIST_ARRAY"; + case DATA_TYPE_BOOLEAN_VALUE: return "DATA_TYPE_BOOLEAN_VALUE"; + case DATA_TYPE_INT8: return "DATA_TYPE_INT8"; + case DATA_TYPE_UINT8: return "DATA_TYPE_UINT8"; + case DATA_TYPE_BOOLEAN_ARRAY: return "DATA_TYPE_BOOLEAN_ARRAY"; + case DATA_TYPE_INT8_ARRAY: return "DATA_TYPE_INT8_ARRAY"; + case DATA_TYPE_UINT8_ARRAY: return "DATA_TYPE_UINT8_ARRAY"; + default: return "UNKNOWN"; + } +} + +/* + * This code is NOT abstracted -- just some duplication, until I + * actually understand what is needed. + */ +static void nvlist_to_json(nvlist_t *nvl, jprint_t *jp) { + const nvpriv_t *priv; + const i_nvp_t *curr; + + if ((priv = (const nvpriv_t *)(uintptr_t)nvl->nvl_priv) == NULL) + return; + + for (curr = priv->nvp_list; curr != NULL; curr = curr->nvi_next) { + const nvpair_t *nvp = &curr->nvi_nvp; + const char *name = (const char *)NVP_NAME(nvp); + data_type_t type = NVP_TYPE(nvp); + void *p = NVP_VALUE(nvp); + + if (jp_error(jp) != JPRINT_OK) + return; + + switch (type) { + + /* + * Array types + */ + case DATA_TYPE_UINT64_ARRAY: + uint64_t *u = (uint64_t *)p; + jp_printf(jp, "%k: [", name); + for (int i = 0; i < NVP_NELEM(nvp); ++i) { + if (jp_error(jp) != JPRINT_OK) + break; + jp_printf(jp, "%U", u[i]); + } + jp_printf(jp, "]"); + break; + case DATA_TYPE_NVLIST_ARRAY: + nvlist_t **a = (nvlist_t **)p; + jp_printf(jp, "%k: [", name); + for (int i = 0; i < NVP_NELEM(nvp); ++i) { + if (jp_error(jp) != JPRINT_OK) + break; + jp_printf(jp, "{"); + nvlist_to_json(a[i], jp); + jp_printf(jp, "}"); + } + jp_printf(jp, "]"); + break; + /* + * Primitive types + */ + case DATA_TYPE_UINT64: + jp_printf(jp, "%k: %U", name, *(uint64_t *)p); + break; + case DATA_TYPE_INT64: + jp_printf(jp, "%k: %D", name, *(int64_t *)p); + break; + case DATA_TYPE_STRING: + jp_printf(jp, "%k: %s", name, (char *)p); + break; + case DATA_TYPE_BOOLEAN: + jp_printf(jp, "%k: %b", name, B_TRUE); + break; + case DATA_TYPE_BOOLEAN_VALUE: + jp_printf(jp, "%k: %b", name, *(boolean_t *)p); + break; + + /* + * Object types + */ + case DATA_TYPE_NVLIST: + /* + * We are going to suppress vdev_tree, and generate + * later -- needs fixing... quick hack for wasabi + */ + if ((jp->stackp == 0) && (strcmp(name, "vdev_tree") == 0)) + break; + jp_printf(jp, "%k: {", name); + nvlist_to_json((nvlist_t *)p , jp); + jp_printf(jp, "}"); + break; + + /* + * Default -- tell us what we are missing + */ + default: + jp_printf(jp, "%k: %s", name, datatype_string(type)); + zfs_dbgmsg("name = %s type = %d %s", name, + (int)type, datatype_string(type)); + break; + } + } +} + +static void vdev_to_json(vdev_t *v, pool_scan_stat_t *ps, jprint_t *jp) +{ + uint64_t i, n; + vdev_t **a; + const char *s; + jp_printf(jp, "type: %s", v->vdev_ops->vdev_op_type); + jp_printf(jp, "id: %U", v->vdev_id); + jp_printf(jp, "guid: %U", v->vdev_guid); + if (strcmp(v->vdev_ops->vdev_op_type, "root") != 0) { + jp_printf(jp, "asize: %U", v->vdev_asize); + jp_printf(jp, "ashift: %U", v->vdev_ashift); + jp_printf(jp, "whole_disk: %b", + (v->vdev_wholedisk == 0) ? B_FALSE : B_TRUE); + jp_printf(jp, "offline: %b", + (v->vdev_offline == 0) ? B_FALSE : B_TRUE); + jp_printf(jp, "faulted: %b", + (v->vdev_faulted == 0) ? B_FALSE : B_TRUE); + jp_printf(jp, "degraded: %b", + (v->vdev_degraded == 0) ? B_FALSE : B_TRUE); + jp_printf(jp, "removed: %b", + (v->vdev_removed == 0) ? B_FALSE : B_TRUE); + jp_printf(jp, "not_present: %b", + (v->vdev_not_present == 0) ? B_FALSE : B_TRUE); + jp_printf(jp, "is_log: %b", + (v->vdev_islog == 0) ? B_FALSE : B_TRUE); + + jp_printf(jp, "path: %s", v->vdev_path); + if (v->vdev_devid != NULL) + jp_printf(jp, "devid: %s", v->vdev_devid); + if (v->vdev_physpath != NULL) + jp_printf(jp, "physpath: %s", v->vdev_physpath); + if (v->vdev_enc_sysfs_path != NULL) + jp_printf(jp, "enc_sysfs_path: %s", + v->vdev_enc_sysfs_path); + switch (v->vdev_stat.vs_state) { + case VDEV_STATE_UNKNOWN: s = "HEALTHY"; break; + case VDEV_STATE_CLOSED: s = "CLOSED"; break; + case VDEV_STATE_OFFLINE: s = "OFFLINE"; break; + case VDEV_STATE_REMOVED: s = "REMOVED"; break; + case VDEV_STATE_CANT_OPEN: s = "CAN'T OPEN"; break; + case VDEV_STATE_FAULTED: s = "FAULTED"; break; + case VDEV_STATE_DEGRADED: s = "DEGRADED"; break; + case VDEV_STATE_HEALTHY: s = "HEALTHY"; break; + default: s = "?"; + } + jp_printf(jp, "state: %s", s); + /* Try for some of the extended status annotations that + * zpool status provides. + */ + /* (removing) */ + jp_printf(jp, "vs_scan_removing: %b", + v->vdev_stat.vs_scan_removing != 0); + /* (non-allocating) */ + jp_printf(jp, "vs_noalloc: %b", + v->vdev_stat.vs_noalloc != 0); + /* (awaiting resilver) */ + jp_printf(jp, "vs_resilver_deferred: %b", + v->vdev_stat.vs_resilver_deferred); + if ((v->vdev_state == VDEV_STATE_UNKNOWN) || + (v->vdev_state == VDEV_STATE_HEALTHY)) { + if (v->vdev_stat.vs_scan_processed != 0) { + if (ps && + (ps->pss_state == DSS_SCANNING)) { + jp_printf(jp, "resilver_repair: %s", + (ps->pss_func == POOL_SCAN_RESILVER) ? + "resilvering" : "repairing"); + } else if (ps && + v->vdev_stat.vs_resilver_deferred) { + jp_printf(jp, "resilver_repair: %s", + "awaiting resilver"); + } + } + } + jp_printf(jp, "initialize_state: {"); + s = "VDEV_INITIALIZE_NONE"; + if (v->vdev_stat.vs_initialize_state == VDEV_INITIALIZE_ACTIVE) + s = "VDEV_INITIALIZE_ACTIVE"; + if (v->vdev_stat.vs_initialize_state == VDEV_INITIALIZE_SUSPENDED) + s = "VDEV_INITIALIZE_SUSPENDED"; + if (v->vdev_stat.vs_initialize_state == VDEV_INITIALIZE_COMPLETE) + s = "VDEV_INITIALIZE_COMPLETE"; + jp_printf(jp, "vs_initialize_state: %s", s); + jp_printf(jp, "vs_initialize_bytes_done: %U", + v->vdev_stat.vs_initialize_bytes_done); + jp_printf(jp, "vs_initialize_bytes_est: %U", + v->vdev_stat.vs_initialize_bytes_est); + jp_printf(jp, "vs_initialize_action_time: %U", + v->vdev_stat.vs_initialize_action_time); + jp_printf(jp, "}"); + + jp_printf(jp, "trim_state: {"); + s = "VDEV_UNTRIMMED"; + if (v->vdev_stat.vs_trim_state == VDEV_TRIM_ACTIVE) + s = "VDEV_TRIM_ACTIVE"; + if (v->vdev_stat.vs_trim_state == VDEV_TRIM_SUSPENDED) + s = "VDEV_TRIM_SUSPENDED"; + if (v->vdev_stat.vs_trim_state == VDEV_TRIM_COMPLETE) + s = "VDEV_TRIM_COMPLETE"; + if (v->vdev_stat.vs_trim_notsup) + s = "VDEV_TRIM_UNSUPPORTED"; + jp_printf(jp, "vs_trim_state: %s", s); + if (!v->vdev_stat.vs_trim_notsup) { + jp_printf(jp, "vs_trim_action_time: %U", + v->vdev_stat.vs_trim_action_time); + jp_printf(jp, "vs_trim_bytes_done: %U", + v->vdev_stat.vs_trim_bytes_done); + jp_printf(jp, "vs_trim_bytes_est: %U", + v->vdev_stat.vs_trim_bytes_est); + } + jp_printf(jp, "}"); + + jp_printf(jp, "read_errors: %U", + v->vdev_stat.vs_read_errors); + jp_printf(jp, "write_errors: %U", + v->vdev_stat.vs_write_errors); + jp_printf(jp, "checksum_errors: %U", + v->vdev_stat.vs_checksum_errors); + jp_printf(jp, "slow_ios: %U", + v->vdev_stat.vs_slow_ios); + jp_printf(jp, "trim_errors: %U", + v->vdev_stat.vs_trim_errors); + } + /* + * Please note that children of children will translate to + * json... we will not put out the number, but that is not + * needed in json anyway. + */ + n = v->vdev_children; + a = v->vdev_child; + if (n != 0) { + jp_printf(jp, "children: ["); + for (i = 0; i < n; ++i) { + jp_printf(jp, "{"); + vdev_to_json(a[i], ps, jp); + jp_printf(jp, "}"); + } + jp_printf(jp, "]"); + } +} + +static void iterate_vdevs(spa_t *spa, pool_scan_stat_t *ps, jprint_t *jp) +{ + vdev_t *v = spa->spa_root_vdev; + if (v == NULL) { + jp_printf(jp, "error: %s", "NO ROOT VDEV"); + return; + } + jp_printf(jp, "vdev_tree: {"); + vdev_to_json(v, ps, jp); + jp_printf(jp, "}"); +} + +static int json_data(char *buf, size_t size, void *data) { + spa_t *spa = (spa_t *)data; + int error = 0; + int ps_error = 0; + jprint_t jp; + nvlist_t *nvl, *pnvl; + uint64_t loadtimes[2]; + pool_scan_stat_t ps; + const char *s = NULL; + + if (nvlist_dup(spa->spa_config, &nvl, 0) != 0) { + /* + * fmgw - fixme, what to do here?!? + */ + zfs_dbgmsg("json_data: nvlist_dup failed"); + return (0); + } + fnvlist_add_nvlist(nvl, ZPOOL_CONFIG_LOAD_INFO, spa->spa_load_info); + + spa_config_enter(spa, SCL_CONFIG, FTAG, RW_READER); + + ps_error = spa_scan_get_stats(spa, &ps); + + error = spa_prop_get(spa, &pnvl); + fnvlist_add_nvlist(nvl, "spa_props", pnvl); + + loadtimes[0] = spa->spa_loaded_ts.tv_sec; + loadtimes[1] = spa->spa_loaded_ts.tv_nsec; + fnvlist_add_uint64_array(nvl, ZPOOL_CONFIG_LOADED_TIME, loadtimes, 2); + fnvlist_add_uint64(nvl, ZPOOL_CONFIG_ERRCOUNT, + spa_get_errlog_size(spa)); + fnvlist_add_boolean_value(nvl, "is_suspended", spa_suspended(spa)); + fnvlist_add_uint64(nvl, ZPOOL_CONFIG_SUSPENDED, spa->spa_failmode); + fnvlist_add_uint64(nvl, ZPOOL_CONFIG_SUSPENDED_REASON, + spa->spa_suspended); + + jp_open(&jp, buf, size); + jp_printf(&jp, "{"); + + jp_printf(&jp, "stats_version: %d", 1); + jp_printf(&jp, "scan_error: %d", ps_error); + if (ps_error == 0) { + jp_printf(&jp, "scan_stats: {"); + switch (ps.pss_func) { + case POOL_SCAN_NONE: s = "NONE"; break; + case POOL_SCAN_SCRUB: s = "SCRUB"; break; + case POOL_SCAN_RESILVER: s = "RESILVER"; break; + case POOL_SCAN_FUNCS: s = "?"; + } + jp_printf(&jp, "func: %s", s); + switch (ps.pss_state) { + case DSS_NONE: s = "NONE"; break; + case DSS_SCANNING: s = "SCANNING"; break; + case DSS_FINISHED: s = "FINISHED"; break; + case DSS_CANCELED: s = "CANCELED"; break; + case DSS_NUM_STATES: s = "?"; + } + jp_printf(&jp, "state: %s", s); + jp_printf(&jp, "start_time: %U", ps.pss_start_time); + jp_printf(&jp, "end_time: %U", ps.pss_end_time); + jp_printf(&jp, "to_examine: %U", ps.pss_to_examine); + jp_printf(&jp, "examined: %U", ps.pss_examined); + jp_printf(&jp, "processed: %U", ps.pss_processed); + jp_printf(&jp, "errors: %U", ps.pss_errors); + + jp_printf(&jp, "pass_exam: %U", ps.pss_pass_exam); + jp_printf(&jp, "pass_start: %U", ps.pss_pass_start); + jp_printf(&jp, "pass_scrub_pause: %U", ps.pss_pass_scrub_pause); + jp_printf(&jp, "pass_scrub_scrub_spent_paused: %U", + ps.pss_pass_scrub_spent_paused); + jp_printf(&jp, "pass_issued: %U", ps.pss_pass_issued); + jp_printf(&jp, "issued: %U", ps.pss_issued); + jp_printf(&jp, "}"); + } + s = "?"; + switch (spa->spa_state) { + case POOL_STATE_ACTIVE: s = "ACTIVE"; break; + case POOL_STATE_EXPORTED: s = "EXPORTED"; break; + case POOL_STATE_DESTROYED: s = "DESTROYED"; break; + case POOL_STATE_SPARE: s = "SPARE"; break; + case POOL_STATE_L2CACHE: s = "L2CACHE"; break; + case POOL_STATE_UNINITIALIZED: s = "UNINITIALIZED"; break; + case POOL_STATE_UNAVAIL: s = "UNAVAIL"; break; + case POOL_STATE_POTENTIALLY_ACTIVE: s = "POTENTIALLY ACTIVE"; break; + } + jp_printf(&jp, "pool_state: %s", s); + + spa_add_spares(spa, nvl); + spa_add_l2cache(spa, nvl); + spa_add_feature_stats(spa, nvl); + + /* iterate and transfer nvl to json */ + nvlist_to_json(nvl, &jp); + + iterate_vdevs(spa, &ps, &jp); + + /* + * close the root object + */ + jp_printf(&jp, "}"); + + spa_config_exit(spa, SCL_CONFIG, FTAG); + nvlist_free(nvl); + + error = jp_close(&jp); + if (error == JPRINT_BUF_FULL) { + error = SET_ERROR(ENOMEM); + } else if (error != 0) { + /* + * Another error from jprint, format an error message + * but this is not ever to happen (this would be a + * defect elsewhere). + * + * If this does happen, we simply put the string where + * the json should go... this is expected to trigger + * a json decode error, and report "upstream" + */ + snprintf(buf, size, + "jprint error %s (%d) callno %d, size %ld\n", + jp_errorstring(error), error, jp_errorpos(&jp), size); + error = 0; + } + return (error); +} + + +static void *json_addr(kstat_t *ksp, loff_t n) +{ + if (n == 0) + return (ksp->ks_private); + return (NULL); +} + + +void json_stats_init(spa_t *spa) +{ + spa_history_kstat_t *shk = &spa->spa_json_stats.kstat; + char *name; + kstat_t *ksp; + + mutex_init(&shk->lock, NULL, MUTEX_DEFAULT, NULL); + name = kmem_asprintf("zfs/%s", spa_name(spa)); + ksp = kstat_create(name, 0, "stats", "misc", + KSTAT_TYPE_RAW, 0, KSTAT_FLAG_VIRTUAL); + shk->kstat = ksp; + if (ksp) { + ksp->ks_lock = &shk->lock; + ksp->ks_data = NULL; + ksp->ks_private = spa; + ksp->ks_flags |= KSTAT_FLAG_NO_HEADERS; + kstat_set_raw_ops(ksp, NULL, json_data, json_addr); + kstat_install(ksp); + } + + kmem_strfree(name); +} + diff --git a/module/zfs/spa.c b/module/zfs/spa.c index dfa73483ac..dfdbde8688 100644 --- a/module/zfs/spa.c +++ b/module/zfs/spa.c @@ -5285,7 +5285,7 @@ spa_inject_delref(spa_t *spa) /* * Add spares device information to the nvlist. */ -static void +void spa_add_spares(spa_t *spa, nvlist_t *config) { nvlist_t **spares; @@ -5334,7 +5334,7 @@ spa_add_spares(spa_t *spa, nvlist_t *config) /* * Add l2cache device information to the nvlist, including vdev stats. */ -static void +void spa_add_l2cache(spa_t *spa, nvlist_t *config) { nvlist_t **l2cache; @@ -5448,7 +5448,7 @@ spa_feature_stats_from_cache(spa_t *spa, nvlist_t *features) * ensures we don't block here on I/O on a suspended pool so 'zpool * clear' can resume the pool. */ -static void +void spa_add_feature_stats(spa_t *spa, nvlist_t *config) { nvlist_t *features; @@ -9098,6 +9098,7 @@ spa_sync_iterate_to_convergence(spa_t *spa, dmu_tx_t *tx) &spa->spa_deferred_bpobj, tx); } + ddt_sync(spa, txg); dsl_scan_sync(dp, tx); svr_sync(spa, tx); diff --git a/module/zfs/spa_stats.c b/module/zfs/spa_stats.c index 534ac72fee..42afc024ac 100644 --- a/module/zfs/spa_stats.c +++ b/module/zfs/spa_stats.c @@ -24,6 +24,7 @@ #include #include #include +#include /* * Keeps stats on last N reads per spa_t, disabled by default. @@ -951,11 +952,13 @@ spa_stats_init(spa_t *spa) spa_mmp_history_init(spa); spa_state_init(spa); spa_iostats_init(spa); + json_stats_init(spa); } void spa_stats_destroy(spa_t *spa) { + json_stats_destroy(spa); spa_iostats_destroy(spa); spa_health_destroy(spa); spa_tx_assign_destroy(spa); diff --git a/module/zfs/zfs_ioctl.c b/module/zfs/zfs_ioctl.c index 3336bb7832..e0d3f4788f 100644 --- a/module/zfs/zfs_ioctl.c +++ b/module/zfs/zfs_ioctl.c @@ -221,6 +221,9 @@ #include #include + +int fmgw_debug = 0; + kmutex_t zfsdev_state_lock; zfsdev_state_t *zfsdev_state_list; @@ -1592,6 +1595,8 @@ zfs_ioc_pool_configs(zfs_cmd_t *zc) nvlist_t *configs; int error; + if (fmgw_debug != 0) + zfs_dbgmsg("fmgw -- calling spa_all_configs, which takes spa_namespace_lock"); if ((configs = spa_all_configs(&zc->zc_cookie)) == NULL) return (SET_ERROR(EEXIST)); @@ -1618,6 +1623,8 @@ zfs_ioc_pool_stats(zfs_cmd_t *zc) int error; int ret = 0; + if (fmgw_debug != 0) + zfs_dbgmsg("fmgw -- calling spa_get_stats"); error = spa_get_stats(zc->zc_name, &config, zc->zc_value, sizeof (zc->zc_value)); @@ -1685,7 +1692,7 @@ zfs_ioc_pool_scan(zfs_cmd_t *zc) if (zc->zc_flags == POOL_SCRUB_PAUSE) error = spa_scrub_pause_resume(spa, POOL_SCRUB_PAUSE); - else if (zc->zc_cookie == POOL_SCAN_NONE) +else if (zc->zc_cookie == POOL_SCAN_NONE) error = spa_scan_stop(spa); else error = spa_scan(spa, zc->zc_cookie); @@ -1695,6 +1702,44 @@ zfs_ioc_pool_scan(zfs_cmd_t *zc) return (error); } +/* + * This interface lets us put messages into zfs_dbgmsg() log. We + * also look at the message; if the first character is *, we take + * it as a private command. *D0..F sets fmgw_debug bits to 0..F + */ +static int +zfs_ioc_addlog(zfs_cmd_t *zc) +{ + char *s = "(NULL)"; + s = zc->zc_name; + if ((s[0] == '*') && (s[1] == 'D')) { + switch (s[2]) { + case '0': fmgw_debug = 0x0; break; + case '1': fmgw_debug = 0x1; break; + case '2': fmgw_debug = 0x2; break; + case '3': fmgw_debug = 0x3; break; + case '4': fmgw_debug = 0x4; break; + case '5': fmgw_debug = 0x5; break; + case '6': fmgw_debug = 0x6; break; + case '7': fmgw_debug = 0x7; break; + case '8': fmgw_debug = 0x8; break; + case '9': fmgw_debug = 0x9; break; + case 'A': fmgw_debug = 0xa; break; + case 'B': fmgw_debug = 0xb; break; + case 'C': fmgw_debug = 0xc; break; + case 'D': fmgw_debug = 0xd; break; + case 'E': fmgw_debug = 0xe; break; + case 'F': fmgw_debug = 0xf; break; + default: break; + } + } else if (s[0] == '*') { + zfs_dbgmsg("bad command %c", s[1]); + } else { + zfs_dbgmsg("%s", s); + } + return (0); +} + static int zfs_ioc_pool_freeze(zfs_cmd_t *zc) { @@ -2087,6 +2132,8 @@ zfs_ioc_objset_stats(zfs_cmd_t *zc) objset_t *os; int error; + if (fmgw_debug != 0) + zfs_dbgmsg("fmgw"); error = dmu_objset_hold(zc->zc_name, FTAG, &os); if (error == 0) { error = zfs_ioc_objset_stats_impl(zc, os); @@ -2214,6 +2261,8 @@ zfs_ioc_dataset_list_next(zfs_cmd_t *zc) char *p; size_t orig_len = strlen(zc->zc_name); + if (fmgw_debug != 0) + zfs_dbgmsg("fmgw"); top: if ((error = dmu_objset_hold(zc->zc_name, FTAG, &os))) { if (error == ENOENT) @@ -2957,6 +3006,8 @@ zfs_ioc_pool_get_props(zfs_cmd_t *zc) int error; nvlist_t *nvp = NULL; + if (fmgw_debug != 0) + zfs_dbgmsg("fmgw"); if ((error = spa_open(zc->zc_name, &spa, FTAG)) != 0) { /* * If the pool is faulted, there may be properties we can still @@ -3515,6 +3566,8 @@ zfs_ioc_log_history(const char *unused, nvlist_t *innvl, nvlist_t *outnvl) spa_t *spa; int error; + if (fmgw_debug != 0) + zfs_dbgmsg("fmgw"); /* * The poolname in the ioctl is not set, we get it from the TSD, * which was set at the end of the last successful ioctl that allows @@ -7112,6 +7165,12 @@ zfs_ioctl_init(void) zfs_ioctl_register_legacy(ZFS_IOC_POOL_FREEZE, zfs_ioc_pool_freeze, zfs_secpolicy_config, NO_NAME, B_FALSE, POOL_CHECK_READONLY); + /* fmgw - we sneak this in here... just as awful as zfs_ioc_pool_freeze + * which we are modeled on. + */ + zfs_ioctl_register_legacy(ZFS_IOC_ADD_LOG, zfs_ioc_addlog, + zfs_secpolicy_none, NO_NAME, B_FALSE, POOL_CHECK_READONLY); + zfs_ioctl_register_pool(ZFS_IOC_POOL_CREATE, zfs_ioc_pool_create, zfs_secpolicy_config, B_TRUE, POOL_CHECK_NONE); zfs_ioctl_register_pool_modify(ZFS_IOC_POOL_SCAN, @@ -7458,6 +7517,8 @@ zfsdev_ioctl_common(uint_t vecnum, zfs_cmd_t *zc, int flag) if (vec->zvec_func == NULL && vec->zvec_legacy_func == NULL) return (SET_ERROR(ZFS_ERR_IOC_CMD_UNAVAIL)); +// if (fmgw_debug != 0) +// zfs_dbgmsg("enter ioctl, %d %x %s", vecnum, vecnum, fmgw_zioctl(vecnum + ZFS_IOC_FIRST)); zc->zc_iflags = flag & FKIOCTL; max_nvlist_src_size = zfs_max_nvlist_src_size_os(); if (zc->zc_nvlist_src_size > max_nvlist_src_size) {