@@ -5,7 +5,8 @@ man1_MANS = \
ndctl-disable-region.1 \
ndctl-enable-namespace.1 \
ndctl-disable-namespace.1 \
- ndctl-create-namespace.1
+ ndctl-create-namespace.1 \
+ ndctl-list.1
XML_DEPS = \
$(top_srcdir)/version.m4 \
new file mode 100644
@@ -0,0 +1,86 @@
+ndctl-list(1)
+=============
+
+NAME
+----
+ndctl-list - dump the platform nvdimm device topology in json
+
+SYNOPSIS
+--------
+[verse]
+'ndctl list' [<options>]
+
+Walk all the nvdimm buses in the system and list all attached devices
+along with some of their major attributes.
+
+Options can be specified to limit the output to devices of a certain
+class. Where the classes are buses, dimms, regions, and namespaces. By
+default, 'ndctl list' with no options is equivalent to:
+[verse]
+ndctl list --namespaces --bus=all --region=all
+
+EXAMPLE
+-------
+[verse]
+# ndctl list --buses --namespaces
+
+["literal"]
+{
+ "provider":"nfit_test.1",
+ "dev":"ndbus2",
+ "namespaces":[
+ {
+ "dev":"namespace9.0",
+ "mode":"raw",
+ "size":33554432,
+ "blockdev":"pmem9"
+ }
+ ]
+}
+{
+ "provider":"nfit_test.0",
+ "dev":"ndbus1"
+}
+{
+ "provider":"e820",
+ "dev":"ndbus0",
+ "namespaces":[
+ {
+ "dev":"namespace0.0",
+ "mode":"memory",
+ "size":8589934592,
+ "blockdev":"pmem0"
+ }
+ ]
+}
+
+OPTIONS
+-------
+-r::
+--region=::
+include::xable-region-options.txt[]
+
+-B::
+--buses::
+ Include bus info in the listing
+
+-D::
+--dimms::
+ Include dimm info in the listing
+
+-R::
+--regions::
+ Include region info in the listing
+
+-N::
+--namespaces::
+ Include namespace info in the listing. Namespace info is
+ included by default, specify '--no-namespaces' to omit.
+
+-i::
+--idle::
+ Include idle (not enabled) devices in the listing
+
+SEE ALSO
+--------
+linkndctl:ndctl-create-namespace[1]
@@ -15,7 +15,8 @@ AM_CPPFLAGS = \
-I${top_srcdir}/ \
$(KMOD_CFLAGS) \
$(UDEV_CFLAGS) \
- $(UUID_CFLAGS)
+ $(UUID_CFLAGS) \
+ $(JSON_CFLAGS)
AM_CFLAGS = ${my_CFLAGS} \
-fvisibility=hidden \
@@ -66,12 +67,14 @@ ndctl_SOURCES = ndctl.c \
builtin-create-nfit.c \
builtin-xaction-namespace.c \
builtin-xable-region.c \
+ builtin-list.c \
builtin-test.c \
builtin-help.c \
builtin-zero-labels.c \
util/parse-options.c \
util/parse-options.h \
util/usage.c \
+ util/json.c \
util/size.c \
util/strbuf.c \
util/wrapper.c \
@@ -91,7 +94,7 @@ ndctl_SOURCES += test/blk_namespaces.c \
ndctl_SOURCES += builtin-bat.c
endif
-ndctl_LDADD = lib/libndctl.la $(UUID_LIBS) $(KMOD_LIBS)
+ndctl_LDADD = lib/libndctl.la $(UUID_LIBS) $(KMOD_LIBS) $(JSON_LIBS)
noinst_LIBRARIES = libccan.a
libccan_a_SOURCES = \
new file mode 100644
@@ -0,0 +1,367 @@
+#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>
+
+#ifdef HAVE_NDCTL_H
+#include <linux/ndctl.h>
+#else
+#include <ndctl.h>
+#endif
+
+static struct {
+ bool buses;
+ bool dimms;
+ bool regions;
+ bool namespaces;
+ bool idle;
+} list = {
+ .namespaces = true,
+};
+
+static struct {
+ const char *bus;
+ const char *region;
+} param;
+
+static int did_fail;
+static int jflag = JSON_C_TO_STRING_PRETTY;
+
+#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 *list_namespaces(struct ndctl_region *region,
+ struct json_object *container, struct json_object *jnamespaces,
+ bool continue_array)
+{
+ struct ndctl_namespace *ndns;
+
+ ndctl_namespace_foreach(region, ndns) {
+ struct json_object *jndns;
+
+ /* are we emitting namespaces? */
+ if (!list.namespaces)
+ break;
+
+ if (!list.idle && !util_namespace_active(ndns))
+ continue;
+
+ if (!jnamespaces) {
+ jnamespaces = json_object_new_array();
+ if (!jnamespaces) {
+ fail("\n");
+ continue;
+ }
+
+ if (container)
+ json_object_object_add(container, "namespaces",
+ jnamespaces);
+ }
+
+ jndns = util_namespace_to_json(ndns);
+ if (!jndns) {
+ fail("\n");
+ continue;
+ }
+
+ json_object_array_add(jnamespaces, jndns);
+ }
+
+ /*
+ * We we are collecting namespaces anonymously across the
+ * platform / bus
+ */
+ if (continue_array)
+ return jnamespaces;
+ return NULL;
+}
+
+static struct json_object *region_to_json(struct ndctl_region *region)
+{
+ struct json_object *jregion = json_object_new_object();
+ struct json_object *jobj, *jmappings = NULL;
+ struct ndctl_interleave_set *iset;
+ struct ndctl_mapping *mapping;
+
+ 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 = json_object_new_int64(ndctl_region_get_size(region));
+ if (!jobj)
+ goto err;
+ json_object_object_add(jregion, "size", jobj);
+
+ jobj = json_object_new_int64(ndctl_region_get_available_size(region));
+ if (!jobj)
+ goto err;
+ json_object_object_add(jregion, "available_size", 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);
+
+ iset = ndctl_region_get_interleave_set(region);
+ if (iset) {
+ jobj = json_object_new_int64(
+ ndctl_interleave_set_get_cookie(iset));
+ 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 (!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);
+ 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);
+ }
+
+ list_namespaces(region, jregion, NULL, false);
+ return jregion;
+ err:
+ fail("\n");
+ json_object_put(jregion);
+ return NULL;
+}
+
+static void display_array(struct json_object *jarray)
+{
+ if (json_object_array_length(jarray) > 1)
+ printf("%s\n", json_object_to_json_string_ext(jarray, jflag));
+ else {
+ struct json_object *jobj;
+
+ jobj = json_object_array_get_idx(jarray, 0);
+ printf("%s\n", json_object_to_json_string_ext(jobj, jflag));
+ }
+ json_object_put(jarray);
+}
+
+int cmd_list(int argc, const char **argv)
+{
+ 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_BOOLEAN('B', "buses", &list.buses, "include bus info"),
+ OPT_BOOLEAN('D', "dimms", &list.dimms, "include dimm info"),
+ OPT_BOOLEAN('R', "regions", &list.regions,
+ "include region info"),
+ OPT_BOOLEAN('N', "namespaces", &list.namespaces,
+ "include namespace info (default)"),
+ OPT_BOOLEAN('i', "idle", &list.idle, "include idle devices"),
+ OPT_END(),
+ };
+ const char * const u[] = {
+ "ndctl list [<options>]",
+ NULL
+ };
+ struct json_object *jnamespaces = NULL;
+ struct json_object *jregions = NULL;
+ struct json_object *jdimms = NULL;
+ struct json_object *jbuses = NULL;
+ struct ndctl_ctx *ctx;
+ struct ndctl_bus *bus;
+ 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);
+
+ rc = ndctl_new(&ctx);
+ if (rc < 0)
+ return rc;
+
+ ndctl_bus_foreach(ctx, bus) {
+ struct json_object *jbus = NULL;
+ struct ndctl_region *region;
+ struct ndctl_dimm *dimm;
+
+ if (!util_bus_filter(bus, param.bus))
+ continue;
+
+ if (list.buses) {
+ if (!jbuses) {
+ jbuses = json_object_new_array();
+ if (!jbuses) {
+ fail("\n");
+ continue;
+ }
+ }
+
+ jbus = util_bus_to_json(bus);
+ if (!jbus) {
+ fail("\n");
+ continue;
+ }
+ json_object_array_add(jbuses, jbus);
+ }
+
+ ndctl_dimm_foreach(bus, dimm) {
+ struct json_object *jdimm;
+
+ /* are we emitting dimms? */
+ if (!list.dimms)
+ break;
+
+ if (!list.idle && !ndctl_dimm_is_enabled(dimm))
+ continue;
+
+ if (!jdimms) {
+ jdimms = json_object_new_array();
+ if (!jdimms) {
+ fail("\n");
+ continue;
+ }
+
+ if (jbus)
+ json_object_object_add(jbus, "dimms", jdimms);
+ }
+
+ jdimm = util_dimm_to_json(dimm);
+ if (!jdimm) {
+ fail("\n");
+ continue;
+ }
+
+ /*
+ * Without a bus we are collecting dimms anonymously
+ * across the platform.
+ */
+ json_object_array_add(jdimms, jdimm);
+ }
+
+ ndctl_region_foreach(bus, region) {
+ struct json_object *jregion;
+
+ if (!util_region_filter(region, param.region))
+ continue;
+
+ if (!list.regions) {
+ jnamespaces = list_namespaces(region, jbus,
+ jnamespaces, true);
+ continue;
+ }
+
+ if (!list.idle && !ndctl_region_is_enabled(region))
+ continue;
+
+ if (!jregions) {
+ jregions = json_object_new_array();
+ if (!jregions) {
+ fail("\n");
+ continue;
+ }
+
+ if (jbus)
+ json_object_object_add(jbus, "regions",
+ jregions);
+ }
+
+ jregion = region_to_json(region);
+ if (!jregion) {
+ fail("\n");
+ continue;
+ }
+
+ /*
+ * Without a bus we are collecting regions anonymously
+ * across the platform.
+ */
+ json_object_array_add(jregions, jregion);
+ }
+
+ if (jbuses) {
+ jdimms = NULL;
+ jregions = NULL;
+ jnamespaces = NULL;
+ }
+ }
+
+ if (jbuses)
+ display_array(jbuses);
+ 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)
+ json_object_object_add(jplatform, "namespaces",
+ jnamespaces);
+ printf("%s\n", json_object_to_json_string_ext(jplatform,
+ jflag));
+ json_object_put(jplatform);
+ } else if (jdimms)
+ display_array(jdimms);
+ else if (jregions)
+ display_array(jregions);
+ else if (jnamespaces)
+ display_array(jnamespaces);
+
+ if (did_fail)
+ return -ENOMEM;
+ return 0;
+}
@@ -16,6 +16,7 @@ int cmd_enable_region(int argc, const char **argv);
int cmd_disable_region(int argc, const char **argv);
int cmd_zero_labels(int argc, const char **argv);
int cmd_help(int argc, const char **argv);
+int cmd_list(int argc, const char **argv);
#ifdef ENABLE_TEST
int cmd_test(int argc, const char **argv);
#endif
@@ -82,6 +82,7 @@ AM_CONDITIONAL([ENABLE_DESTRUCTIVE], [test "x$enable_destructive" = "xyes"])
PKG_CHECK_MODULES([KMOD], [libkmod])
PKG_CHECK_MODULES([UDEV], [libudev])
PKG_CHECK_MODULES([UUID], [uuid])
+PKG_CHECK_MODULES([JSON], [json-c])
AC_ARG_ENABLE([local],
AS_HELP_STRING([--enable-local], [build with local ndctl.h @<:@default=system@:>@]),
@@ -21,6 +21,7 @@ BuildRequires: pkgconfig
BuildRequires: pkgconfig(libkmod)
BuildRequires: pkgconfig(libudev)
BuildRequires: pkgconfig(uuid)
+BuildRequires: pkgconfig(json-c)
%description
Utility library for managing the "libnvdimm" subsystem. The "libnvdimm"
@@ -30,6 +30,7 @@ static struct cmd_struct commands[] = {
{ "enable-region", cmd_enable_region },
{ "disable-region", cmd_disable_region },
{ "zero-labels", cmd_zero_labels },
+ { "list", cmd_list },
{ "help", cmd_help },
#ifdef ENABLE_TEST
{ "test", cmd_test },
new file mode 100644
@@ -0,0 +1,208 @@
+#include <util/json.h>
+#include <uuid/uuid.h>
+#include <json-c/json.h>
+#include <ndctl/libndctl.h>
+#include <ccan/array_size/array_size.h>
+
+#ifdef HAVE_NDCTL_H
+#include <linux/ndctl.h>
+#else
+#include <ndctl.h>
+#endif
+
+struct json_object *util_bus_to_json(struct ndctl_bus *bus)
+{
+ struct json_object *jbus = json_object_new_object();
+ struct json_object *jobj;
+
+ if (!jbus)
+ return NULL;
+
+ jobj = json_object_new_string(ndctl_bus_get_provider(bus));
+ if (!jobj)
+ goto err;
+ json_object_object_add(jbus, "provider", jobj);
+
+ jobj = json_object_new_string(ndctl_bus_get_devname(bus));
+ if (!jobj)
+ goto err;
+ json_object_object_add(jbus, "dev", jobj);
+
+ return jbus;
+ err:
+ json_object_put(jbus);
+ return NULL;
+}
+
+struct json_object *util_dimm_to_json(struct ndctl_dimm *dimm)
+{
+ struct json_object *jdimm = json_object_new_object();
+ struct json_object *jobj;
+
+ if (!jdimm)
+ return NULL;
+
+ jobj = json_object_new_string(ndctl_dimm_get_devname(dimm));
+ if (!jobj)
+ goto err;
+ json_object_object_add(jdimm, "dev", jobj);
+
+ if (!ndctl_dimm_is_enabled(dimm)) {
+ jobj = json_object_new_string("disabled");
+ if (!jobj)
+ goto err;
+ json_object_object_add(jdimm, "state", jobj);
+ }
+
+ return jdimm;
+ err:
+ json_object_put(jdimm);
+ return NULL;
+}
+
+bool util_namespace_active(struct ndctl_namespace *ndns)
+{
+ struct ndctl_btt *btt = ndctl_namespace_get_btt(ndns);
+ struct ndctl_pfn *pfn = ndctl_namespace_get_pfn(ndns);
+
+ if ((btt && ndctl_btt_is_enabled(btt))
+ || (pfn && ndctl_pfn_is_enabled(pfn))
+ || (!btt && !pfn
+ && ndctl_namespace_is_enabled(ndns)))
+ return true;
+ return false;
+}
+
+struct json_object *util_namespace_to_json(struct ndctl_namespace *ndns)
+{
+ struct json_object *jndns = json_object_new_object();
+ struct json_object *jobj;
+ const char *bdev = NULL;
+ struct ndctl_btt *btt;
+ struct ndctl_pfn *pfn;
+ char buf[40];
+ uuid_t uuid;
+
+ if (!jndns)
+ return NULL;
+
+ jobj = json_object_new_string(ndctl_namespace_get_devname(ndns));
+ if (!jobj)
+ goto err;
+ json_object_object_add(jndns, "dev", jobj);
+
+ switch (ndctl_namespace_get_mode(ndns)) {
+ case NDCTL_NS_MODE_MEMORY:
+ jobj = json_object_new_string("memory");
+ break;
+ case NDCTL_NS_MODE_SAFE:
+ jobj = json_object_new_string("sector");
+ break;
+ case NDCTL_NS_MODE_RAW:
+ jobj = json_object_new_string("raw");
+ break;
+ default:
+ jobj = NULL;
+ }
+ if (jobj)
+ json_object_object_add(jndns, "mode", jobj);
+
+ jobj = json_object_new_int64(ndctl_namespace_get_size(ndns));
+ if (!jobj)
+ goto err;
+ json_object_object_add(jndns, "size", jobj);
+
+ btt = ndctl_namespace_get_btt(ndns);
+ pfn = ndctl_namespace_get_pfn(ndns);
+ if (btt) {
+ ndctl_btt_get_uuid(btt, uuid);
+ uuid_unparse(uuid, buf);
+ jobj = json_object_new_string(buf);
+ if (!jobj)
+ goto err;
+ json_object_object_add(jndns, "uuid", jobj);
+
+ jobj = json_object_new_int(ndctl_btt_get_sector_size(btt));
+ if (!jobj)
+ goto err;
+ json_object_object_add(jndns, "sector_size", jobj);
+
+ bdev = ndctl_btt_get_block_device(btt);
+ } else if (pfn) {
+ ndctl_pfn_get_uuid(pfn, uuid);
+ uuid_unparse(uuid, buf);
+ jobj = json_object_new_string(buf);
+ if (!jobj)
+ goto err;
+ json_object_object_add(jndns, "uuid", jobj);
+ bdev = ndctl_pfn_get_block_device(pfn);
+ } else if (ndctl_namespace_get_type(ndns) != ND_DEVICE_NAMESPACE_IO) {
+ const char *name;
+
+ ndctl_namespace_get_uuid(ndns, uuid);
+ uuid_unparse(uuid, buf);
+ jobj = json_object_new_string(buf);
+ if (!jobj)
+ goto err;
+ json_object_object_add(jndns, "uuid", jobj);
+
+ name = ndctl_namespace_get_alt_name(ndns);
+ if (name[0]) {
+ jobj = json_object_new_string(name);
+ if (!jobj)
+ goto err;
+ json_object_object_add(jndns, "name", jobj);
+ }
+ bdev = ndctl_namespace_get_block_device(ndns);
+ } else
+ bdev = ndctl_namespace_get_block_device(ndns);
+
+ if (bdev && bdev[0]) {
+ jobj = json_object_new_string(bdev);
+ if (!jobj)
+ goto err;
+ json_object_object_add(jndns, "blockdev", jobj);
+ }
+
+ if (!util_namespace_active(ndns)) {
+ jobj = json_object_new_string("disabled");
+ if (!jobj)
+ goto err;
+ json_object_object_add(jndns, "state", jobj);
+ }
+
+ return jndns;
+ err:
+ json_object_put(jndns);
+ return NULL;
+}
+
+struct json_object *util_mapping_to_json(struct ndctl_mapping *mapping)
+{
+ struct json_object *jmapping = json_object_new_object();
+ struct ndctl_dimm *dimm = ndctl_mapping_get_dimm(mapping);
+ struct json_object *jobj;
+
+ if (!jmapping)
+ return NULL;
+
+ jobj = json_object_new_string(ndctl_dimm_get_devname(dimm));
+ if (!jobj)
+ goto err;
+ json_object_object_add(jmapping, "dimm", jobj);
+
+ jobj = json_object_new_int64(ndctl_mapping_get_offset(mapping));
+ if (!jobj)
+ goto err;
+ json_object_object_add(jmapping, "offset", jobj);
+
+ jobj = json_object_new_int64(ndctl_mapping_get_length(mapping));
+ if (!jobj)
+ goto err;
+ json_object_object_add(jmapping, "length", jobj);
+
+ return jmapping;
+ err:
+ json_object_put(jmapping);
+ return NULL;
+}
new file mode 100644
@@ -0,0 +1,11 @@
+#ifndef __NDCTL_JSON_H__
+#define __NDCTL_JSON_H__
+#include <stdbool.h>
+#include <ndctl/libndctl.h>
+
+bool util_namespace_active(struct ndctl_namespace *ndns);
+struct json_object *util_bus_to_json(struct ndctl_bus *bus);
+struct json_object *util_dimm_to_json(struct ndctl_dimm *dimm);
+struct json_object *util_mapping_to_json(struct ndctl_mapping *mapping);
+struct json_object *util_namespace_to_json(struct ndctl_namespace *ndns);
+#endif /* __NDCTL_JSON_H__ */
Dump the nvdimm device topology (buses, dimms, regions, namespaces) and related attributes in json format. Signed-off-by: Dan Williams <dan.j.williams@intel.com> --- Documentation/Makefile.am | 3 Documentation/ndctl-list.txt | 86 ++++++++++ Makefile.am | 7 + builtin-list.c | 367 ++++++++++++++++++++++++++++++++++++++++++ builtin.h | 1 configure.ac | 1 contrib/ndctl.spec.in | 1 ndctl.c | 1 util/json.c | 208 ++++++++++++++++++++++++ util/json.h | 11 + 10 files changed, 683 insertions(+), 3 deletions(-) create mode 100644 Documentation/ndctl-list.txt create mode 100644 builtin-list.c create mode 100644 util/json.c create mode 100644 util/json.h