| /* |
| * Copyright(c) 2015-2017 Intel Corporation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of version 2 of the GNU General Public License as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * General Public License for more details. |
| */ |
| #include <stdio.h> |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <limits.h> |
| |
| #include <util/json.h> |
| #include <util/filter.h> |
| #include <json-c/json.h> |
| #include <ndctl/libndctl.h> |
| #include <util/parse-options.h> |
| #include <ccan/array_size/array_size.h> |
| |
| #include <ndctl.h> |
| |
| static struct { |
| bool buses; |
| bool dimms; |
| bool regions; |
| bool namespaces; |
| bool idle; |
| bool health; |
| bool dax; |
| bool media_errors; |
| bool human; |
| bool firmware; |
| int verbose; |
| } list; |
| |
| static unsigned long listopts_to_flags(void) |
| { |
| unsigned long flags = 0; |
| |
| if (list.idle) |
| flags |= UTIL_JSON_IDLE; |
| if (list.media_errors) |
| flags |= UTIL_JSON_MEDIA_ERRORS; |
| if (list.dax) |
| flags |= UTIL_JSON_DAX | UTIL_JSON_DAX_DEVS; |
| if (list.human) |
| flags |= UTIL_JSON_HUMAN; |
| if (list.verbose) |
| flags |= UTIL_JSON_VERBOSE; |
| return flags; |
| } |
| |
| struct util_filter_params param; |
| |
| static int did_fail; |
| |
| #define fail(fmt, ...) \ |
| do { \ |
| did_fail = 1; \ |
| fprintf(stderr, "ndctl-%s:%s:%d: " fmt, \ |
| VERSION, __func__, __LINE__, ##__VA_ARGS__); \ |
| } while (0) |
| |
| static struct json_object *region_to_json(struct ndctl_region *region, |
| unsigned long flags) |
| { |
| struct json_object *jregion = json_object_new_object(); |
| struct json_object *jobj, *jbbs, *jmappings = NULL; |
| struct ndctl_interleave_set *iset; |
| struct ndctl_mapping *mapping; |
| unsigned int bb_count = 0; |
| unsigned long long extent; |
| enum ndctl_persistence_domain pd; |
| int numa; |
| |
| if (!jregion) |
| return NULL; |
| |
| jobj = json_object_new_string(ndctl_region_get_devname(region)); |
| if (!jobj) |
| goto err; |
| json_object_object_add(jregion, "dev", jobj); |
| |
| jobj = util_json_object_size(ndctl_region_get_size(region), flags); |
| if (!jobj) |
| goto err; |
| json_object_object_add(jregion, "size", jobj); |
| |
| jobj = util_json_object_size(ndctl_region_get_available_size(region), |
| flags); |
| if (!jobj) |
| goto err; |
| json_object_object_add(jregion, "available_size", jobj); |
| |
| extent = ndctl_region_get_max_available_extent(region); |
| if (extent != ULLONG_MAX) { |
| jobj = util_json_object_size(extent, flags); |
| if (!jobj) |
| goto err; |
| json_object_object_add(jregion, "max_available_extent", jobj); |
| } |
| |
| switch (ndctl_region_get_type(region)) { |
| case ND_DEVICE_REGION_PMEM: |
| jobj = json_object_new_string("pmem"); |
| break; |
| case ND_DEVICE_REGION_BLK: |
| jobj = json_object_new_string("blk"); |
| break; |
| default: |
| jobj = NULL; |
| } |
| if (!jobj) |
| goto err; |
| json_object_object_add(jregion, "type", jobj); |
| |
| numa = ndctl_region_get_numa_node(region); |
| if (numa >= 0 && flags & UTIL_JSON_VERBOSE) { |
| jobj = json_object_new_int(numa); |
| if (jobj) |
| json_object_object_add(jregion, "numa_node", jobj); |
| } |
| |
| iset = ndctl_region_get_interleave_set(region); |
| if (iset) { |
| jobj = util_json_object_hex( |
| ndctl_interleave_set_get_cookie(iset), flags); |
| if (!jobj) |
| fail("\n"); |
| else |
| json_object_object_add(jregion, "iset_id", jobj); |
| } |
| |
| ndctl_mapping_foreach(region, mapping) { |
| struct ndctl_dimm *dimm = ndctl_mapping_get_dimm(mapping); |
| struct json_object *jmapping; |
| |
| if (!list.dimms) |
| break; |
| |
| if (!util_dimm_filter(dimm, param.dimm)) |
| continue; |
| |
| if (!list.idle && !ndctl_dimm_is_enabled(dimm)) |
| continue; |
| |
| if (!jmappings) { |
| jmappings = json_object_new_array(); |
| if (!jmappings) { |
| fail("\n"); |
| continue; |
| } |
| json_object_object_add(jregion, "mappings", jmappings); |
| } |
| |
| jmapping = util_mapping_to_json(mapping, listopts_to_flags()); |
| if (!jmapping) { |
| fail("\n"); |
| continue; |
| } |
| json_object_array_add(jmappings, jmapping); |
| } |
| |
| if (!ndctl_region_is_enabled(region)) { |
| jobj = json_object_new_string("disabled"); |
| if (!jobj) |
| goto err; |
| json_object_object_add(jregion, "state", jobj); |
| } |
| |
| jbbs = util_region_badblocks_to_json(region, &bb_count, flags); |
| if (bb_count) { |
| jobj = json_object_new_int(bb_count); |
| if (!jobj) { |
| json_object_put(jbbs); |
| goto err; |
| } |
| json_object_object_add(jregion, "badblock_count", jobj); |
| } |
| if ((flags & UTIL_JSON_MEDIA_ERRORS) && jbbs) |
| json_object_object_add(jregion, "badblocks", jbbs); |
| |
| pd = ndctl_region_get_persistence_domain(region); |
| switch (pd) { |
| case PERSISTENCE_CPU_CACHE: |
| jobj = json_object_new_string("cpu_cache"); |
| break; |
| case PERSISTENCE_MEM_CTRL: |
| jobj = json_object_new_string("memory_controller"); |
| break; |
| case PERSISTENCE_NONE: |
| jobj = json_object_new_string("none"); |
| break; |
| default: |
| jobj = json_object_new_string("unknown"); |
| break; |
| } |
| |
| if (jobj) |
| json_object_object_add(jregion, "persistence_domain", jobj); |
| |
| return jregion; |
| err: |
| fail("\n"); |
| json_object_put(jregion); |
| return NULL; |
| } |
| |
| static void filter_namespace(struct ndctl_namespace *ndns, |
| struct util_filter_ctx *ctx) |
| { |
| struct json_object *jndns; |
| struct list_filter_arg *lfa = ctx->list; |
| struct json_object *container = lfa->jregion ? lfa->jregion : lfa->jbus; |
| |
| if (!list.idle && !ndctl_namespace_is_active(ndns)) |
| return; |
| |
| if (!lfa->jnamespaces) { |
| lfa->jnamespaces = json_object_new_array(); |
| if (!lfa->jnamespaces) { |
| fail("\n"); |
| return; |
| } |
| |
| if (container) |
| json_object_object_add(container, "namespaces", |
| lfa->jnamespaces); |
| } |
| |
| jndns = util_namespace_to_json(ndns, lfa->flags); |
| if (!jndns) { |
| fail("\n"); |
| return; |
| } |
| |
| json_object_array_add(lfa->jnamespaces, jndns); |
| } |
| |
| static bool filter_region(struct ndctl_region *region, |
| struct util_filter_ctx *ctx) |
| { |
| struct list_filter_arg *lfa = ctx->list; |
| struct json_object *jbus = lfa->jbus; |
| struct json_object *jregion; |
| |
| if (!list.regions) |
| return true; |
| |
| if (!list.idle && !ndctl_region_is_enabled(region)) |
| return true; |
| |
| if (!lfa->jregions) { |
| lfa->jregions = json_object_new_array(); |
| if (!lfa->jregions) { |
| fail("\n"); |
| return false; |
| } |
| |
| if (jbus) |
| json_object_object_add(jbus, "regions", |
| lfa->jregions); |
| } |
| |
| jregion = region_to_json(region, lfa->flags); |
| if (!jregion) { |
| fail("\n"); |
| return false; |
| } |
| lfa->jregion = jregion; |
| |
| /* |
| * We've started a new region, any previous jnamespaces will |
| * have been parented to the last region. Clear out jnamespaces |
| * so we start a new array per region. |
| */ |
| lfa->jnamespaces = NULL; |
| |
| /* |
| * Without a bus we are collecting regions anonymously across |
| * the platform. |
| */ |
| json_object_array_add(lfa->jregions, jregion); |
| return true; |
| } |
| |
| static void filter_dimm(struct ndctl_dimm *dimm, struct util_filter_ctx *ctx) |
| { |
| struct list_filter_arg *lfa = ctx->list; |
| struct json_object *jdimm; |
| |
| if (!list.idle && !ndctl_dimm_is_enabled(dimm)) |
| return; |
| |
| if (!lfa->jdimms) { |
| lfa->jdimms = json_object_new_array(); |
| if (!lfa->jdimms) { |
| fail("\n"); |
| return; |
| } |
| |
| if (lfa->jbus) |
| json_object_object_add(lfa->jbus, "dimms", lfa->jdimms); |
| } |
| |
| jdimm = util_dimm_to_json(dimm, lfa->flags); |
| if (!jdimm) { |
| fail("\n"); |
| return; |
| } |
| |
| if (list.health) { |
| struct json_object *jhealth; |
| |
| jhealth = util_dimm_health_to_json(dimm); |
| if (jhealth) |
| json_object_object_add(jdimm, "health", jhealth); |
| else if (ndctl_dimm_is_cmd_supported(dimm, ND_CMD_SMART)) { |
| /* |
| * Failed to retrieve health data from a dimm |
| * that otherwise supports smart data retrieval |
| * commands. |
| */ |
| fail("\n"); |
| return; |
| } |
| } |
| |
| if (list.firmware) { |
| struct json_object *jfirmware; |
| |
| jfirmware = util_dimm_firmware_to_json(dimm, lfa->flags); |
| if (jfirmware) |
| json_object_object_add(jdimm, "firmware", jfirmware); |
| } |
| |
| /* |
| * Without a bus we are collecting dimms anonymously across the |
| * platform. |
| */ |
| json_object_array_add(lfa->jdimms, jdimm); |
| } |
| |
| static bool filter_bus(struct ndctl_bus *bus, struct util_filter_ctx *ctx) |
| { |
| struct list_filter_arg *lfa = ctx->list; |
| |
| /* |
| * These sub-objects are local to a bus and, if present, have |
| * been added as a child of a parent object on the last |
| * iteration. |
| */ |
| if (lfa->jbuses) { |
| lfa->jdimms = NULL; |
| lfa->jregion = NULL; |
| lfa->jregions = NULL; |
| lfa->jnamespaces = NULL; |
| } |
| |
| if (!list.buses) |
| return true; |
| |
| if (!lfa->jbuses) { |
| lfa->jbuses = json_object_new_array(); |
| if (!lfa->jbuses) { |
| fail("\n"); |
| return false; |
| } |
| } |
| |
| lfa->jbus = util_bus_to_json(bus); |
| if (!lfa->jbus) { |
| fail("\n"); |
| return false; |
| } |
| json_object_array_add(lfa->jbuses, lfa->jbus); |
| return true; |
| } |
| |
| static int list_display(struct list_filter_arg *lfa) |
| { |
| struct json_object *jnamespaces = lfa->jnamespaces; |
| struct json_object *jregions = lfa->jregions; |
| struct json_object *jdimms = lfa->jdimms; |
| struct json_object *jbuses = lfa->jbuses; |
| |
| if (jbuses) |
| util_display_json_array(stdout, jbuses, lfa->flags); |
| else if ((!!jdimms + !!jregions + !!jnamespaces) > 1) { |
| struct json_object *jplatform = json_object_new_object(); |
| |
| if (!jplatform) { |
| fail("\n"); |
| return -ENOMEM; |
| } |
| |
| if (jdimms) |
| json_object_object_add(jplatform, "dimms", jdimms); |
| if (jregions) |
| json_object_object_add(jplatform, "regions", jregions); |
| if (jnamespaces && !jregions) |
| json_object_object_add(jplatform, "namespaces", |
| jnamespaces); |
| printf("%s\n", json_object_to_json_string_ext(jplatform, |
| JSON_C_TO_STRING_PRETTY)); |
| json_object_put(jplatform); |
| } else if (jdimms) |
| util_display_json_array(stdout, jdimms, lfa->flags); |
| else if (jregions) |
| util_display_json_array(stdout, jregions, lfa->flags); |
| else if (jnamespaces) |
| util_display_json_array(stdout, jnamespaces, lfa->flags); |
| return 0; |
| } |
| |
| static int num_list_flags(void) |
| { |
| return list.buses + list.dimms + list.regions + list.namespaces; |
| } |
| |
| int cmd_list(int argc, const char **argv, struct ndctl_ctx *ctx) |
| { |
| const struct option options[] = { |
| OPT_STRING('b', "bus", ¶m.bus, "bus-id", "filter by bus"), |
| OPT_STRING('r', "region", ¶m.region, "region-id", |
| "filter by region"), |
| OPT_STRING('d', "dimm", ¶m.dimm, "dimm-id", |
| "filter by dimm"), |
| OPT_STRING('n', "namespace", ¶m.namespace, "namespace-id", |
| "filter by namespace id"), |
| OPT_STRING('m', "mode", ¶m.mode, "namespace-mode", |
| "filter by namespace mode"), |
| OPT_STRING('t', "type", ¶m.type, "region-type", |
| "filter by region-type"), |
| OPT_STRING('U', "numa-node", ¶m.numa_node, "numa node", |
| "filter by numa node"), |
| OPT_BOOLEAN('B', "buses", &list.buses, "include bus info"), |
| OPT_BOOLEAN('D', "dimms", &list.dimms, "include dimm info"), |
| OPT_BOOLEAN('F', "firmware", &list.firmware, "include firmware info"), |
| OPT_BOOLEAN('H', "health", &list.health, "include dimm health"), |
| OPT_BOOLEAN('R', "regions", &list.regions, |
| "include region info"), |
| OPT_BOOLEAN('N', "namespaces", &list.namespaces, |
| "include namespace info (default)"), |
| OPT_BOOLEAN('X', "device-dax", &list.dax, |
| "include device-dax info"), |
| OPT_BOOLEAN('i', "idle", &list.idle, "include idle devices"), |
| OPT_BOOLEAN('M', "media-errors", &list.media_errors, |
| "include media errors"), |
| OPT_BOOLEAN('u', "human", &list.human, |
| "use human friendly number formats "), |
| OPT_INCR('v', "verbose", &list.verbose, |
| "increase output detail"), |
| OPT_END(), |
| }; |
| const char * const u[] = { |
| "ndctl list [<options>]", |
| NULL |
| }; |
| struct util_filter_ctx fctx = { 0 }; |
| struct list_filter_arg lfa = { 0 }; |
| int i, rc; |
| |
| argc = parse_options(argc, argv, options, u, 0); |
| for (i = 0; i < argc; i++) |
| error("unknown parameter \"%s\"\n", argv[i]); |
| if (argc) |
| usage_with_options(u, options); |
| |
| if (num_list_flags() == 0) { |
| list.buses = !!param.bus; |
| list.regions = !!param.region; |
| list.dimms = !!param.dimm; |
| if (list.dax && !param.mode) |
| param.mode = "dax"; |
| } |
| |
| switch (list.verbose) { |
| default: |
| case 3: |
| list.idle = true; |
| list.firmware = true; |
| list.health = true; |
| case 2: |
| list.dimms = true; |
| list.buses = true; |
| list.regions = true; |
| case 1: |
| list.media_errors = true; |
| list.namespaces = true; |
| list.dax = true; |
| case 0: |
| break; |
| } |
| |
| if (num_list_flags() == 0) |
| list.namespaces = true; |
| |
| fctx.filter_bus = filter_bus; |
| fctx.filter_dimm = list.dimms ? filter_dimm : NULL; |
| fctx.filter_region = filter_region; |
| fctx.filter_namespace = list.namespaces ? filter_namespace : NULL; |
| fctx.list = &lfa; |
| lfa.flags = listopts_to_flags(); |
| |
| rc = util_filter_walk(ctx, &fctx, ¶m); |
| if (rc) |
| return rc; |
| |
| if (list_display(&lfa) || did_fail) |
| return -ENOMEM; |
| return 0; |
| } |