All of lore.kernel.org
 help / color / mirror / Atom feed
From: Gris Ge <fge@redhat.com>
To: dm-devel@redhat.com
Cc: Gris Ge <fge@redhat.com>
Subject: [PATCH V3 3/3] multipath-tools: Introducing multipath C API <libdmmp/libdmmp.h>
Date: Fri,  1 Jul 2016 21:06:13 +0800	[thread overview]
Message-ID: <20160701130613.209313-1-fge@redhat.com> (raw)
In-Reply-To: <20160701124656.169890-1-fge@redhat.com>

Features:

 * Use mpath_cmd.h for IPC connection and use output of 'show maps json'.
 * Library user guide will be 'man 3 libdmmp.h'.
 * Every public function has its own manpage in section 3 which is
   generated by linux 'kernel-doc' tool.

Usage:

    make -j5
    sudo make install \
            bindir=/usr/sbin/ \
            syslibdir=/usr/lib64/ \
            libdir=/usr/lib64/multipath \
            rcdir=/etc/rc.d/init.d \
            unitdir=/usr/lib/systemd/system \
            includedir=/usr/include
    make -C libdmmp check
    make -C libdmmp speed_test

    man libdmmp.h
    man dmmp_mpath_array_get
    man <dmmp function name>

Performance:

 * 10k scsi_debug sdX with 2 disks per mpath (i7-3520M 8GiB RAM):
   $ make -C libdmmp speed_test
   Got 5000 mpath
   real 3.37
   user 0.19
   sys 0.02

Misc:
 * Developer note is libdmmp/DEV_NOTES.

Signed-off-by: Gris Ge <fge@redhat.com>
---
 .gitignore                        |    4 +
 Makefile                          |    1 +
 Makefile.inc                      |    3 +
 libdmmp/DEV_NOTES                 |   41 +
 libdmmp/Makefile                  |   78 +
 libdmmp/docs/kernel-doc           | 2971 +++++++++++++++++++++++++++++++++++++
 libdmmp/docs/libdmmp.h.3          |  113 ++
 libdmmp/docs/split-man.pl         |   41 +
 libdmmp/libdmmp.c                 |  272 ++++
 libdmmp/libdmmp.pc.in             |    9 +
 libdmmp/libdmmp/libdmmp.h         |  607 ++++++++
 libdmmp/libdmmp_misc.c            |   87 ++
 libdmmp/libdmmp_mp.c              |  150 ++
 libdmmp/libdmmp_path.c            |  115 ++
 libdmmp/libdmmp_pg.c              |  208 +++
 libdmmp/libdmmp_private.h         |  208 +++
 libdmmp/test/Makefile             |   30 +
 libdmmp/test/libdmmp_speed_test.c |   49 +
 libdmmp/test/libdmmp_test.c       |  144 ++
 19 files changed, 5131 insertions(+)
 create mode 100644 libdmmp/DEV_NOTES
 create mode 100644 libdmmp/Makefile
 create mode 100644 libdmmp/docs/kernel-doc
 create mode 100644 libdmmp/docs/libdmmp.h.3
 create mode 100644 libdmmp/docs/split-man.pl
 create mode 100644 libdmmp/libdmmp.c
 create mode 100644 libdmmp/libdmmp.pc.in
 create mode 100644 libdmmp/libdmmp/libdmmp.h
 create mode 100644 libdmmp/libdmmp_misc.c
 create mode 100644 libdmmp/libdmmp_mp.c
 create mode 100644 libdmmp/libdmmp_path.c
 create mode 100644 libdmmp/libdmmp_pg.c
 create mode 100644 libdmmp/libdmmp_private.h
 create mode 100644 libdmmp/test/Makefile
 create mode 100644 libdmmp/test/libdmmp_speed_test.c
 create mode 100644 libdmmp/test/libdmmp_test.c

diff --git a/.gitignore b/.gitignore
index 7f25d0e..a805191 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,7 @@ multipath/multipath
 multipathd/multipathd
 mpathpersist/mpathpersist
 .nfs*
+libdmmp/docs/man/*.3.gz
+libdmmp/*.so.*
+libdmmp/test/libdmmp_test
+libdmmp/test/libdmmp_speed_test
diff --git a/Makefile b/Makefile
index 06f50c8..c989d51 100644
--- a/Makefile
+++ b/Makefile
@@ -25,6 +25,7 @@ BUILDDIRS = \
 	libmultipath/prioritizers \
 	libmultipath/checkers \
 	libmpathpersist \
+	libdmmp \
 	multipath \
 	multipathd \
 	mpathpersist \
diff --git a/Makefile.inc b/Makefile.inc
index d7fc444..52e4905 100644
--- a/Makefile.inc
+++ b/Makefile.inc
@@ -55,6 +55,9 @@ libdir	    = $(prefix)/$(LIB)/multipath
 unitdir     = $(prefix)/$(SYSTEMDPATH)/systemd/system
 mpathpersistdir = $(TOPDIR)/libmpathpersist
 mpathcmddir = $(TOPDIR)/libmpathcmd
+libdmmpdir  = $(TOPDIR)/libdmmp
+includedir  = $(prefix)/usr/include
+pkgconfdir  = $(prefix)/usr/share/pkgconfig
 
 GZIP        = gzip -9 -c
 INSTALL_PROGRAM = install
diff --git a/libdmmp/DEV_NOTES b/libdmmp/DEV_NOTES
new file mode 100644
index 0000000..220a9f4
--- /dev/null
+++ b/libdmmp/DEV_NOTES
@@ -0,0 +1,41 @@
+== Planed features ==
+ * Expose all properties used by /usr/bin/multipath
+
+== Code style ==
+ * Keep things as simple as possible.
+ * Linux Kernel code style.
+ * Don't use typedef.
+ * Don't use enum.
+ * We are not smarter than API user, so don't create wrapping function like:
+
+    ```
+    dmmp_mpath_search_by_id(struct dmmp_context *ctx,
+                            struct dmmp_mpath **dmmp_mp,
+                            uint32_t dmmp_mp_count, const char *id)
+
+    dmmp_path_group_id_search(struct dmmp_mpath *dmmp_mp,
+                              const char *blk_name)
+    ```
+ * The performance is the same for query single mpath and query all mpaths,
+   so no `dmmp_mpath_of_wwid(struct dmmp_context *ctx, const char *wwid)` yet.
+
+== Naming scheme ==
+ * Public constants should be named as `DMMP_XXX_YYY`.
+ * Public functions should be named as `dmmp_<noun>_<verb>`.
+ * Private constants should be named as `_DMMP_XXX_YYY`.
+ * Private functions should be named as `_dmmp_<noun>_<verb>`.
+
+== Code Layout ==
+ * libdmmp_private.h
+    Internal functions or macros.
+ * libdmmp.c
+    Handling multipathd IPC and generate dmmp_context and
+    dmmp_mpath_array_get().
+ * libdmmp_mp.c
+    For `struct dmmp_mpath`
+ * libdmmp_pg.c
+    For `struct dmmp_path_group`
+ * libdmmp_path.c
+    For `struct dmmp_path`
+ * libdmmp_misc.c
+    Misc functions.
diff --git a/libdmmp/Makefile b/libdmmp/Makefile
new file mode 100644
index 0000000..ce7ca3f
--- /dev/null
+++ b/libdmmp/Makefile
@@ -0,0 +1,78 @@
+# Makefile
+#
+# Copyright (C) 2015 - 2016 Red Hat, Inc.
+# Gris Ge <fge@redhat.com>
+#
+include ../Makefile.inc
+
+LIBDMMP_VERSION=0.1.0
+SONAME=$(LIBDMMP_VERSION)
+DEVLIB = libdmmp.so
+LIBS = $(DEVLIB).$(SONAME)
+LIBDEPS = -pthread
+PKGFILE = libdmmp.pc
+EXTRA_MAN_FILES = libdmmp.h.3
+HEADERS = libdmmp/libdmmp.h
+OBJS = libdmmp.o libdmmp_mp.o libdmmp_pg.o libdmmp_path.o libdmmp_misc.o
+
+CFLAGS += -fvisibility=hidden -I$(libdmmpdir) -I$(mpathcmddir) \
+	  $(shell pkg-config --cflags json-c)
+LDFLAGS += $(shell pkg-config --libs json-c) -L$(mpathcmddir) -lmpathcmd
+
+all: $(LIBS) doc
+
+$(LIBS): $(OBJS)
+	$(CC) $(LDFLAGS) $(SHARED_FLAGS) \
+	-Wl,-soname=$@ $(CFLAGS) -o $@ $(OBJS) $(LIBDEPS)
+	ln -sf $@ $(DEVLIB)
+
+install:
+	$(INSTALL_PROGRAM) -m 755 $(LIBS) $(DESTDIR)$(syslibdir)/$(LIBS)
+	$(INSTALL_PROGRAM) -m 644 -D \
+		$(HEADERS) $(DESTDIR)/$(includedir)/$(HEADERS)
+	ln -sf $(LIBS) $(DESTDIR)/$(syslibdir)/$(DEVLIB)
+	$(INSTALL_PROGRAM) -m 644 -D \
+		$(PKGFILE).in $(DESTDIR)/$(pkgconfdir)/$(PKGFILE)
+	perl -i -pe 's|__VERSION__|$(LIBDMMP_VERSION)|g' \
+		$(DESTDIR)/$(pkgconfdir)/$(PKGFILE)
+	perl -i -pe 's|__LIBDIR__|$(syslibdir)|g' \
+		$(DESTDIR)/$(pkgconfdir)/$(PKGFILE)
+	perl -i -pe 's|__INCLUDEDIR__|$(includedir)|g' \
+		$(DESTDIR)/$(pkgconfdir)/$(PKGFILE)
+	@for file in docs/man/*.3.gz; do \
+		$(INSTALL_PROGRAM) -m 644 -D \
+			$$file \
+			$(DESTDIR)/$(man3dir)/ || exit $?; \
+	done
+
+uninstall:
+	rm -f $(DESTDIR)$(syslibdir)/$(LIBS)
+	rm -f $(DESTDIR)$(includedir)/$(HEADERS)
+	rm -f $(DESTDIR)/$(syslibdir)/$(DEVLIB)
+	@for file in $(DESTDIR)/$(man3dir)/dmmp_*; do \
+		rm $$file; \
+	done
+	rm -f $(DESTDIR)$(man3dir)/libdmmp.h*
+
+clean:
+	rm -f core *.a *.o *.gz *.so *.so.*
+	rm -f docs/man/*.3.gz
+	$(MAKE) -C test clean
+
+check: all
+	$(MAKE) -C test check
+
+speed_test: all
+	$(MAKE) -C test speed_test
+
+doc: docs/man/$(EXTRA_MAN_FILES).gz
+
+docs/man/$(EXTRA_MAN_FILES).gz: $(HEADERS)
+	@for file in $(EXTRA_MAN_FILES); do \
+		$(INSTALL_PROGRAM) -v -m 644 -D docs/$$file docs/man/$$file; \
+	done
+	perl docs/kernel-doc -man $(HEADERS) | perl docs/split-man.pl docs/man
+	@for file in docs/man/*.3; do \
+		gzip -f $$file; \
+	done
+	find docs/man -type f -name \*[0-9].gz
diff --git a/libdmmp/docs/kernel-doc b/libdmmp/docs/kernel-doc
new file mode 100644
index 0000000..2fc8fad
--- /dev/null
+++ b/libdmmp/docs/kernel-doc
@@ -0,0 +1,2971 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+## Copyright (c) 1998 Michael Zucchi, All Rights Reserved        ##
+## Copyright (C) 2000, 1  Tim Waugh <twaugh@redhat.com>          ##
+## Copyright (C) 2001  Simon Huggins                             ##
+## Copyright (C) 2005-2012  Randy Dunlap                         ##
+## Copyright (C) 2012  Dan Luedtke                               ##
+## 								 ##
+## #define enhancements by Armin Kuster <akuster@mvista.com>	 ##
+## Copyright (c) 2000 MontaVista Software, Inc.			 ##
+## 								 ##
+## This software falls under the GNU General Public License.     ##
+## Please read the COPYING file for more information             ##
+
+# 18/01/2001 - 	Cleanups
+# 		Functions prototyped as foo(void) same as foo()
+# 		Stop eval'ing where we don't need to.
+# -- huggie@earth.li
+
+# 27/06/2001 -  Allowed whitespace after initial "/**" and
+#               allowed comments before function declarations.
+# -- Christian Kreibich <ck@whoop.org>
+
+# Still to do:
+# 	- add perldoc documentation
+# 	- Look more closely at some of the scarier bits :)
+
+# 26/05/2001 - 	Support for separate source and object trees.
+#		Return error code.
+# 		Keith Owens <kaos@ocs.com.au>
+
+# 23/09/2001 - Added support for typedefs, structs, enums and unions
+#              Support for Context section; can be terminated using empty line
+#              Small fixes (like spaces vs. \s in regex)
+# -- Tim Jansen <tim@tjansen.de>
+
+# 25/07/2012 - Added support for HTML5
+# -- Dan Luedtke <mail@danrl.de>
+
+sub usage {
+    my $message = <<"EOF";
+Usage: $0 [OPTION ...] FILE ...
+
+Read C language source or header FILEs, extract embedded documentation comments,
+and print formatted documentation to standard output.
+
+The documentation comments are identified by "/**" opening comment mark. See
+Documentation/kernel-doc-nano-HOWTO.txt for the documentation comment syntax.
+
+Output format selection (mutually exclusive):
+  -docbook		Output DocBook format.
+  -html			Output HTML format.
+  -html5		Output HTML5 format.
+  -list			Output symbol list format. This is for use by docproc.
+  -man			Output troff manual page format. This is the default.
+  -rst			Output reStructuredText format.
+  -text			Output plain text format.
+
+Output selection (mutually exclusive):
+  -function NAME	Only output documentation for the given function(s)
+			or DOC: section title(s). All other functions and DOC:
+			sections are ignored. May be specified multiple times.
+  -nofunction NAME	Do NOT output documentation for the given function(s);
+			only output documentation for the other functions and
+			DOC: sections. May be specified multiple times.
+
+Output selection modifiers:
+  -no-doc-sections	Do not output DOC: sections.
+
+Other parameters:
+  -v			Verbose output, more warnings and other information.
+  -h			Print this help.
+
+EOF
+    print $message;
+    exit 1;
+}
+
+#
+# format of comments.
+# In the following table, (...)? signifies optional structure.
+#                         (...)* signifies 0 or more structure elements
+# /**
+#  * function_name(:)? (- short description)?
+# (* @parameterx: (description of parameter x)?)*
+# (* a blank line)?
+#  * (Description:)? (Description of function)?
+#  * (section header: (section description)? )*
+#  (*)?*/
+#
+# So .. the trivial example would be:
+#
+# /**
+#  * my_function
+#  */
+#
+# If the Description: header tag is omitted, then there must be a blank line
+# after the last parameter specification.
+# e.g.
+# /**
+#  * my_function - does my stuff
+#  * @my_arg: its mine damnit
+#  *
+#  * Does my stuff explained.
+#  */
+#
+#  or, could also use:
+# /**
+#  * my_function - does my stuff
+#  * @my_arg: its mine damnit
+#  * Description: Does my stuff explained.
+#  */
+# etc.
+#
+# Besides functions you can also write documentation for structs, unions,
+# enums and typedefs. Instead of the function name you must write the name
+# of the declaration;  the struct/union/enum/typedef must always precede
+# the name. Nesting of declarations is not supported.
+# Use the argument mechanism to document members or constants.
+# e.g.
+# /**
+#  * struct my_struct - short description
+#  * @a: first member
+#  * @b: second member
+#  *
+#  * Longer description
+#  */
+# struct my_struct {
+#     int a;
+#     int b;
+# /* private: */
+#     int c;
+# };
+#
+# All descriptions can be multiline, except the short function description.
+#
+# For really longs structs, you can also describe arguments inside the
+# body of the struct.
+# eg.
+# /**
+#  * struct my_struct - short description
+#  * @a: first member
+#  * @b: second member
+#  *
+#  * Longer description
+#  */
+# struct my_struct {
+#     int a;
+#     int b;
+#     /**
+#      * @c: This is longer description of C
+#      *
+#      * You can use paragraphs to describe arguments
+#      * using this method.
+#      */
+#     int c;
+# };
+#
+# This should be use only for struct/enum members.
+#
+# You can also add additional sections. When documenting kernel functions you
+# should document the "Context:" of the function, e.g. whether the functions
+# can be called form interrupts. Unlike other sections you can end it with an
+# empty line.
+# A non-void function should have a "Return:" section describing the return
+# value(s).
+# Example-sections should contain the string EXAMPLE so that they are marked
+# appropriately in DocBook.
+#
+# Example:
+# /**
+#  * user_function - function that can only be called in user context
+#  * @a: some argument
+#  * Context: !in_interrupt()
+#  *
+#  * Some description
+#  * Example:
+#  *    user_function(22);
+#  */
+# ...
+#
+#
+# All descriptive text is further processed, scanning for the following special
+# patterns, which are highlighted appropriately.
+#
+# 'funcname()' - function
+# '$ENVVAR' - environmental variable
+# '&struct_name' - name of a structure (up to two words including 'struct')
+# '@parameter' - name of a parameter
+# '%CONST' - name of a constant.
+
+## init lots of data
+
+my $errors = 0;
+my $warnings = 0;
+my $anon_struct_union = 0;
+
+# match expressions used to find embedded type information
+my $type_constant = '\%([-_\w]+)';
+my $type_func = '(\w+)\(\)';
+my $type_param = '\@(\w+)';
+my $type_struct = '\&((struct\s*)*[_\w]+)';
+my $type_struct_xml = '\\&amp;((struct\s*)*[_\w]+)';
+my $type_env = '(\$\w+)';
+my $type_enum_full = '\&(enum)\s*([_\w]+)';
+my $type_struct_full = '\&(struct)\s*([_\w]+)';
+
+# Output conversion substitutions.
+#  One for each output format
+
+# these work fairly well
+my @highlights_html = (
+                       [$type_constant, "<i>\$1</i>"],
+                       [$type_func, "<b>\$1</b>"],
+                       [$type_struct_xml, "<i>\$1</i>"],
+                       [$type_env, "<b><i>\$1</i></b>"],
+                       [$type_param, "<tt><b>\$1</b></tt>"]
+                      );
+my $local_lt = "\\\\\\\\lt:";
+my $local_gt = "\\\\\\\\gt:";
+my $blankline_html = $local_lt . "p" . $local_gt;	# was "<p>"
+
+# html version 5
+my @highlights_html5 = (
+                        [$type_constant, "<span class=\"const\">\$1</span>"],
+                        [$type_func, "<span class=\"func\">\$1</span>"],
+                        [$type_struct_xml, "<span class=\"struct\">\$1</span>"],
+                        [$type_env, "<span class=\"env\">\$1</span>"],
+                        [$type_param, "<span class=\"param\">\$1</span>]"]
+		       );
+my $blankline_html5 = $local_lt . "br /" . $local_gt;
+
+# XML, docbook format
+my @highlights_xml = (
+                      ["([^=])\\\"([^\\\"<]+)\\\"", "\$1<quote>\$2</quote>"],
+                      [$type_constant, "<constant>\$1</constant>"],
+                      [$type_struct_xml, "<structname>\$1</structname>"],
+                      [$type_param, "<parameter>\$1</parameter>"],
+                      [$type_func, "<function>\$1</function>"],
+                      [$type_env, "<envar>\$1</envar>"]
+		     );
+my $blankline_xml = $local_lt . "/para" . $local_gt . $local_lt . "para" . $local_gt . "\n";
+
+# gnome, docbook format
+my @highlights_gnome = (
+                        [$type_constant, "<replaceable class=\"option\">\$1</replaceable>"],
+                        [$type_func, "<function>\$1</function>"],
+                        [$type_struct, "<structname>\$1</structname>"],
+                        [$type_env, "<envar>\$1</envar>"],
+                        [$type_param, "<parameter>\$1</parameter>" ]
+		       );
+my $blankline_gnome = "</para><para>\n";
+
+# these are pretty rough
+my @highlights_man = (
+                      [$type_constant, "\$1"],
+                      [$type_func, "\\\\fB\$1\\\\fP"],
+                      [$type_struct, "\\\\fI\$1\\\\fP"],
+                      [$type_param, "\\\\fI\$1\\\\fP"]
+		     );
+my $blankline_man = "";
+
+# text-mode
+my @highlights_text = (
+                       [$type_constant, "\$1"],
+                       [$type_func, "\$1"],
+                       [$type_struct, "\$1"],
+                       [$type_param, "\$1"]
+		      );
+my $blankline_text = "";
+
+# rst-mode
+my @highlights_rst = (
+                       [$type_constant, "``\$1``"],
+                       [$type_func, "\\:c\\:func\\:`\$1`"],
+                       [$type_struct_full, "\\:c\\:type\\:`\$1 \$2 <\$2>`"],
+                       [$type_enum_full, "\\:c\\:type\\:`\$1 \$2 <\$2>`"],
+                       [$type_struct, "\\:c\\:type\\:`struct \$1 <\$1>`"],
+                       [$type_param, "**\$1**"]
+		      );
+my $blankline_rst = "\n";
+
+# list mode
+my @highlights_list = (
+                       [$type_constant, "\$1"],
+                       [$type_func, "\$1"],
+                       [$type_struct, "\$1"],
+                       [$type_param, "\$1"]
+		      );
+my $blankline_list = "";
+
+# read arguments
+if ($#ARGV == -1) {
+    usage();
+}
+
+my $kernelversion;
+my $dohighlight = "";
+
+my $verbose = 0;
+my $output_mode = "man";
+my $output_preformatted = 0;
+my $no_doc_sections = 0;
+my @highlights = @highlights_man;
+my $blankline = $blankline_man;
+my $modulename = "Kernel API";
+my $function_only = 0;
+my $show_not_found = 0;
+
+my @build_time;
+if (defined($ENV{'KBUILD_BUILD_TIMESTAMP'}) &&
+    (my $seconds = `date -d"${ENV{'KBUILD_BUILD_TIMESTAMP'}}" +%s`) ne '') {
+    @build_time = gmtime($seconds);
+} else {
+    @build_time = localtime;
+}
+
+my $man_date = ('January', 'February', 'March', 'April', 'May', 'June',
+		'July', 'August', 'September', 'October',
+		'November', 'December')[$build_time[4]] .
+  " " . ($build_time[5]+1900);
+
+# Essentially these are globals.
+# They probably want to be tidied up, made more localised or something.
+# CAVEAT EMPTOR!  Some of the others I localised may not want to be, which
+# could cause "use of undefined value" or other bugs.
+my ($function, %function_table, %parametertypes, $declaration_purpose);
+my ($type, $declaration_name, $return_type);
+my ($newsection, $newcontents, $prototype, $brcount, %source_map);
+
+if (defined($ENV{'KBUILD_VERBOSE'})) {
+	$verbose = "$ENV{'KBUILD_VERBOSE'}";
+}
+
+# Generated docbook code is inserted in a template at a point where
+# docbook v3.1 requires a non-zero sequence of RefEntry's; see:
+# http://www.oasis-open.org/docbook/documentation/reference/html/refentry.html
+# We keep track of number of generated entries and generate a dummy
+# if needs be to ensure the expanded template can be postprocessed
+# into html.
+my $section_counter = 0;
+
+my $lineprefix="";
+
+# states
+# 0 - normal code
+# 1 - looking for function name
+# 2 - scanning field start.
+# 3 - scanning prototype.
+# 4 - documentation block
+# 5 - gathering documentation outside main block
+my $state;
+my $in_doc_sect;
+
+# Split Doc State
+# 0 - Invalid (Before start or after finish)
+# 1 - Is started (the /** was found inside a struct)
+# 2 - The @parameter header was found, start accepting multi paragraph text.
+# 3 - Finished (the */ was found)
+# 4 - Error - Comment without header was found. Spit a warning as it's not
+#     proper kernel-doc and ignore the rest.
+my $split_doc_state;
+
+#declaration types: can be
+# 'function', 'struct', 'union', 'enum', 'typedef'
+my $decl_type;
+
+my $doc_special = "\@\%\$\&";
+
+my $doc_start = '^/\*\*\s*$'; # Allow whitespace at end of comment start.
+my $doc_end = '\*/';
+my $doc_com = '\s*\*\s*';
+my $doc_com_body = '\s*\* ?';
+my $doc_decl = $doc_com . '(\w+)';
+my $doc_sect = $doc_com . '([' . $doc_special . ']?[\w\s]+):(.*)';
+my $doc_content = $doc_com_body . '(.*)';
+my $doc_block = $doc_com . 'DOC:\s*(.*)?';
+my $doc_split_start = '^\s*/\*\*\s*$';
+my $doc_split_sect = '\s*\*\s*(@[\w\s]+):(.*)';
+my $doc_split_end = '^\s*\*/\s*$';
+
+my %constants;
+my %parameterdescs;
+my @parameterlist;
+my %sections;
+my @sectionlist;
+my $sectcheck;
+my $struct_actual;
+
+my $contents = "";
+my $section_default = "Description";	# default section
+my $section_intro = "Introduction";
+my $section = $section_default;
+my $section_context = "Context";
+my $section_return = "Return";
+
+my $undescribed = "-- undescribed --";
+
+reset_state();
+
+while ($ARGV[0] =~ m/^-(.*)/) {
+    my $cmd = shift @ARGV;
+    if ($cmd eq "-html") {
+	$output_mode = "html";
+	@highlights = @highlights_html;
+	$blankline = $blankline_html;
+    } elsif ($cmd eq "-html5") {
+	$output_mode = "html5";
+	@highlights = @highlights_html5;
+	$blankline = $blankline_html5;
+    } elsif ($cmd eq "-man") {
+	$output_mode = "man";
+	@highlights = @highlights_man;
+	$blankline = $blankline_man;
+    } elsif ($cmd eq "-text") {
+	$output_mode = "text";
+	@highlights = @highlights_text;
+	$blankline = $blankline_text;
+    } elsif ($cmd eq "-rst") {
+	$output_mode = "rst";
+	@highlights = @highlights_rst;
+	$blankline = $blankline_rst;
+    } elsif ($cmd eq "-docbook") {
+	$output_mode = "xml";
+	@highlights = @highlights_xml;
+	$blankline = $blankline_xml;
+    } elsif ($cmd eq "-list") {
+	$output_mode = "list";
+	@highlights = @highlights_list;
+	$blankline = $blankline_list;
+    } elsif ($cmd eq "-gnome") {
+	$output_mode = "gnome";
+	@highlights = @highlights_gnome;
+	$blankline = $blankline_gnome;
+    } elsif ($cmd eq "-module") { # not needed for XML, inherits from calling document
+	$modulename = shift @ARGV;
+    } elsif ($cmd eq "-function") { # to only output specific functions
+	$function_only = 1;
+	$function = shift @ARGV;
+	$function_table{$function} = 1;
+    } elsif ($cmd eq "-nofunction") { # to only output specific functions
+	$function_only = 2;
+	$function = shift @ARGV;
+	$function_table{$function} = 1;
+    } elsif ($cmd eq "-v") {
+	$verbose = 1;
+    } elsif (($cmd eq "-h") || ($cmd eq "--help")) {
+	usage();
+    } elsif ($cmd eq '-no-doc-sections') {
+	    $no_doc_sections = 1;
+    } elsif ($cmd eq '-show-not-found') {
+	$show_not_found = 1;
+    }
+}
+
+# continue execution near EOF;
+
+# get kernel version from env
+sub get_kernel_version() {
+    my $version = 'unknown kernel version';
+
+    if (defined($ENV{'KERNELVERSION'})) {
+	$version = $ENV{'KERNELVERSION'};
+    }
+    return $version;
+}
+
+##
+# dumps section contents to arrays/hashes intended for that purpose.
+#
+sub dump_section {
+    my $file = shift;
+    my $name = shift;
+    my $contents = join "\n", @_;
+
+    if ($name =~ m/$type_constant/) {
+	$name = $1;
+#	print STDERR "constant section '$1' = '$contents'\n";
+	$constants{$name} = $contents;
+    } elsif ($name =~ m/$type_param/) {
+#	print STDERR "parameter def '$1' = '$contents'\n";
+	$name = $1;
+	$parameterdescs{$name} = $contents;
+	$sectcheck = $sectcheck . $name . " ";
+    } elsif ($name eq "@\.\.\.") {
+#	print STDERR "parameter def '...' = '$contents'\n";
+	$name = "...";
+	$parameterdescs{$name} = $contents;
+	$sectcheck = $sectcheck . $name . " ";
+    } else {
+#	print STDERR "other section '$name' = '$contents'\n";
+	if (defined($sections{$name}) && ($sections{$name} ne "")) {
+		print STDERR "${file}:$.: error: duplicate section name '$name'\n";
+		++$errors;
+	}
+	$sections{$name} = $contents;
+	push @sectionlist, $name;
+    }
+}
+
+##
+# dump DOC: section after checking that it should go out
+#
+sub dump_doc_section {
+    my $file = shift;
+    my $name = shift;
+    my $contents = join "\n", @_;
+
+    if ($no_doc_sections) {
+        return;
+    }
+
+    if (($function_only == 0) ||
+	( $function_only == 1 && defined($function_table{$name})) ||
+	( $function_only == 2 && !defined($function_table{$name})))
+    {
+	dump_section($file, $name, $contents);
+	output_blockhead({'sectionlist' => \@sectionlist,
+			  'sections' => \%sections,
+			  'module' => $modulename,
+			  'content-only' => ($function_only != 0), });
+    }
+}
+
+##
+# output function
+#
+# parameterdescs, a hash.
+#  function => "function name"
+#  parameterlist => @list of parameters
+#  parameterdescs => %parameter descriptions
+#  sectionlist => @list of sections
+#  sections => %section descriptions
+#
+
+sub output_highlight {
+    my $contents = join "\n",@_;
+    my $line;
+
+#   DEBUG
+#   if (!defined $contents) {
+#	use Carp;
+#	confess "output_highlight got called with no args?\n";
+#   }
+
+    if ($output_mode eq "html" || $output_mode eq "html5" ||
+	$output_mode eq "xml") {
+	$contents = local_unescape($contents);
+	# convert data read & converted thru xml_escape() into &xyz; format:
+	$contents =~ s/\\\\\\/\&/g;
+    }
+#   print STDERR "contents b4:$contents\n";
+    eval $dohighlight;
+    die $@ if $@;
+#   print STDERR "contents af:$contents\n";
+
+#   strip whitespaces when generating html5
+    if ($output_mode eq "html5") {
+	$contents =~ s/^\s+//;
+	$contents =~ s/\s+$//;
+    }
+    foreach $line (split "\n", $contents) {
+	if (! $output_preformatted) {
+	    $line =~ s/^\s*//;
+	}
+	if ($line eq ""){
+	    if (! $output_preformatted) {
+		print $lineprefix, local_unescape($blankline);
+	    }
+	} else {
+	    $line =~ s/\\\\\\/\&/g;
+	    if ($output_mode eq "man" && substr($line, 0, 1) eq ".") {
+		print "\\&$line";
+	    } else {
+		print $lineprefix, $line;
+	    }
+	}
+	print "\n";
+    }
+}
+
+# output sections in html
+sub output_section_html(%) {
+    my %args = %{$_[0]};
+    my $section;
+
+    foreach $section (@{$args{'sectionlist'}}) {
+	print "<h3>$section</h3>\n";
+	print "<blockquote>\n";
+	output_highlight($args{'sections'}{$section});
+	print "</blockquote>\n";
+    }
+}
+
+# output enum in html
+sub output_enum_html(%) {
+    my %args = %{$_[0]};
+    my ($parameter);
+    my $count;
+    print "<h2>enum " . $args{'enum'} . "</h2>\n";
+
+    print "<b>enum " . $args{'enum'} . "</b> {<br>\n";
+    $count = 0;
+    foreach $parameter (@{$args{'parameterlist'}}) {
+	print " <b>" . $parameter . "</b>";
+	if ($count != $#{$args{'parameterlist'}}) {
+	    $count++;
+	    print ",\n";
+	}
+	print "<br>";
+    }
+    print "};<br>\n";
+
+    print "<h3>Constants</h3>\n";
+    print "<dl>\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+	print "<dt><b>" . $parameter . "</b>\n";
+	print "<dd>";
+	output_highlight($args{'parameterdescs'}{$parameter});
+    }
+    print "</dl>\n";
+    output_section_html(@_);
+    print "<hr>\n";
+}
+
+# output typedef in html
+sub output_typedef_html(%) {
+    my %args = %{$_[0]};
+    my ($parameter);
+    my $count;
+    print "<h2>typedef " . $args{'typedef'} . "</h2>\n";
+
+    print "<b>typedef " . $args{'typedef'} . "</b>\n";
+    output_section_html(@_);
+    print "<hr>\n";
+}
+
+# output struct in html
+sub output_struct_html(%) {
+    my %args = %{$_[0]};
+    my ($parameter);
+
+    print "<h2>" . $args{'type'} . " " . $args{'struct'} . " - " . $args{'purpose'} . "</h2>\n";
+    print "<b>" . $args{'type'} . " " . $args{'struct'} . "</b> {<br>\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+	if ($parameter =~ /^#/) {
+		print "$parameter<br>\n";
+		next;
+	}
+	my $parameter_name = $parameter;
+	$parameter_name =~ s/\[.*//;
+
+	($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+	$type = $args{'parametertypes'}{$parameter};
+	if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+	    # pointer-to-function
+	    print "&nbsp; &nbsp; <i>$1</i><b>$parameter</b>) <i>($2)</i>;<br>\n";
+	} elsif ($type =~ m/^(.*?)\s*(:.*)/) {
+	    # bitfield
+	    print "&nbsp; &nbsp; <i>$1</i> <b>$parameter</b>$2;<br>\n";
+	} else {
+	    print "&nbsp; &nbsp; <i>$type</i> <b>$parameter</b>;<br>\n";
+	}
+    }
+    print "};<br>\n";
+
+    print "<h3>Members</h3>\n";
+    print "<dl>\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+	($parameter =~ /^#/) && next;
+
+	my $parameter_name = $parameter;
+	$parameter_name =~ s/\[.*//;
+
+	($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+	print "<dt><b>" . $parameter . "</b>\n";
+	print "<dd>";
+	output_highlight($args{'parameterdescs'}{$parameter_name});
+    }
+    print "</dl>\n";
+    output_section_html(@_);
+    print "<hr>\n";
+}
+
+# output function in html
+sub output_function_html(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $count;
+
+    print "<h2>" . $args{'function'} . " - " . $args{'purpose'} . "</h2>\n";
+    print "<i>" . $args{'functiontype'} . "</i>\n";
+    print "<b>" . $args{'function'} . "</b>\n";
+    print "(";
+    $count = 0;
+    foreach $parameter (@{$args{'parameterlist'}}) {
+	$type = $args{'parametertypes'}{$parameter};
+	if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+	    # pointer-to-function
+	    print "<i>$1</i><b>$parameter</b>) <i>($2)</i>";
+	} else {
+	    print "<i>" . $type . "</i> <b>" . $parameter . "</b>";
+	}
+	if ($count != $#{$args{'parameterlist'}}) {
+	    $count++;
+	    print ",\n";
+	}
+    }
+    print ")\n";
+
+    print "<h3>Arguments</h3>\n";
+    print "<dl>\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+	my $parameter_name = $parameter;
+	$parameter_name =~ s/\[.*//;
+
+	($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+	print "<dt><b>" . $parameter . "</b>\n";
+	print "<dd>";
+	output_highlight($args{'parameterdescs'}{$parameter_name});
+    }
+    print "</dl>\n";
+    output_section_html(@_);
+    print "<hr>\n";
+}
+
+# output DOC: block header in html
+sub output_blockhead_html(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $count;
+
+    foreach $section (@{$args{'sectionlist'}}) {
+	print "<h3>$section</h3>\n";
+	print "<ul>\n";
+	output_highlight($args{'sections'}{$section});
+	print "</ul>\n";
+    }
+    print "<hr>\n";
+}
+
+# output sections in html5
+sub output_section_html5(%) {
+    my %args = %{$_[0]};
+    my $section;
+
+    foreach $section (@{$args{'sectionlist'}}) {
+	print "<section>\n";
+	print "<h1>$section</h1>\n";
+	print "<p>\n";
+	output_highlight($args{'sections'}{$section});
+	print "</p>\n";
+	print "</section>\n";
+    }
+}
+
+# output enum in html5
+sub output_enum_html5(%) {
+    my %args = %{$_[0]};
+    my ($parameter);
+    my $count;
+    my $html5id;
+
+    $html5id = $args{'enum'};
+    $html5id =~ s/[^a-zA-Z0-9\-]+/_/g;
+    print "<article class=\"enum\" id=\"enum:". $html5id . "\">";
+    print "<h1>enum " . $args{'enum'} . "</h1>\n";
+    print "<ol class=\"code\">\n";
+    print "<li>";
+    print "<span class=\"keyword\">enum</span> ";
+    print "<span class=\"identifier\">" . $args{'enum'} . "</span> {";
+    print "</li>\n";
+    $count = 0;
+    foreach $parameter (@{$args{'parameterlist'}}) {
+	print "<li class=\"indent\">";
+	print "<span class=\"param\">" . $parameter . "</span>";
+	if ($count != $#{$args{'parameterlist'}}) {
+	    $count++;
+	    print ",";
+	}
+	print "</li>\n";
+    }
+    print "<li>};</li>\n";
+    print "</ol>\n";
+
+    print "<section>\n";
+    print "<h1>Constants</h1>\n";
+    print "<dl>\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+	print "<dt>" . $parameter . "</dt>\n";
+	print "<dd>";
+	output_highlight($args{'parameterdescs'}{$parameter});
+	print "</dd>\n";
+    }
+    print "</dl>\n";
+    print "</section>\n";
+    output_section_html5(@_);
+    print "</article>\n";
+}
+
+# output typedef in html5
+sub output_typedef_html5(%) {
+    my %args = %{$_[0]};
+    my ($parameter);
+    my $count;
+    my $html5id;
+
+    $html5id = $args{'typedef'};
+    $html5id =~ s/[^a-zA-Z0-9\-]+/_/g;
+    print "<article class=\"typedef\" id=\"typedef:" . $html5id . "\">\n";
+    print "<h1>typedef " . $args{'typedef'} . "</h1>\n";
+
+    print "<ol class=\"code\">\n";
+    print "<li>";
+    print "<span class=\"keyword\">typedef</span> ";
+    print "<span class=\"identifier\">" . $args{'typedef'} . "</span>";
+    print "</li>\n";
+    print "</ol>\n";
+    output_section_html5(@_);
+    print "</article>\n";
+}
+
+# output struct in html5
+sub output_struct_html5(%) {
+    my %args = %{$_[0]};
+    my ($parameter);
+    my $html5id;
+
+    $html5id = $args{'struct'};
+    $html5id =~ s/[^a-zA-Z0-9\-]+/_/g;
+    print "<article class=\"struct\" id=\"struct:" . $html5id . "\">\n";
+    print "<hgroup>\n";
+    print "<h1>" . $args{'type'} . " " . $args{'struct'} . "</h1>";
+    print "<h2>". $args{'purpose'} . "</h2>\n";
+    print "</hgroup>\n";
+    print "<ol class=\"code\">\n";
+    print "<li>";
+    print "<span class=\"type\">" . $args{'type'} . "</span> ";
+    print "<span class=\"identifier\">" . $args{'struct'} . "</span> {";
+    print "</li>\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+	print "<li class=\"indent\">";
+	if ($parameter =~ /^#/) {
+		print "<span class=\"param\">" . $parameter ."</span>\n";
+		print "</li>\n";
+		next;
+	}
+	my $parameter_name = $parameter;
+	$parameter_name =~ s/\[.*//;
+
+	($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+	$type = $args{'parametertypes'}{$parameter};
+	if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+	    # pointer-to-function
+	    print "<span class=\"type\">$1</span> ";
+	    print "<span class=\"param\">$parameter</span>";
+	    print "<span class=\"type\">)</span> ";
+	    print "(<span class=\"args\">$2</span>);";
+	} elsif ($type =~ m/^(.*?)\s*(:.*)/) {
+	    # bitfield
+	    print "<span class=\"type\">$1</span> ";
+	    print "<span class=\"param\">$parameter</span>";
+	    print "<span class=\"bits\">$2</span>;";
+	} else {
+	    print "<span class=\"type\">$type</span> ";
+	    print "<span class=\"param\">$parameter</span>;";
+	}
+	print "</li>\n";
+    }
+    print "<li>};</li>\n";
+    print "</ol>\n";
+
+    print "<section>\n";
+    print "<h1>Members</h1>\n";
+    print "<dl>\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+	($parameter =~ /^#/) && next;
+
+	my $parameter_name = $parameter;
+	$parameter_name =~ s/\[.*//;
+
+	($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+	print "<dt>" . $parameter . "</dt>\n";
+	print "<dd>";
+	output_highlight($args{'parameterdescs'}{$parameter_name});
+	print "</dd>\n";
+    }
+    print "</dl>\n";
+    print "</section>\n";
+    output_section_html5(@_);
+    print "</article>\n";
+}
+
+# output function in html5
+sub output_function_html5(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $count;
+    my $html5id;
+
+    $html5id = $args{'function'};
+    $html5id =~ s/[^a-zA-Z0-9\-]+/_/g;
+    print "<article class=\"function\" id=\"func:". $html5id . "\">\n";
+    print "<hgroup>\n";
+    print "<h1>" . $args{'function'} . "</h1>";
+    print "<h2>" . $args{'purpose'} . "</h2>\n";
+    print "</hgroup>\n";
+    print "<ol class=\"code\">\n";
+    print "<li>";
+    print "<span class=\"type\">" . $args{'functiontype'} . "</span> ";
+    print "<span class=\"identifier\">" . $args{'function'} . "</span> (";
+    print "</li>";
+    $count = 0;
+    foreach $parameter (@{$args{'parameterlist'}}) {
+	print "<li class=\"indent\">";
+	$type = $args{'parametertypes'}{$parameter};
+	if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+	    # pointer-to-function
+	    print "<span class=\"type\">$1</span> ";
+	    print "<span class=\"param\">$parameter</span>";
+	    print "<span class=\"type\">)</span> ";
+	    print "(<span class=\"args\">$2</span>)";
+	} else {
+	    print "<span class=\"type\">$type</span> ";
+	    print "<span class=\"param\">$parameter</span>";
+	}
+	if ($count != $#{$args{'parameterlist'}}) {
+	    $count++;
+	    print ",";
+	}
+	print "</li>\n";
+    }
+    print "<li>)</li>\n";
+    print "</ol>\n";
+
+    print "<section>\n";
+    print "<h1>Arguments</h1>\n";
+    print "<p>\n";
+    print "<dl>\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+	my $parameter_name = $parameter;
+	$parameter_name =~ s/\[.*//;
+
+	($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+	print "<dt>" . $parameter . "</dt>\n";
+	print "<dd>";
+	output_highlight($args{'parameterdescs'}{$parameter_name});
+	print "</dd>\n";
+    }
+    print "</dl>\n";
+    print "</section>\n";
+    output_section_html5(@_);
+    print "</article>\n";
+}
+
+# output DOC: block header in html5
+sub output_blockhead_html5(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $count;
+    my $html5id;
+
+    foreach $section (@{$args{'sectionlist'}}) {
+	$html5id = $section;
+	$html5id =~ s/[^a-zA-Z0-9\-]+/_/g;
+	print "<article class=\"doc\" id=\"doc:". $html5id . "\">\n";
+	print "<h1>$section</h1>\n";
+	print "<p>\n";
+	output_highlight($args{'sections'}{$section});
+	print "</p>\n";
+    }
+    print "</article>\n";
+}
+
+sub output_section_xml(%) {
+    my %args = %{$_[0]};
+    my $section;
+    # print out each section
+    $lineprefix="   ";
+    foreach $section (@{$args{'sectionlist'}}) {
+	print "<refsect1>\n";
+	print "<title>$section</title>\n";
+	if ($section =~ m/EXAMPLE/i) {
+	    print "<informalexample><programlisting>\n";
+	    $output_preformatted = 1;
+	} else {
+	    print "<para>\n";
+	}
+	output_highlight($args{'sections'}{$section});
+	$output_preformatted = 0;
+	if ($section =~ m/EXAMPLE/i) {
+	    print "</programlisting></informalexample>\n";
+	} else {
+	    print "</para>\n";
+	}
+	print "</refsect1>\n";
+    }
+}
+
+# output function in XML DocBook
+sub output_function_xml(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $count;
+    my $id;
+
+    $id = "API-" . $args{'function'};
+    $id =~ s/[^A-Za-z0-9]/-/g;
+
+    print "<refentry id=\"$id\">\n";
+    print "<refentryinfo>\n";
+    print " <title>LINUX</title>\n";
+    print " <productname>Kernel Hackers Manual</productname>\n";
+    print " <date>$man_date</date>\n";
+    print "</refentryinfo>\n";
+    print "<refmeta>\n";
+    print " <refentrytitle><phrase>" . $args{'function'} . "</phrase></refentrytitle>\n";
+    print " <manvolnum>9</manvolnum>\n";
+    print " <refmiscinfo class=\"version\">" . $kernelversion . "</refmiscinfo>\n";
+    print "</refmeta>\n";
+    print "<refnamediv>\n";
+    print " <refname>" . $args{'function'} . "</refname>\n";
+    print " <refpurpose>\n";
+    print "  ";
+    output_highlight ($args{'purpose'});
+    print " </refpurpose>\n";
+    print "</refnamediv>\n";
+
+    print "<refsynopsisdiv>\n";
+    print " <title>Synopsis</title>\n";
+    print "  <funcsynopsis><funcprototype>\n";
+    print "   <funcdef>" . $args{'functiontype'} . " ";
+    print "<function>" . $args{'function'} . " </function></funcdef>\n";
+
+    $count = 0;
+    if ($#{$args{'parameterlist'}} >= 0) {
+	foreach $parameter (@{$args{'parameterlist'}}) {
+	    $type = $args{'parametertypes'}{$parameter};
+	    if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+		# pointer-to-function
+		print "   <paramdef>$1<parameter>$parameter</parameter>)\n";
+		print "     <funcparams>$2</funcparams></paramdef>\n";
+	    } else {
+		print "   <paramdef>" . $type;
+		print " <parameter>$parameter</parameter></paramdef>\n";
+	    }
+	}
+    } else {
+	print "  <void/>\n";
+    }
+    print "  </funcprototype></funcsynopsis>\n";
+    print "</refsynopsisdiv>\n";
+
+    # print parameters
+    print "<refsect1>\n <title>Arguments</title>\n";
+    if ($#{$args{'parameterlist'}} >= 0) {
+	print " <variablelist>\n";
+	foreach $parameter (@{$args{'parameterlist'}}) {
+	    my $parameter_name = $parameter;
+	    $parameter_name =~ s/\[.*//;
+
+	    print "  <varlistentry>\n   <term><parameter>$parameter</parameter></term>\n";
+	    print "   <listitem>\n    <para>\n";
+	    $lineprefix="     ";
+	    output_highlight($args{'parameterdescs'}{$parameter_name});
+	    print "    </para>\n   </listitem>\n  </varlistentry>\n";
+	}
+	print " </variablelist>\n";
+    } else {
+	print " <para>\n  None\n </para>\n";
+    }
+    print "</refsect1>\n";
+
+    output_section_xml(@_);
+    print "</refentry>\n\n";
+}
+
+# output struct in XML DocBook
+sub output_struct_xml(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $id;
+
+    $id = "API-struct-" . $args{'struct'};
+    $id =~ s/[^A-Za-z0-9]/-/g;
+
+    print "<refentry id=\"$id\">\n";
+    print "<refentryinfo>\n";
+    print " <title>LINUX</title>\n";
+    print " <productname>Kernel Hackers Manual</productname>\n";
+    print " <date>$man_date</date>\n";
+    print "</refentryinfo>\n";
+    print "<refmeta>\n";
+    print " <refentrytitle><phrase>" . $args{'type'} . " " . $args{'struct'} . "</phrase></refentrytitle>\n";
+    print " <manvolnum>9</manvolnum>\n";
+    print " <refmiscinfo class=\"version\">" . $kernelversion . "</refmiscinfo>\n";
+    print "</refmeta>\n";
+    print "<refnamediv>\n";
+    print " <refname>" . $args{'type'} . " " . $args{'struct'} . "</refname>\n";
+    print " <refpurpose>\n";
+    print "  ";
+    output_highlight ($args{'purpose'});
+    print " </refpurpose>\n";
+    print "</refnamediv>\n";
+
+    print "<refsynopsisdiv>\n";
+    print " <title>Synopsis</title>\n";
+    print "  <programlisting>\n";
+    print $args{'type'} . " " . $args{'struct'} . " {\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+	if ($parameter =~ /^#/) {
+	    my $prm = $parameter;
+	    # convert data read & converted thru xml_escape() into &xyz; format:
+	    # This allows us to have #define macros interspersed in a struct.
+	    $prm =~ s/\\\\\\/\&/g;
+	    print "$prm\n";
+	    next;
+	}
+
+	my $parameter_name = $parameter;
+	$parameter_name =~ s/\[.*//;
+
+	defined($args{'parameterdescs'}{$parameter_name}) || next;
+	($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+	$type = $args{'parametertypes'}{$parameter};
+	if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+	    # pointer-to-function
+	    print "  $1 $parameter) ($2);\n";
+	} elsif ($type =~ m/^(.*?)\s*(:.*)/) {
+	    # bitfield
+	    print "  $1 $parameter$2;\n";
+	} else {
+	    print "  " . $type . " " . $parameter . ";\n";
+	}
+    }
+    print "};";
+    print "  </programlisting>\n";
+    print "</refsynopsisdiv>\n";
+
+    print " <refsect1>\n";
+    print "  <title>Members</title>\n";
+
+    if ($#{$args{'parameterlist'}} >= 0) {
+    print "  <variablelist>\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+      ($parameter =~ /^#/) && next;
+
+      my $parameter_name = $parameter;
+      $parameter_name =~ s/\[.*//;
+
+      defined($args{'parameterdescs'}{$parameter_name}) || next;
+      ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+      print "    <varlistentry>";
+      print "      <term>$parameter</term>\n";
+      print "      <listitem><para>\n";
+      output_highlight($args{'parameterdescs'}{$parameter_name});
+      print "      </para></listitem>\n";
+      print "    </varlistentry>\n";
+    }
+    print "  </variablelist>\n";
+    } else {
+	print " <para>\n  None\n </para>\n";
+    }
+    print " </refsect1>\n";
+
+    output_section_xml(@_);
+
+    print "</refentry>\n\n";
+}
+
+# output enum in XML DocBook
+sub output_enum_xml(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $count;
+    my $id;
+
+    $id = "API-enum-" . $args{'enum'};
+    $id =~ s/[^A-Za-z0-9]/-/g;
+
+    print "<refentry id=\"$id\">\n";
+    print "<refentryinfo>\n";
+    print " <title>LINUX</title>\n";
+    print " <productname>Kernel Hackers Manual</productname>\n";
+    print " <date>$man_date</date>\n";
+    print "</refentryinfo>\n";
+    print "<refmeta>\n";
+    print " <refentrytitle><phrase>enum " . $args{'enum'} . "</phrase></refentrytitle>\n";
+    print " <manvolnum>9</manvolnum>\n";
+    print " <refmiscinfo class=\"version\">" . $kernelversion . "</refmiscinfo>\n";
+    print "</refmeta>\n";
+    print "<refnamediv>\n";
+    print " <refname>enum " . $args{'enum'} . "</refname>\n";
+    print " <refpurpose>\n";
+    print "  ";
+    output_highlight ($args{'purpose'});
+    print " </refpurpose>\n";
+    print "</refnamediv>\n";
+
+    print "<refsynopsisdiv>\n";
+    print " <title>Synopsis</title>\n";
+    print "  <programlisting>\n";
+    print "enum " . $args{'enum'} . " {\n";
+    $count = 0;
+    foreach $parameter (@{$args{'parameterlist'}}) {
+	print "  $parameter";
+	if ($count != $#{$args{'parameterlist'}}) {
+	    $count++;
+	    print ",";
+	}
+	print "\n";
+    }
+    print "};";
+    print "  </programlisting>\n";
+    print "</refsynopsisdiv>\n";
+
+    print "<refsect1>\n";
+    print " <title>Constants</title>\n";
+    print "  <variablelist>\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+      my $parameter_name = $parameter;
+      $parameter_name =~ s/\[.*//;
+
+      print "    <varlistentry>";
+      print "      <term>$parameter</term>\n";
+      print "      <listitem><para>\n";
+      output_highlight($args{'parameterdescs'}{$parameter_name});
+      print "      </para></listitem>\n";
+      print "    </varlistentry>\n";
+    }
+    print "  </variablelist>\n";
+    print "</refsect1>\n";
+
+    output_section_xml(@_);
+
+    print "</refentry>\n\n";
+}
+
+# output typedef in XML DocBook
+sub output_typedef_xml(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $id;
+
+    $id = "API-typedef-" . $args{'typedef'};
+    $id =~ s/[^A-Za-z0-9]/-/g;
+
+    print "<refentry id=\"$id\">\n";
+    print "<refentryinfo>\n";
+    print " <title>LINUX</title>\n";
+    print " <productname>Kernel Hackers Manual</productname>\n";
+    print " <date>$man_date</date>\n";
+    print "</refentryinfo>\n";
+    print "<refmeta>\n";
+    print " <refentrytitle><phrase>typedef " . $args{'typedef'} . "</phrase></refentrytitle>\n";
+    print " <manvolnum>9</manvolnum>\n";
+    print "</refmeta>\n";
+    print "<refnamediv>\n";
+    print " <refname>typedef " . $args{'typedef'} . "</refname>\n";
+    print " <refpurpose>\n";
+    print "  ";
+    output_highlight ($args{'purpose'});
+    print " </refpurpose>\n";
+    print "</refnamediv>\n";
+
+    print "<refsynopsisdiv>\n";
+    print " <title>Synopsis</title>\n";
+    print "  <synopsis>typedef " . $args{'typedef'} . ";</synopsis>\n";
+    print "</refsynopsisdiv>\n";
+
+    output_section_xml(@_);
+
+    print "</refentry>\n\n";
+}
+
+# output in XML DocBook
+sub output_blockhead_xml(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $count;
+
+    my $id = $args{'module'};
+    $id =~ s/[^A-Za-z0-9]/-/g;
+
+    # print out each section
+    $lineprefix="   ";
+    foreach $section (@{$args{'sectionlist'}}) {
+	if (!$args{'content-only'}) {
+		print "<refsect1>\n <title>$section</title>\n";
+	}
+	if ($section =~ m/EXAMPLE/i) {
+	    print "<example><para>\n";
+	    $output_preformatted = 1;
+	} else {
+	    print "<para>\n";
+	}
+	output_highlight($args{'sections'}{$section});
+	$output_preformatted = 0;
+	if ($section =~ m/EXAMPLE/i) {
+	    print "</para></example>\n";
+	} else {
+	    print "</para>";
+	}
+	if (!$args{'content-only'}) {
+		print "\n</refsect1>\n";
+	}
+    }
+
+    print "\n\n";
+}
+
+# output in XML DocBook
+sub output_function_gnome {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $count;
+    my $id;
+
+    $id = $args{'module'} . "-" . $args{'function'};
+    $id =~ s/[^A-Za-z0-9]/-/g;
+
+    print "<sect2>\n";
+    print " <title id=\"$id\">" . $args{'function'} . "</title>\n";
+
+    print "  <funcsynopsis>\n";
+    print "   <funcdef>" . $args{'functiontype'} . " ";
+    print "<function>" . $args{'function'} . " ";
+    print "</function></funcdef>\n";
+
+    $count = 0;
+    if ($#{$args{'parameterlist'}} >= 0) {
+	foreach $parameter (@{$args{'parameterlist'}}) {
+	    $type = $args{'parametertypes'}{$parameter};
+	    if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+		# pointer-to-function
+		print "   <paramdef>$1 <parameter>$parameter</parameter>)\n";
+		print "     <funcparams>$2</funcparams></paramdef>\n";
+	    } else {
+		print "   <paramdef>" . $type;
+		print " <parameter>$parameter</parameter></paramdef>\n";
+	    }
+	}
+    } else {
+	print "  <void>\n";
+    }
+    print "  </funcsynopsis>\n";
+    if ($#{$args{'parameterlist'}} >= 0) {
+	print " <informaltable pgwide=\"1\" frame=\"none\" role=\"params\">\n";
+	print "<tgroup cols=\"2\">\n";
+	print "<colspec colwidth=\"2*\">\n";
+	print "<colspec colwidth=\"8*\">\n";
+	print "<tbody>\n";
+	foreach $parameter (@{$args{'parameterlist'}}) {
+	    my $parameter_name = $parameter;
+	    $parameter_name =~ s/\[.*//;
+
+	    print "  <row><entry align=\"right\"><parameter>$parameter</parameter></entry>\n";
+	    print "   <entry>\n";
+	    $lineprefix="     ";
+	    output_highlight($args{'parameterdescs'}{$parameter_name});
+	    print "    </entry></row>\n";
+	}
+	print " </tbody></tgroup></informaltable>\n";
+    } else {
+	print " <para>\n  None\n </para>\n";
+    }
+
+    # print out each section
+    $lineprefix="   ";
+    foreach $section (@{$args{'sectionlist'}}) {
+	print "<simplesect>\n <title>$section</title>\n";
+	if ($section =~ m/EXAMPLE/i) {
+	    print "<example><programlisting>\n";
+	    $output_preformatted = 1;
+	} else {
+	}
+	print "<para>\n";
+	output_highlight($args{'sections'}{$section});
+	$output_preformatted = 0;
+	print "</para>\n";
+	if ($section =~ m/EXAMPLE/i) {
+	    print "</programlisting></example>\n";
+	} else {
+	}
+	print " </simplesect>\n";
+    }
+
+    print "</sect2>\n\n";
+}
+
+##
+# output function in man
+sub output_function_man(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $count;
+
+    print ".TH \"$args{'function'}\" 9 \"$args{'function'}\" \"$man_date\" \"Kernel Hacker's Manual\" LINUX\n";
+
+    print ".SH NAME\n";
+    print $args{'function'} . " \\- " . $args{'purpose'} . "\n";
+
+    print ".SH SYNOPSIS\n";
+    if ($args{'functiontype'} ne "") {
+	print ".B \"" . $args{'functiontype'} . "\" " . $args{'function'} . "\n";
+    } else {
+	print ".B \"" . $args{'function'} . "\n";
+    }
+    $count = 0;
+    my $parenth = "(";
+    my $post = ",";
+    foreach my $parameter (@{$args{'parameterlist'}}) {
+	if ($count == $#{$args{'parameterlist'}}) {
+	    $post = ");";
+	}
+	$type = $args{'parametertypes'}{$parameter};
+	if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+	    # pointer-to-function
+	    print ".BI \"" . $parenth . $1 . "\" " . $parameter . " \") (" . $2 . ")" . $post . "\"\n";
+	} else {
+	    $type =~ s/([^\*])$/$1 /;
+	    print ".BI \"" . $parenth . $type . "\" " . $parameter . " \"" . $post . "\"\n";
+	}
+	$count++;
+	$parenth = "";
+    }
+
+    print ".SH ARGUMENTS\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+	my $parameter_name = $parameter;
+	$parameter_name =~ s/\[.*//;
+
+	print ".IP \"" . $parameter . "\" 12\n";
+	output_highlight($args{'parameterdescs'}{$parameter_name});
+    }
+    foreach $section (@{$args{'sectionlist'}}) {
+	print ".SH \"", uc $section, "\"\n";
+	output_highlight($args{'sections'}{$section});
+    }
+}
+
+##
+# output enum in man
+sub output_enum_man(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $count;
+
+    print ".TH \"$args{'module'}\" 9 \"enum $args{'enum'}\" \"$man_date\" \"API Manual\" LINUX\n";
+
+    print ".SH NAME\n";
+    print "enum " . $args{'enum'} . " \\- " . $args{'purpose'} . "\n";
+
+    print ".SH SYNOPSIS\n";
+    print "enum " . $args{'enum'} . " {\n";
+    $count = 0;
+    foreach my $parameter (@{$args{'parameterlist'}}) {
+	print ".br\n.BI \"    $parameter\"\n";
+	if ($count == $#{$args{'parameterlist'}}) {
+	    print "\n};\n";
+	    last;
+	}
+	else {
+	    print ", \n.br\n";
+	}
+	$count++;
+    }
+
+    print ".SH Constants\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+	my $parameter_name = $parameter;
+	$parameter_name =~ s/\[.*//;
+
+	print ".IP \"" . $parameter . "\" 12\n";
+	output_highlight($args{'parameterdescs'}{$parameter_name});
+    }
+    foreach $section (@{$args{'sectionlist'}}) {
+	print ".SH \"$section\"\n";
+	output_highlight($args{'sections'}{$section});
+    }
+}
+
+##
+# output struct in man
+sub output_struct_man(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+
+    print ".TH \"$args{'module'}\" 9 \"" . $args{'type'} . " " . $args{'struct'} . "\" \"$man_date\" \"API Manual\" LINUX\n";
+
+    print ".SH NAME\n";
+    print $args{'type'} . " " . $args{'struct'} . " \\- " . $args{'purpose'} . "\n";
+
+    print ".SH SYNOPSIS\n";
+    print $args{'type'} . " " . $args{'struct'} . " {\n.br\n";
+
+    foreach my $parameter (@{$args{'parameterlist'}}) {
+	if ($parameter =~ /^#/) {
+	    print ".BI \"$parameter\"\n.br\n";
+	    next;
+	}
+	my $parameter_name = $parameter;
+	$parameter_name =~ s/\[.*//;
+
+	($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+	$type = $args{'parametertypes'}{$parameter};
+	if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+	    # pointer-to-function
+	    print ".BI \"    " . $1 . "\" " . $parameter . " \") (" . $2 . ")" . "\"\n;\n";
+	} elsif ($type =~ m/^(.*?)\s*(:.*)/) {
+	    # bitfield
+	    print ".BI \"    " . $1 . "\ \" " . $parameter . $2 . " \"" . "\"\n;\n";
+	} else {
+	    $type =~ s/([^\*])$/$1 /;
+	    print ".BI \"    " . $type . "\" " . $parameter . " \"" . "\"\n;\n";
+	}
+	print "\n.br\n";
+    }
+    print "};\n.br\n";
+
+    print ".SH Members\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+	($parameter =~ /^#/) && next;
+
+	my $parameter_name = $parameter;
+	$parameter_name =~ s/\[.*//;
+
+	($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+	print ".IP \"" . $parameter . "\" 12\n";
+	output_highlight($args{'parameterdescs'}{$parameter_name});
+    }
+    foreach $section (@{$args{'sectionlist'}}) {
+	print ".SH \"$section\"\n";
+	output_highlight($args{'sections'}{$section});
+    }
+}
+
+##
+# output typedef in man
+sub output_typedef_man(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+
+    print ".TH \"$args{'module'}\" 9 \"$args{'typedef'}\" \"$man_date\" \"API Manual\" LINUX\n";
+
+    print ".SH NAME\n";
+    print "typedef " . $args{'typedef'} . " \\- " . $args{'purpose'} . "\n";
+
+    foreach $section (@{$args{'sectionlist'}}) {
+	print ".SH \"$section\"\n";
+	output_highlight($args{'sections'}{$section});
+    }
+}
+
+sub output_blockhead_man(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $count;
+
+    print ".TH \"$args{'module'}\" 9 \"$args{'module'}\" \"$man_date\" \"API Manual\" LINUX\n";
+
+    foreach $section (@{$args{'sectionlist'}}) {
+	print ".SH \"$section\"\n";
+	output_highlight($args{'sections'}{$section});
+    }
+}
+
+##
+# output in text
+sub output_function_text(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $start;
+
+    print "Name:\n\n";
+    print $args{'function'} . " - " . $args{'purpose'} . "\n";
+
+    print "\nSynopsis:\n\n";
+    if ($args{'functiontype'} ne "") {
+	$start = $args{'functiontype'} . " " . $args{'function'} . " (";
+    } else {
+	$start = $args{'function'} . " (";
+    }
+    print $start;
+
+    my $count = 0;
+    foreach my $parameter (@{$args{'parameterlist'}}) {
+	$type = $args{'parametertypes'}{$parameter};
+	if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+	    # pointer-to-function
+	    print $1 . $parameter . ") (" . $2;
+	} else {
+	    print $type . " " . $parameter;
+	}
+	if ($count != $#{$args{'parameterlist'}}) {
+	    $count++;
+	    print ",\n";
+	    print " " x length($start);
+	} else {
+	    print ");\n\n";
+	}
+    }
+
+    print "Arguments:\n\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+	my $parameter_name = $parameter;
+	$parameter_name =~ s/\[.*//;
+
+	print $parameter . "\n\t" . $args{'parameterdescs'}{$parameter_name} . "\n";
+    }
+    output_section_text(@_);
+}
+
+#output sections in text
+sub output_section_text(%) {
+    my %args = %{$_[0]};
+    my $section;
+
+    print "\n";
+    foreach $section (@{$args{'sectionlist'}}) {
+	print "$section:\n\n";
+	output_highlight($args{'sections'}{$section});
+    }
+    print "\n\n";
+}
+
+# output enum in text
+sub output_enum_text(%) {
+    my %args = %{$_[0]};
+    my ($parameter);
+    my $count;
+    print "Enum:\n\n";
+
+    print "enum " . $args{'enum'} . " - " . $args{'purpose'} . "\n\n";
+    print "enum " . $args{'enum'} . " {\n";
+    $count = 0;
+    foreach $parameter (@{$args{'parameterlist'}}) {
+	print "\t$parameter";
+	if ($count != $#{$args{'parameterlist'}}) {
+	    $count++;
+	    print ",";
+	}
+	print "\n";
+    }
+    print "};\n\n";
+
+    print "Constants:\n\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+	print "$parameter\n\t";
+	print $args{'parameterdescs'}{$parameter} . "\n";
+    }
+
+    output_section_text(@_);
+}
+
+# output typedef in text
+sub output_typedef_text(%) {
+    my %args = %{$_[0]};
+    my ($parameter);
+    my $count;
+    print "Typedef:\n\n";
+
+    print "typedef " . $args{'typedef'} . " - " . $args{'purpose'} . "\n";
+    output_section_text(@_);
+}
+
+# output struct as text
+sub output_struct_text(%) {
+    my %args = %{$_[0]};
+    my ($parameter);
+
+    print $args{'type'} . " " . $args{'struct'} . " - " . $args{'purpose'} . "\n\n";
+    print $args{'type'} . " " . $args{'struct'} . " {\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+	if ($parameter =~ /^#/) {
+	    print "$parameter\n";
+	    next;
+	}
+
+	my $parameter_name = $parameter;
+	$parameter_name =~ s/\[.*//;
+
+	($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+	$type = $args{'parametertypes'}{$parameter};
+	if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+	    # pointer-to-function
+	    print "\t$1 $parameter) ($2);\n";
+	} elsif ($type =~ m/^(.*?)\s*(:.*)/) {
+	    # bitfield
+	    print "\t$1 $parameter$2;\n";
+	} else {
+	    print "\t" . $type . " " . $parameter . ";\n";
+	}
+    }
+    print "};\n\n";
+
+    print "Members:\n\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+	($parameter =~ /^#/) && next;
+
+	my $parameter_name = $parameter;
+	$parameter_name =~ s/\[.*//;
+
+	($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+	print "$parameter\n\t";
+	print $args{'parameterdescs'}{$parameter_name} . "\n";
+    }
+    print "\n";
+    output_section_text(@_);
+}
+
+sub output_blockhead_text(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+
+    foreach $section (@{$args{'sectionlist'}}) {
+	print " $section:\n";
+	print "    -> ";
+	output_highlight($args{'sections'}{$section});
+    }
+}
+
+##
+# output in restructured text
+#
+
+#
+# This could use some work; it's used to output the DOC: sections, and
+# starts by putting out the name of the doc section itself, but that tends
+# to duplicate a header already in the template file.
+#
+sub output_blockhead_rst(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+
+    foreach $section (@{$args{'sectionlist'}}) {
+	print "**$section**\n\n";
+	output_highlight_rst($args{'sections'}{$section});
+	print "\n";
+    }
+}
+
+sub output_highlight_rst {
+    my $contents = join "\n",@_;
+    my $line;
+
+    # undo the evil effects of xml_escape() earlier
+    $contents = xml_unescape($contents);
+
+    eval $dohighlight;
+    die $@ if $@;
+
+    foreach $line (split "\n", $contents) {
+	if ($line eq "") {
+	    print $lineprefix, $blankline;
+	} else {
+	    $line =~ s/\\\\\\/\&/g;
+	    print $lineprefix, $line;
+	}
+	print "\n";
+    }
+}
+
+sub output_function_rst(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $start;
+
+    print ".. c:function:: ";
+    if ($args{'functiontype'} ne "") {
+	$start = $args{'functiontype'} . " " . $args{'function'} . " (";
+    } else {
+	$start = $args{'function'} . " (";
+    }
+    print $start;
+
+    my $count = 0;
+    foreach my $parameter (@{$args{'parameterlist'}}) {
+	if ($count ne 0) {
+	    print ", ";
+	}
+	$count++;
+	$type = $args{'parametertypes'}{$parameter};
+	if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+	    # pointer-to-function
+	    print $1 . $parameter . ") (" . $2;
+	} else {
+	    print $type . " " . $parameter;
+	}
+    }
+    print ")\n\n    " . $args{'purpose'} . "\n\n";
+
+    print ":Parameters:\n\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+	my $parameter_name = $parameter;
+	#$parameter_name =~ s/\[.*//;
+	$type = $args{'parametertypes'}{$parameter};
+
+	if ($type ne "") {
+	    print "      ``$type $parameter``\n";
+	} else {
+	    print "      ``$parameter``\n";
+	}
+	if ($args{'parameterdescs'}{$parameter_name} ne $undescribed) {
+	    my $oldprefix = $lineprefix;
+	    $lineprefix = "        ";
+	    output_highlight_rst($args{'parameterdescs'}{$parameter_name});
+	    $lineprefix = $oldprefix;
+	} else {
+	    print "\n        _undescribed_\n";
+	}
+	print "\n";
+    }
+    output_section_rst(@_);
+}
+
+sub output_section_rst(%) {
+    my %args = %{$_[0]};
+    my $section;
+    my $oldprefix = $lineprefix;
+    $lineprefix = "        ";
+
+    foreach $section (@{$args{'sectionlist'}}) {
+	print ":$section:\n\n";
+	output_highlight_rst($args{'sections'}{$section});
+	print "\n";
+    }
+    print "\n";
+    $lineprefix = $oldprefix;
+}
+
+sub output_enum_rst(%) {
+    my %args = %{$_[0]};
+    my ($parameter);
+    my $count;
+    my $name = "enum " . $args{'enum'};
+
+    print "\n\n.. c:type:: " . $name . "\n\n";
+    print "    " . $args{'purpose'} . "\n\n";
+
+    print "..\n\n:Constants:\n\n";
+    my $oldprefix = $lineprefix;
+    $lineprefix = "    ";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+	print "  `$parameter`\n";
+	if ($args{'parameterdescs'}{$parameter} ne $undescribed) {
+	    output_highlight_rst($args{'parameterdescs'}{$parameter});
+	} else {
+	    print "    undescribed\n";
+	}
+	print "\n";
+    }
+    $lineprefix = $oldprefix;
+    output_section_rst(@_);
+}
+
+sub output_typedef_rst(%) {
+    my %args = %{$_[0]};
+    my ($parameter);
+    my $count;
+    my $name = "typedef " . $args{'typedef'};
+
+    ### FIXME: should the name below contain "typedef" or not?
+    print "\n\n.. c:type:: " . $name . "\n\n";
+    print "    " . $args{'purpose'} . "\n\n";
+
+    output_section_rst(@_);
+}
+
+sub output_struct_rst(%) {
+    my %args = %{$_[0]};
+    my ($parameter);
+    my $name = $args{'type'} . " " . $args{'struct'};
+
+    print "\n\n.. c:type:: " . $name . "\n\n";
+    print "    " . $args{'purpose'} . "\n\n";
+
+    print ":Definition:\n\n";
+    print " ::\n\n";
+    print "  " . $args{'type'} . " " . $args{'struct'} . " {\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+	if ($parameter =~ /^#/) {
+	    print "    " . "$parameter\n";
+	    next;
+	}
+
+	my $parameter_name = $parameter;
+	$parameter_name =~ s/\[.*//;
+
+	($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+	$type = $args{'parametertypes'}{$parameter};
+	if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+	    # pointer-to-function
+	    print "    $1 $parameter) ($2);\n";
+	} elsif ($type =~ m/^(.*?)\s*(:.*)/) {
+	    # bitfield
+	    print "    $1 $parameter$2;\n";
+	} else {
+	    print "    " . $type . " " . $parameter . ";\n";
+	}
+    }
+    print "  };\n\n";
+
+    print ":Members:\n\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+	($parameter =~ /^#/) && next;
+
+	my $parameter_name = $parameter;
+	$parameter_name =~ s/\[.*//;
+
+	($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+	$type = $args{'parametertypes'}{$parameter};
+	print "      `$type $parameter`" . "\n";
+	my $oldprefix = $lineprefix;
+	$lineprefix = "        ";
+	output_highlight_rst($args{'parameterdescs'}{$parameter_name});
+	$lineprefix = $oldprefix;
+	print "\n";
+    }
+    print "\n";
+    output_section_rst(@_);
+}
+
+
+## list mode output functions
+
+sub output_function_list(%) {
+    my %args = %{$_[0]};
+
+    print $args{'function'} . "\n";
+}
+
+# output enum in list
+sub output_enum_list(%) {
+    my %args = %{$_[0]};
+    print $args{'enum'} . "\n";
+}
+
+# output typedef in list
+sub output_typedef_list(%) {
+    my %args = %{$_[0]};
+    print $args{'typedef'} . "\n";
+}
+
+# output struct as list
+sub output_struct_list(%) {
+    my %args = %{$_[0]};
+
+    print $args{'struct'} . "\n";
+}
+
+sub output_blockhead_list(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+
+    foreach $section (@{$args{'sectionlist'}}) {
+	print "DOC: $section\n";
+    }
+}
+
+##
+# generic output function for all types (function, struct/union, typedef, enum);
+# calls the generated, variable output_ function name based on
+# functype and output_mode
+sub output_declaration {
+    no strict 'refs';
+    my $name = shift;
+    my $functype = shift;
+    my $func = "output_${functype}_$output_mode";
+    if (($function_only==0) ||
+	( $function_only == 1 && defined($function_table{$name})) ||
+	( $function_only == 2 && !($functype eq "function" && defined($function_table{$name}))))
+    {
+	&$func(@_);
+	$section_counter++;
+    }
+}
+
+##
+# generic output function - calls the right one based on current output mode.
+sub output_blockhead {
+    no strict 'refs';
+    my $func = "output_blockhead_" . $output_mode;
+    &$func(@_);
+    $section_counter++;
+}
+
+##
+# takes a declaration (struct, union, enum, typedef) and
+# invokes the right handler. NOT called for functions.
+sub dump_declaration($$) {
+    no strict 'refs';
+    my ($prototype, $file) = @_;
+    my $func = "dump_" . $decl_type;
+    &$func(@_);
+}
+
+sub dump_union($$) {
+    dump_struct(@_);
+}
+
+sub dump_struct($$) {
+    my $x = shift;
+    my $file = shift;
+    my $nested;
+
+    if ($x =~ /(struct|union)\s+(\w+)\s*{(.*)}/) {
+	#my $decl_type = $1;
+	$declaration_name = $2;
+	my $members = $3;
+
+	# ignore embedded structs or unions
+	$members =~ s/({.*})//g;
+	$nested = $1;
+
+	# ignore members marked private:
+	$members =~ s/\/\*\s*private:.*?\/\*\s*public:.*?\*\///gosi;
+	$members =~ s/\/\*\s*private:.*//gosi;
+	# strip comments:
+	$members =~ s/\/\*.*?\*\///gos;
+	$nested =~ s/\/\*.*?\*\///gos;
+	# strip kmemcheck_bitfield_{begin,end}.*;
+	$members =~ s/kmemcheck_bitfield_.*?;//gos;
+	# strip attributes
+	$members =~ s/__attribute__\s*\(\([a-z,_\*\s\(\)]*\)\)//i;
+	$members =~ s/__aligned\s*\([^;]*\)//gos;
+	$members =~ s/\s*CRYPTO_MINALIGN_ATTR//gos;
+	# replace DECLARE_BITMAP
+	$members =~ s/DECLARE_BITMAP\s*\(([^,)]+), ([^,)]+)\)/unsigned long $1\[BITS_TO_LONGS($2)\]/gos;
+
+	create_parameterlist($members, ';', $file);
+	check_sections($file, $declaration_name, "struct", $sectcheck, $struct_actual, $nested);
+
+	output_declaration($declaration_name,
+			   'struct',
+			   {'struct' => $declaration_name,
+			    'module' => $modulename,
+			    'parameterlist' => \@parameterlist,
+			    'parameterdescs' => \%parameterdescs,
+			    'parametertypes' => \%parametertypes,
+			    'sectionlist' => \@sectionlist,
+			    'sections' => \%sections,
+			    'purpose' => $declaration_purpose,
+			    'type' => $decl_type
+			   });
+    }
+    else {
+	print STDERR "${file}:$.: error: Cannot parse struct or union!\n";
+	++$errors;
+    }
+}
+
+sub dump_enum($$) {
+    my $x = shift;
+    my $file = shift;
+
+    $x =~ s@/\*.*?\*/@@gos;	# strip comments.
+    # strip #define macros inside enums
+    $x =~ s@#\s*((define|ifdef)\s+|endif)[^;]*;@@gos;
+
+    if ($x =~ /enum\s+(\w+)\s*{(.*)}/) {
+	$declaration_name = $1;
+	my $members = $2;
+
+	foreach my $arg (split ',', $members) {
+	    $arg =~ s/^\s*(\w+).*/$1/;
+	    push @parameterlist, $arg;
+	    if (!$parameterdescs{$arg}) {
+		$parameterdescs{$arg} = $undescribed;
+		print STDERR "${file}:$.: warning: Enum value '$arg' ".
+		    "not described in enum '$declaration_name'\n";
+	    }
+
+	}
+
+	output_declaration($declaration_name,
+			   'enum',
+			   {'enum' => $declaration_name,
+			    'module' => $modulename,
+			    'parameterlist' => \@parameterlist,
+			    'parameterdescs' => \%parameterdescs,
+			    'sectionlist' => \@sectionlist,
+			    'sections' => \%sections,
+			    'purpose' => $declaration_purpose
+			   });
+    }
+    else {
+	print STDERR "${file}:$.: error: Cannot parse enum!\n";
+	++$errors;
+    }
+}
+
+sub dump_typedef($$) {
+    my $x = shift;
+    my $file = shift;
+
+    $x =~ s@/\*.*?\*/@@gos;	# strip comments.
+
+    # Parse function prototypes
+    if ($x =~ /typedef\s+(\w+)\s*\(\*\s*(\w\S+)\s*\)\s*\((.*)\);/) {
+	# Function typedefs
+	$return_type = $1;
+	$declaration_name = $2;
+	my $args = $3;
+
+	create_parameterlist($args, ',', $file);
+
+	output_declaration($declaration_name,
+			   'function',
+			   {'function' => $declaration_name,
+			    'module' => $modulename,
+			    'functiontype' => $return_type,
+			    'parameterlist' => \@parameterlist,
+			    'parameterdescs' => \%parameterdescs,
+			    'parametertypes' => \%parametertypes,
+			    'sectionlist' => \@sectionlist,
+			    'sections' => \%sections,
+			    'purpose' => $declaration_purpose
+			   });
+	return;
+    }
+
+    while (($x =~ /\(*.\)\s*;$/) || ($x =~ /\[*.\]\s*;$/)) {
+	$x =~ s/\(*.\)\s*;$/;/;
+	$x =~ s/\[*.\]\s*;$/;/;
+    }
+
+    if ($x =~ /typedef.*\s+(\w+)\s*;/) {
+	$declaration_name = $1;
+
+	output_declaration($declaration_name,
+			   'typedef',
+			   {'typedef' => $declaration_name,
+			    'module' => $modulename,
+			    'sectionlist' => \@sectionlist,
+			    'sections' => \%sections,
+			    'purpose' => $declaration_purpose
+			   });
+    }
+    else {
+	print STDERR "${file}:$.: error: Cannot parse typedef!\n";
+	++$errors;
+    }
+}
+
+sub save_struct_actual($) {
+    my $actual = shift;
+
+    # strip all spaces from the actual param so that it looks like one string item
+    $actual =~ s/\s*//g;
+    $struct_actual = $struct_actual . $actual . " ";
+}
+
+sub create_parameterlist($$$) {
+    my $args = shift;
+    my $splitter = shift;
+    my $file = shift;
+    my $type;
+    my $param;
+
+    # temporarily replace commas inside function pointer definition
+    while ($args =~ /(\([^\),]+),/) {
+	$args =~ s/(\([^\),]+),/$1#/g;
+    }
+
+    foreach my $arg (split($splitter, $args)) {
+	# strip comments
+	$arg =~ s/\/\*.*\*\///;
+	# strip leading/trailing spaces
+	$arg =~ s/^\s*//;
+	$arg =~ s/\s*$//;
+	$arg =~ s/\s+/ /;
+
+	if ($arg =~ /^#/) {
+	    # Treat preprocessor directive as a typeless variable just to fill
+	    # corresponding data structures "correctly". Catch it later in
+	    # output_* subs.
+	    push_parameter($arg, "", $file);
+	} elsif ($arg =~ m/\(.+\)\s*\(/) {
+	    # pointer-to-function
+	    $arg =~ tr/#/,/;
+	    $arg =~ m/[^\(]+\(\*?\s*(\w*)\s*\)/;
+	    $param = $1;
+	    $type = $arg;
+	    $type =~ s/([^\(]+\(\*?)\s*$param/$1/;
+	    save_struct_actual($param);
+	    push_parameter($param, $type, $file);
+	} elsif ($arg) {
+	    $arg =~ s/\s*:\s*/:/g;
+	    $arg =~ s/\s*\[/\[/g;
+
+	    my @args = split('\s*,\s*', $arg);
+	    if ($args[0] =~ m/\*/) {
+		$args[0] =~ s/(\*+)\s*/ $1/;
+	    }
+
+	    my @first_arg;
+	    if ($args[0] =~ /^(.*\s+)(.*?\[.*\].*)$/) {
+		    shift @args;
+		    push(@first_arg, split('\s+', $1));
+		    push(@first_arg, $2);
+	    } else {
+		    @first_arg = split('\s+', shift @args);
+	    }
+
+	    unshift(@args, pop @first_arg);
+	    $type = join " ", @first_arg;
+
+	    foreach $param (@args) {
+		if ($param =~ m/^(\*+)\s*(.*)/) {
+		    save_struct_actual($2);
+		    push_parameter($2, "$type $1", $file);
+		}
+		elsif ($param =~ m/(.*?):(\d+)/) {
+		    if ($type ne "") { # skip unnamed bit-fields
+			save_struct_actual($1);
+			push_parameter($1, "$type:$2", $file)
+		    }
+		}
+		else {
+		    save_struct_actual($param);
+		    push_parameter($param, $type, $file);
+		}
+	    }
+	}
+    }
+}
+
+sub push_parameter($$$) {
+	my $param = shift;
+	my $type = shift;
+	my $file = shift;
+
+	if (($anon_struct_union == 1) && ($type eq "") &&
+	    ($param eq "}")) {
+		return;		# ignore the ending }; from anon. struct/union
+	}
+
+	$anon_struct_union = 0;
+	my $param_name = $param;
+	$param_name =~ s/\[.*//;
+
+	if ($type eq "" && $param =~ /\.\.\.$/)
+	{
+	    if (!defined $parameterdescs{$param} || $parameterdescs{$param} eq "") {
+		$parameterdescs{$param} = "variable arguments";
+	    }
+	}
+	elsif ($type eq "" && ($param eq "" or $param eq "void"))
+	{
+	    $param="void";
+	    $parameterdescs{void} = "no arguments";
+	}
+	elsif ($type eq "" && ($param eq "struct" or $param eq "union"))
+	# handle unnamed (anonymous) union or struct:
+	{
+		$type = $param;
+		$param = "{unnamed_" . $param . "}";
+		$parameterdescs{$param} = "anonymous\n";
+		$anon_struct_union = 1;
+	}
+
+	# warn if parameter has no description
+	# (but ignore ones starting with # as these are not parameters
+	# but inline preprocessor statements);
+	# also ignore unnamed structs/unions;
+	if (!$anon_struct_union) {
+	if (!defined $parameterdescs{$param_name} && $param_name !~ /^#/) {
+
+	    $parameterdescs{$param_name} = $undescribed;
+
+	    if (($type eq 'function') || ($type eq 'enum')) {
+		print STDERR "${file}:$.: warning: Function parameter ".
+		    "or member '$param' not " .
+		    "described in '$declaration_name'\n";
+	    }
+	    print STDERR "${file}:$.: warning:" .
+			 " No description found for parameter '$param'\n";
+	    ++$warnings;
+	}
+	}
+
+	$param = xml_escape($param);
+
+	# strip spaces from $param so that it is one continuous string
+	# on @parameterlist;
+	# this fixes a problem where check_sections() cannot find
+	# a parameter like "addr[6 + 2]" because it actually appears
+	# as "addr[6", "+", "2]" on the parameter list;
+	# but it's better to maintain the param string unchanged for output,
+	# so just weaken the string compare in check_sections() to ignore
+	# "[blah" in a parameter string;
+	###$param =~ s/\s*//g;
+	push @parameterlist, $param;
+	$parametertypes{$param} = $type;
+}
+
+sub check_sections($$$$$$) {
+	my ($file, $decl_name, $decl_type, $sectcheck, $prmscheck, $nested) = @_;
+	my @sects = split ' ', $sectcheck;
+	my @prms = split ' ', $prmscheck;
+	my $err;
+	my ($px, $sx);
+	my $prm_clean;		# strip trailing "[array size]" and/or beginning "*"
+
+	foreach $sx (0 .. $#sects) {
+		$err = 1;
+		foreach $px (0 .. $#prms) {
+			$prm_clean = $prms[$px];
+			$prm_clean =~ s/\[.*\]//;
+			$prm_clean =~ s/__attribute__\s*\(\([a-z,_\*\s\(\)]*\)\)//i;
+			# ignore array size in a parameter string;
+			# however, the original param string may contain
+			# spaces, e.g.:  addr[6 + 2]
+			# and this appears in @prms as "addr[6" since the
+			# parameter list is split at spaces;
+			# hence just ignore "[..." for the sections check;
+			$prm_clean =~ s/\[.*//;
+
+			##$prm_clean =~ s/^\**//;
+			if ($prm_clean eq $sects[$sx]) {
+				$err = 0;
+				last;
+			}
+		}
+		if ($err) {
+			if ($decl_type eq "function") {
+				print STDERR "${file}:$.: warning: " .
+					"Excess function parameter " .
+					"'$sects[$sx]' " .
+					"description in '$decl_name'\n";
+				++$warnings;
+			} else {
+				if ($nested !~ m/\Q$sects[$sx]\E/) {
+				    print STDERR "${file}:$.: warning: " .
+					"Excess struct/union/enum/typedef member " .
+					"'$sects[$sx]' " .
+					"description in '$decl_name'\n";
+				    ++$warnings;
+				}
+			}
+		}
+	}
+}
+
+##
+# Checks the section describing the return value of a function.
+sub check_return_section {
+        my $file = shift;
+        my $declaration_name = shift;
+        my $return_type = shift;
+
+        # Ignore an empty return type (It's a macro)
+        # Ignore functions with a "void" return type. (But don't ignore "void *")
+        if (($return_type eq "") || ($return_type =~ /void\s*\w*\s*$/)) {
+                return;
+        }
+
+        if (!defined($sections{$section_return}) ||
+            $sections{$section_return} eq "") {
+                print STDERR "${file}:$.: warning: " .
+                        "No description found for return value of " .
+                        "'$declaration_name'\n";
+                ++$warnings;
+        }
+}
+
+##
+# takes a function prototype and the name of the current file being
+# processed and spits out all the details stored in the global
+# arrays/hashes.
+sub dump_function($$) {
+    my $prototype = shift;
+    my $file = shift;
+    my $noret = 0;
+
+    $prototype =~ s/^static +//;
+    $prototype =~ s/^extern +//;
+    $prototype =~ s/^asmlinkage +//;
+    $prototype =~ s/^inline +//;
+    $prototype =~ s/^__inline__ +//;
+    $prototype =~ s/^__inline +//;
+    $prototype =~ s/^__always_inline +//;
+    $prototype =~ s/^noinline +//;
+    $prototype =~ s/__init +//;
+    $prototype =~ s/__init_or_module +//;
+    $prototype =~ s/__meminit +//;
+    $prototype =~ s/__must_check +//;
+    $prototype =~ s/__weak +//;
+    my $define = $prototype =~ s/^#\s*define\s+//; #ak added
+    $prototype =~ s/__attribute__\s*\(\([a-z,]*\)\)//;
+
+    # Yes, this truly is vile.  We are looking for:
+    # 1. Return type (may be nothing if we're looking at a macro)
+    # 2. Function name
+    # 3. Function parameters.
+    #
+    # All the while we have to watch out for function pointer parameters
+    # (which IIRC is what the two sections are for), C types (these
+    # regexps don't even start to express all the possibilities), and
+    # so on.
+    #
+    # If you mess with these regexps, it's a good idea to check that
+    # the following functions' documentation still comes out right:
+    # - parport_register_device (function pointer parameters)
+    # - atomic_set (macro)
+    # - pci_match_device, __copy_to_user (long return type)
+
+    if ($define && $prototype =~ m/^()([a-zA-Z0-9_~:]+)\s+/) {
+        # This is an object-like macro, it has no return type and no parameter
+        # list.
+        # Function-like macros are not allowed to have spaces between
+        # declaration_name and opening parenthesis (notice the \s+).
+        $return_type = $1;
+        $declaration_name = $2;
+        $noret = 1;
+    } elsif ($prototype =~ m/^()([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ ||
+	$prototype =~ m/^(\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ ||
+	$prototype =~ m/^(\w+\s*\*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ ||
+	$prototype =~ m/^(\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ ||
+	$prototype =~ m/^(\w+\s+\w+\s*\*+)\s*([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ ||
+	$prototype =~ m/^(\w+\s+\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ ||
+	$prototype =~ m/^(\w+\s+\w+\s+\w+\s*\*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ ||
+	$prototype =~ m/^()([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ ||
+	$prototype =~ m/^(\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ ||
+	$prototype =~ m/^(\w+\s*\*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ ||
+	$prototype =~ m/^(\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ ||
+	$prototype =~ m/^(\w+\s+\w+\s*\*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ ||
+	$prototype =~ m/^(\w+\s+\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ ||
+	$prototype =~ m/^(\w+\s+\w+\s+\w+\s*\*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ ||
+	$prototype =~ m/^(\w+\s+\w+\s+\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ ||
+	$prototype =~ m/^(\w+\s+\w+\s+\w+\s+\w+\s*\*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ ||
+	$prototype =~ m/^(\w+\s+\w+\s*\*\s*\w+\s*\*\s*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/)  {
+	$return_type = $1;
+	$declaration_name = $2;
+	my $args = $3;
+
+	create_parameterlist($args, ',', $file);
+    } else {
+	print STDERR "${file}:$.: warning: cannot understand function prototype: '$prototype'\n";
+	return;
+    }
+
+	my $prms = join " ", @parameterlist;
+	check_sections($file, $declaration_name, "function", $sectcheck, $prms, "");
+
+        # This check emits a lot of warnings at the moment, because many
+        # functions don't have a 'Return' doc section. So until the number
+        # of warnings goes sufficiently down, the check is only performed in
+        # verbose mode.
+        # TODO: always perform the check.
+        if ($verbose && !$noret) {
+                check_return_section($file, $declaration_name, $return_type);
+        }
+
+    output_declaration($declaration_name,
+		       'function',
+		       {'function' => $declaration_name,
+			'module' => $modulename,
+			'functiontype' => $return_type,
+			'parameterlist' => \@parameterlist,
+			'parameterdescs' => \%parameterdescs,
+			'parametertypes' => \%parametertypes,
+			'sectionlist' => \@sectionlist,
+			'sections' => \%sections,
+			'purpose' => $declaration_purpose
+		       });
+}
+
+sub reset_state {
+    $function = "";
+    %constants = ();
+    %parameterdescs = ();
+    %parametertypes = ();
+    @parameterlist = ();
+    %sections = ();
+    @sectionlist = ();
+    $sectcheck = "";
+    $struct_actual = "";
+    $prototype = "";
+
+    $state = 0;
+    $split_doc_state = 0;
+}
+
+sub tracepoint_munge($) {
+	my $file = shift;
+	my $tracepointname = 0;
+	my $tracepointargs = 0;
+
+	if ($prototype =~ m/TRACE_EVENT\((.*?),/) {
+		$tracepointname = $1;
+	}
+	if ($prototype =~ m/DEFINE_SINGLE_EVENT\((.*?),/) {
+		$tracepointname = $1;
+	}
+	if ($prototype =~ m/DEFINE_EVENT\((.*?),(.*?),/) {
+		$tracepointname = $2;
+	}
+	$tracepointname =~ s/^\s+//; #strip leading whitespace
+	if ($prototype =~ m/TP_PROTO\((.*?)\)/) {
+		$tracepointargs = $1;
+	}
+	if (($tracepointname eq 0) || ($tracepointargs eq 0)) {
+		print STDERR "${file}:$.: warning: Unrecognized tracepoint format: \n".
+			     "$prototype\n";
+	} else {
+		$prototype = "static inline void trace_$tracepointname($tracepointargs)";
+	}
+}
+
+sub syscall_munge() {
+	my $void = 0;
+
+	$prototype =~ s@[\r\n\t]+@ @gos; # strip newlines/CR's/tabs
+##	if ($prototype =~ m/SYSCALL_DEFINE0\s*\(\s*(a-zA-Z0-9_)*\s*\)/) {
+	if ($prototype =~ m/SYSCALL_DEFINE0/) {
+		$void = 1;
+##		$prototype = "long sys_$1(void)";
+	}
+
+	$prototype =~ s/SYSCALL_DEFINE.*\(/long sys_/; # fix return type & func name
+	if ($prototype =~ m/long (sys_.*?),/) {
+		$prototype =~ s/,/\(/;
+	} elsif ($void) {
+		$prototype =~ s/\)/\(void\)/;
+	}
+
+	# now delete all of the odd-number commas in $prototype
+	# so that arg types & arg names don't have a comma between them
+	my $count = 0;
+	my $len = length($prototype);
+	if ($void) {
+		$len = 0;	# skip the for-loop
+	}
+	for (my $ix = 0; $ix < $len; $ix++) {
+		if (substr($prototype, $ix, 1) eq ',') {
+			$count++;
+			if ($count % 2 == 1) {
+				substr($prototype, $ix, 1) = ' ';
+			}
+		}
+	}
+}
+
+sub process_state3_function($$) {
+    my $x = shift;
+    my $file = shift;
+
+    $x =~ s@\/\/.*$@@gos; # strip C99-style comments to end of line
+
+    if ($x =~ m#\s*/\*\s+MACDOC\s*#io || ($x =~ /^#/ && $x !~ /^#\s*define/)) {
+	# do nothing
+    }
+    elsif ($x =~ /([^\{]*)/) {
+	$prototype .= $1;
+    }
+
+    if (($x =~ /\{/) || ($x =~ /\#\s*define/) || ($x =~ /;/)) {
+	$prototype =~ s@/\*.*?\*/@@gos;	# strip comments.
+	$prototype =~ s@[\r\n]+@ @gos; # strip newlines/cr's.
+	$prototype =~ s@^\s+@@gos; # strip leading spaces
+	if ($prototype =~ /SYSCALL_DEFINE/) {
+		syscall_munge();
+	}
+	if ($prototype =~ /TRACE_EVENT/ || $prototype =~ /DEFINE_EVENT/ ||
+	    $prototype =~ /DEFINE_SINGLE_EVENT/)
+	{
+		tracepoint_munge($file);
+	}
+	dump_function($prototype, $file);
+	reset_state();
+    }
+}
+
+sub process_state3_type($$) {
+    my $x = shift;
+    my $file = shift;
+
+    $x =~ s@[\r\n]+@ @gos; # strip newlines/cr's.
+    $x =~ s@^\s+@@gos; # strip leading spaces
+    $x =~ s@\s+$@@gos; # strip trailing spaces
+    $x =~ s@\/\/.*$@@gos; # strip C99-style comments to end of line
+
+    if ($x =~ /^#/) {
+	# To distinguish preprocessor directive from regular declaration later.
+	$x .= ";";
+    }
+
+    while (1) {
+	if ( $x =~ /([^{};]*)([{};])(.*)/ ) {
+	    $prototype .= $1 . $2;
+	    ($2 eq '{') && $brcount++;
+	    ($2 eq '}') && $brcount--;
+	    if (($2 eq ';') && ($brcount == 0)) {
+		dump_declaration($prototype, $file);
+		reset_state();
+		last;
+	    }
+	    $x = $3;
+	} else {
+	    $prototype .= $x;
+	    last;
+	}
+    }
+}
+
+# xml_escape: replace <, >, and & in the text stream;
+#
+# however, formatting controls that are generated internally/locally in the
+# kernel-doc script are not escaped here; instead, they begin life like
+# $blankline_html (4 of '\' followed by a mnemonic + ':'), then these strings
+# are converted to their mnemonic-expected output, without the 4 * '\' & ':',
+# just before actual output; (this is done by local_unescape())
+sub xml_escape($) {
+	my $text = shift;
+	if (($output_mode eq "text") || ($output_mode eq "man")) {
+		return $text;
+	}
+	$text =~ s/\&/\\\\\\amp;/g;
+	$text =~ s/\</\\\\\\lt;/g;
+	$text =~ s/\>/\\\\\\gt;/g;
+	return $text;
+}
+
+# xml_unescape: reverse the effects of xml_escape
+sub xml_unescape($) {
+	my $text = shift;
+	if (($output_mode eq "text") || ($output_mode eq "man")) {
+		return $text;
+	}
+	$text =~ s/\\\\\\amp;/\&/g;
+	$text =~ s/\\\\\\lt;/</g;
+	$text =~ s/\\\\\\gt;/>/g;
+	return $text;
+}
+
+# convert local escape strings to html
+# local escape strings look like:  '\\\\menmonic:' (that's 4 backslashes)
+sub local_unescape($) {
+	my $text = shift;
+	if (($output_mode eq "text") || ($output_mode eq "man")) {
+		return $text;
+	}
+	$text =~ s/\\\\\\\\lt:/</g;
+	$text =~ s/\\\\\\\\gt:/>/g;
+	return $text;
+}
+
+sub process_file($) {
+    my $file;
+    my $identifier;
+    my $func;
+    my $descr;
+    my $in_purpose = 0;
+    my $initial_section_counter = $section_counter;
+    my ($orig_file) = @_;
+
+    if (defined($ENV{'SRCTREE'})) {
+	$file = "$ENV{'SRCTREE'}" . "/" . $orig_file;
+    }
+    else {
+	$file = $orig_file;
+    }
+    if (defined($source_map{$file})) {
+	$file = $source_map{$file};
+    }
+
+    if (!open(IN,"<$file")) {
+	print STDERR "Error: Cannot open file $file\n";
+	++$errors;
+	return;
+    }
+
+    $. = 1;
+
+    $section_counter = 0;
+    while (<IN>) {
+	while (s/\\\s*$//) {
+	    $_ .= <IN>;
+	}
+	if ($state == 0) {
+	    if (/$doc_start/o) {
+		$state = 1;		# next line is always the function name
+		$in_doc_sect = 0;
+	    }
+	} elsif ($state == 1) {	# this line is the function name (always)
+	    if (/$doc_block/o) {
+		$state = 4;
+		$contents = "";
+		if ( $1 eq "" ) {
+			$section = $section_intro;
+		} else {
+			$section = $1;
+		}
+	    }
+	    elsif (/$doc_decl/o) {
+		$identifier = $1;
+		if (/\s*([\w\s]+?)\s*-/) {
+		    $identifier = $1;
+		}
+
+		$state = 2;
+		if (/-(.*)/) {
+		    # strip leading/trailing/multiple spaces
+		    $descr= $1;
+		    $descr =~ s/^\s*//;
+		    $descr =~ s/\s*$//;
+		    $descr =~ s/\s+/ /g;
+		    $declaration_purpose = xml_escape($descr);
+		    $in_purpose = 1;
+		} else {
+		    $declaration_purpose = "";
+		}
+
+		if (($declaration_purpose eq "") && $verbose) {
+			print STDERR "${file}:$.: warning: missing initial short description on line:\n";
+			print STDERR $_;
+			++$warnings;
+		}
+
+		if ($identifier =~ m/^struct/) {
+		    $decl_type = 'struct';
+		} elsif ($identifier =~ m/^union/) {
+		    $decl_type = 'union';
+		} elsif ($identifier =~ m/^enum/) {
+		    $decl_type = 'enum';
+		} elsif ($identifier =~ m/^typedef/) {
+		    $decl_type = 'typedef';
+		} else {
+		    $decl_type = 'function';
+		}
+
+		if ($verbose) {
+		    print STDERR "${file}:$.: info: Scanning doc for $identifier\n";
+		}
+	    } else {
+		print STDERR "${file}:$.: warning: Cannot understand $_ on line $.",
+		" - I thought it was a doc line\n";
+		++$warnings;
+		$state = 0;
+	    }
+	} elsif ($state == 2) {	# look for head: lines, and include content
+	    if (/$doc_sect/o) {
+		$newsection = $1;
+		$newcontents = $2;
+
+		if (($contents ne "") && ($contents ne "\n")) {
+		    if (!$in_doc_sect && $verbose) {
+			print STDERR "${file}:$.: warning: contents before sections\n";
+			++$warnings;
+		    }
+		    dump_section($file, $section, xml_escape($contents));
+		    $section = $section_default;
+		}
+
+		$in_doc_sect = 1;
+		$in_purpose = 0;
+		$contents = $newcontents;
+		if ($contents ne "") {
+		    while ((substr($contents, 0, 1) eq " ") ||
+			substr($contents, 0, 1) eq "\t") {
+			    $contents = substr($contents, 1);
+		    }
+		    $contents .= "\n";
+		}
+		$section = $newsection;
+	    } elsif (/$doc_end/) {
+		if (($contents ne "") && ($contents ne "\n")) {
+		    dump_section($file, $section, xml_escape($contents));
+		    $section = $section_default;
+		    $contents = "";
+		}
+		# look for doc_com + <text> + doc_end:
+		if ($_ =~ m'\s*\*\s*[a-zA-Z_0-9:\.]+\*/') {
+		    print STDERR "${file}:$.: warning: suspicious ending line: $_";
+		    ++$warnings;
+		}
+
+		$prototype = "";
+		$state = 3;
+		$brcount = 0;
+#		print STDERR "end of doc comment, looking for prototype\n";
+	    } elsif (/$doc_content/) {
+		# miguel-style comment kludge, look for blank lines after
+		# @parameter line to signify start of description
+		if ($1 eq "") {
+		    if ($section =~ m/^@/ || $section eq $section_context) {
+			dump_section($file, $section, xml_escape($contents));
+			$section = $section_default;
+			$contents = "";
+		    } else {
+			$contents .= "\n";
+		    }
+		    $in_purpose = 0;
+		} elsif ($in_purpose == 1) {
+		    # Continued declaration purpose
+		    chomp($declaration_purpose);
+		    $declaration_purpose .= " " . xml_escape($1);
+		    $declaration_purpose =~ s/\s+/ /g;
+		} else {
+		    $contents .= $1 . "\n";
+		}
+	    } else {
+		# i dont know - bad line?  ignore.
+		print STDERR "${file}:$.: warning: bad line: $_";
+		++$warnings;
+	    }
+	} elsif ($state == 5) { # scanning for split parameters
+	    # First line (state 1) needs to be a @parameter
+	    if ($split_doc_state == 1 && /$doc_split_sect/o) {
+		$section = $1;
+		$contents = $2;
+		if ($contents ne "") {
+		    while ((substr($contents, 0, 1) eq " ") ||
+		           substr($contents, 0, 1) eq "\t") {
+			$contents = substr($contents, 1);
+		    }
+		$contents .= "\n";
+		}
+		$split_doc_state = 2;
+	    # Documentation block end */
+	    } elsif (/$doc_split_end/) {
+		if (($contents ne "") && ($contents ne "\n")) {
+		    dump_section($file, $section, xml_escape($contents));
+		    $section = $section_default;
+		    $contents = "";
+		}
+		$state = 3;
+		$split_doc_state = 0;
+	    # Regular text
+	    } elsif (/$doc_content/) {
+		if ($split_doc_state == 2) {
+		    $contents .= $1 . "\n";
+		} elsif ($split_doc_state == 1) {
+		    $split_doc_state = 4;
+		    print STDERR "Warning(${file}:$.): ";
+		    print STDERR "Incorrect use of kernel-doc format: $_";
+		    ++$warnings;
+		}
+	    }
+	} elsif ($state == 3) {	# scanning for function '{' (end of prototype)
+	    if (/$doc_split_start/) {
+		$state = 5;
+		$split_doc_state = 1;
+	    } elsif ($decl_type eq 'function') {
+		process_state3_function($_, $file);
+	    } else {
+		process_state3_type($_, $file);
+	    }
+	} elsif ($state == 4) {
+		# Documentation block
+		if (/$doc_block/) {
+			dump_doc_section($file, $section, xml_escape($contents));
+			$contents = "";
+			$function = "";
+			%constants = ();
+			%parameterdescs = ();
+			%parametertypes = ();
+			@parameterlist = ();
+			%sections = ();
+			@sectionlist = ();
+			$prototype = "";
+			if ( $1 eq "" ) {
+				$section = $section_intro;
+			} else {
+				$section = $1;
+			}
+		}
+		elsif (/$doc_end/)
+		{
+			dump_doc_section($file, $section, xml_escape($contents));
+			$contents = "";
+			$function = "";
+			%constants = ();
+			%parameterdescs = ();
+			%parametertypes = ();
+			@parameterlist = ();
+			%sections = ();
+			@sectionlist = ();
+			$prototype = "";
+			$state = 0;
+		}
+		elsif (/$doc_content/)
+		{
+			if ( $1 eq "" )
+			{
+				$contents .= $blankline;
+			}
+			else
+			{
+				$contents .= $1 . "\n";
+			}
+		}
+	}
+    }
+    if ($initial_section_counter == $section_counter) {
+	print STDERR "${file}:1: warning: no structured comments found\n";
+	if (($function_only == 1) && ($show_not_found == 1)) {
+	    print STDERR "    Was looking for '$_'.\n" for keys %function_table;
+	}
+	if ($output_mode eq "xml") {
+	    # The template wants at least one RefEntry here; make one.
+	    print "<refentry>\n";
+	    print " <refnamediv>\n";
+	    print "  <refname>\n";
+	    print "   ${orig_file}\n";
+	    print "  </refname>\n";
+	    print "  <refpurpose>\n";
+	    print "   Document generation inconsistency\n";
+	    print "  </refpurpose>\n";
+	    print " </refnamediv>\n";
+	    print " <refsect1>\n";
+	    print "  <title>\n";
+	    print "   Oops\n";
+	    print "  </title>\n";
+	    print "  <warning>\n";
+	    print "   <para>\n";
+	    print "    The template for this document tried to insert\n";
+	    print "    the structured comment from the file\n";
+	    print "    <filename>${orig_file}</filename> at this point,\n";
+	    print "    but none was found.\n";
+	    print "    This dummy section is inserted to allow\n";
+	    print "    generation to continue.\n";
+	    print "   </para>\n";
+	    print "  </warning>\n";
+	    print " </refsect1>\n";
+	    print "</refentry>\n";
+	}
+    }
+}
+
+
+$kernelversion = get_kernel_version();
+
+# generate a sequence of code that will splice in highlighting information
+# using the s// operator.
+for (my $k = 0; $k < @highlights; $k++) {
+    my $pattern = $highlights[$k][0];
+    my $result = $highlights[$k][1];
+#   print STDERR "scanning pattern:$pattern, highlight:($result)\n";
+    $dohighlight .=  "\$contents =~ s:$pattern:$result:gs;\n";
+}
+
+# Read the file that maps relative names to absolute names for
+# separate source and object directories and for shadow trees.
+if (open(SOURCE_MAP, "<.tmp_filelist.txt")) {
+	my ($relname, $absname);
+	while(<SOURCE_MAP>) {
+		chop();
+		($relname, $absname) = (split())[0..1];
+		$relname =~ s:^/+::;
+		$source_map{$relname} = $absname;
+	}
+	close(SOURCE_MAP);
+}
+
+foreach (@ARGV) {
+    chomp;
+    process_file($_);
+}
+if ($verbose && $errors) {
+  print STDERR "$errors errors\n";
+}
+if ($verbose && $warnings) {
+  print STDERR "$warnings warnings\n";
+}
+
+exit($errors);
diff --git a/libdmmp/docs/libdmmp.h.3 b/libdmmp/docs/libdmmp.h.3
new file mode 100644
index 0000000..45d5be3
--- /dev/null
+++ b/libdmmp/docs/libdmmp.h.3
@@ -0,0 +1,113 @@
+.TH "libdmmp.h" 3 "January 2016" "Device Mapper Multipath API - libdmmp Manual"
+
+.SH NAME
+libdmmp.h \- Device Mapper Multipath API.
+
+.SH SYNOPSIS
+#include <libdmmp/libdmmp.h>
+
+.SH "DESCRIPTION"
+
+All the libdmmp public functions ships its own man pages.
+Use 'man 3 <function_name>' to check the detail usage.
+
+.SH "USAGE"
+
+To use libdmmp in your project, we suggest to use the 'pkg-config' way:
+
+ * Add this line into your configure.ac:
+
+    PKG_CHECK_MODULES([LIBDMMP], [libdmmp])
+
+ * Add these lines into your Makefile.am:
+
+    foo_LDFLAGS += $(LIBDMMP_LIBS)
+    foo_CFLAGS += $(LIBDMMP_CFLAGS)
+
+.SH LOG HANDLING
+
+The log handler function could be set via 'dmmp_context_log_func_set()'.
+The log priority could be set via 'dmmp_context_log_priority_set()'.
+
+By default, the log priorities is 'DMMP_LOG_PRIORITY_WARNING'.
+By default, the log handler is print log to STDERR, and its code is listed
+below in case you want to create your own log handler.
+
+        static int _DMMP_LOG_STRERR_ALIGN_WIDTH = 80;
+
+        static void _log_stderr(struct dmmp_context *ctx,
+                                enum dmmp_log_priority priority,
+                                const char *file, int line,
+                                const char *func_name,
+                                const char *format, va_list args)
+        {
+            int printed_bytes = 0;
+
+            printed_bytes += fprintf(stderr, "libdmmp %s: ",
+                                     dmmp_log_priority_str(priority));
+            printed_bytes += vfprintf(stderr, format, args);
+            userdata = dmmp_context_userdata_get(ctx);
+            if (userdata != NULL)
+                fprintf(stderr, "(with user data at memory address %p)",
+                        userdata);
+
+            if (printed_bytes < _DMMP_LOG_STRERR_ALIGN_WIDTH) {
+                fprintf(stderr, "%*s # %s:%s():%d\n",
+                        _DMMP_LOG_STRERR_ALIGN_WIDTH - printed_bytes, "", file,
+                        func_name, line);
+            } else {
+                fprintf(stderr, " # %s:%s():%d\n", file, func_name, line);
+            }
+        }
+
+
+.SH "SAMPLE CODE"
+
+    #include <libdmmp/libdmmp.h>
+
+    int main(int argc, char *argv[]) {
+        struct dmmp_context *ctx = NULL;
+        struct dmmp_mpath **dmmp_mps = NULL;
+        struct dmmp_path_group **dmmp_pgs = NULL;
+        struct dmmp_path **dmmp_ps = NULL;
+        uint32_t dmmp_mp_count = 0;
+        uint32_t dmmp_pg_count = 0;
+        uint32_t dmmp_p_count = 0;
+        const char *name = NULL;
+        const char *wwid = NULL;
+        uint32_t i = 0;
+        int rc = DMMP_OK;
+
+        ctx = dmmp_context_new();
+        dmmp_context_log_priority_set(ctx, DMMP_LOG_PRIORITY_DEBUG);
+        // By default, log will be printed to STDERR, you could
+        // change that via dmmp_context_log_func_set()
+        rc = dmmp_mpath_array_get(ctx, &dmmp_mps, &dmmp_mp_count);
+        if (rc != DMMP_OK) {
+            printf("dmmp_mpath_array_get() failed with %d: %s", rc,
+                   dmmp_strerror(rc));
+            goto out;
+        }
+        for (i = 0; i < dmmp_mp_count; ++i) {
+                name = dmmp_mpath_name_get(dmmp_mps[i]);
+                wwid = dmmp_mpath_wwid_get(dmmp_mps[i]);
+                printf("dmmp_mpath_array_get(): Got mpath: %s %s\n", name,
+                       wwid);
+                // You could use dmmp_path_group_array_get() to retrieve
+                // path group information and then invoke dmmp_path_array_get()
+                // for path information.
+        }
+
+     out:
+        dmmp_context_free(ctx);
+        dmmp_mpath_array_free(dmmp_mps, dmmp_mp_count);
+        if (rc != DMMP_OK)
+            exit(1);
+        exit(0);
+    }
+
+.SH "LICENSE"
+GPLv2+
+
+.SH "BUG"
+Please report bug to <dm-devel@redhat.com>
diff --git a/libdmmp/docs/split-man.pl b/libdmmp/docs/split-man.pl
new file mode 100644
index 0000000..452fd8a
--- /dev/null
+++ b/libdmmp/docs/split-man.pl
@@ -0,0 +1,41 @@
+#!/usr/bin/perl
+# Originally From:
+# https://www.kernel.org/doc/Documentation/kernel-doc-nano-HOWTO.txt
+#
+# Changes:
+#   * Create manpage section 3 instead of 9.
+#   * Replace 'Kernel Hackers Manual' to
+#       'Device Mapper Multipath API - libdmmp Manual'
+#   * Remove LINUX from header.
+#   * Remove DMMP_DLL_EXPORT.
+$man_sec_num = 3;
+$title = 'Device Mapper Multipath API - libdmmp Manual';
+
+if ( $#ARGV < 0 ) {
+    die "where do I put the results?\n";
+}
+
+mkdir $ARGV[0], 0777;
+$state = 0;
+while (<STDIN>) {
+    s/DMMP_DLL_EXPORT//g;
+    if (/^\.TH \"[^\"]*\" 9 \"([^\"]*)\"/) {
+        if ( $state == 1 ) { close OUT }
+        $state = 1;
+        $fn    = "$ARGV[0]/$1.$man_sec_num";
+        print STDERR "Creating $fn\n";
+        open OUT, ">$fn" or die "can't open $fn: $!\n";
+
+        # Change man page code from 9 to $man_sec_num;
+        s/^\.TH (\"[^\"]*\") 9 \"([^\"]*)\"/\.TH $1 $man_sec_num \"$2\"/;
+        s/Kernel Hacker's Manual/$title/g;
+        s/LINUX//g;
+
+        print OUT $_;
+    }
+    elsif ( $state != 0 ) {
+        print OUT $_;
+    }
+}
+
+close OUT;
diff --git a/libdmmp/libdmmp.c b/libdmmp/libdmmp.c
new file mode 100644
index 0000000..f8762fd
--- /dev/null
+++ b/libdmmp/libdmmp.c
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2015 - 2016 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Gris Ge <fge@redhat.com>
+ *         Todd Gill <tgill@redhat.com>
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <libudev.h>
+#include <errno.h>
+#include <libdevmapper.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <assert.h>
+#include <json.h>
+#include <mpath_cmd.h>
+
+#include "libdmmp/libdmmp.h"
+#include "libdmmp_private.h"
+
+#define _DEFAULT_UXSOCK_TIMEOUT		60000
+/* ^ 60 seconds. On system with 10k sdX, dmmp_mpath_array_get()
+ *   only take 3.5 seconds, so this default value should be OK for most users.
+ */
+
+#define _DMMP_IPC_SHOW_JSON_CMD			"show maps json"
+#define _DMMP_JSON_MAJOR_KEY			"major_version"
+#define _DMMP_JSON_MAJOR_VERSION		0
+#define _DMMP_JSON_MAPS_KEY			"maps"
+
+struct dmmp_context {
+	void (*log_func)(struct dmmp_context *ctx, int priority,
+			 const char *file, int line, const char *func_name,
+			 const char *format, va_list args);
+	int log_priority;
+	void *userdata;
+	unsigned int tmo;
+};
+
+_dmmp_getter_func_gen(dmmp_context_log_priority_get,
+		      struct dmmp_context, ctx, log_priority,
+		      int);
+
+_dmmp_getter_func_gen(dmmp_context_userdata_get, struct dmmp_context, ctx,
+		      userdata, void *);
+
+_dmmp_getter_func_gen(dmmp_context_timeout_get, struct dmmp_context, ctx, tmo,
+		      unsigned int);
+
+_dmmp_array_free_func_gen(dmmp_mpath_array_free, struct dmmp_mpath,
+			  _dmmp_mpath_free);
+
+void _dmmp_log(struct dmmp_context *ctx, int priority, const char *file,
+	       int line, const char *func_name, const char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+	ctx->log_func(ctx, priority, file, line, func_name, format, args);
+	va_end(args);
+}
+
+struct dmmp_context *dmmp_context_new(void)
+{
+	struct dmmp_context *ctx = NULL;
+
+	ctx = (struct dmmp_context *) malloc(sizeof(struct dmmp_context));
+
+	if (ctx == NULL)
+		return NULL;
+
+	ctx->log_func = _dmmp_log_stderr;
+	ctx->log_priority = DMMP_LOG_PRIORITY_DEFAULT;
+	ctx->userdata = NULL;
+	ctx->tmo = _DEFAULT_UXSOCK_TIMEOUT;
+
+	return ctx;
+}
+
+void dmmp_context_free(struct dmmp_context *ctx)
+{
+	free(ctx);
+}
+
+void dmmp_context_log_priority_set(struct dmmp_context *ctx, int priority)
+{
+	assert(ctx != NULL);
+	ctx->log_priority = priority;
+}
+
+void dmmp_context_timeout_set(struct dmmp_context *ctx, unsigned int tmo)
+{
+	assert(ctx != NULL);
+	ctx->tmo = tmo;
+}
+
+void dmmp_context_log_func_set
+	(struct dmmp_context *ctx,
+	 void (*log_func)(struct dmmp_context *ctx, int priority,
+			  const char *file, int line, const char *func_name,
+			  const char *format, va_list args))
+{
+	assert(ctx != NULL);
+	ctx->log_func = log_func;
+}
+
+void dmmp_context_userdata_set(struct dmmp_context *ctx, void *userdata)
+{
+	assert(ctx != NULL);
+	ctx->userdata = userdata;
+}
+
+int dmmp_mpath_array_get(struct dmmp_context *ctx,
+			 struct dmmp_mpath ***dmmp_mps, uint32_t *dmmp_mp_count)
+{
+	struct dmmp_mpath *dmmp_mp = NULL;
+	int rc = DMMP_OK;
+	char *j_str = NULL;
+	json_object *j_obj = NULL;
+	json_object *j_obj_map = NULL;
+	enum json_tokener_error j_err = json_tokener_success;
+	json_tokener *j_token = NULL;
+	struct array_list *ar_maps = NULL;
+	uint32_t i = 0;
+	int cur_json_major_version = -1;
+	int ar_maps_len = -1;
+	int socket_fd = -1;
+	int errno_save = 0;
+
+	assert(ctx != NULL);
+	assert(dmmp_mps != NULL);
+	assert(dmmp_mp_count != NULL);
+
+	*dmmp_mps = NULL;
+	*dmmp_mp_count = 0;
+
+	socket_fd = mpath_connect();
+	if (socket_fd == -1) {
+		_error(ctx, "IPC failed with error %d", errno);
+		rc = DMMP_ERR_IPC_ERROR;
+		goto out;
+	}
+
+	if (mpath_process_cmd(socket_fd, _DMMP_IPC_SHOW_JSON_CMD,
+			      &j_str, ctx->tmo) != 0) {
+		errno_save = errno;
+		mpath_disconnect(socket_fd);
+		if (errno_save == ETIMEDOUT) {
+			rc = DMMP_ERR_IPC_TIMEOUT;
+			_error(ctx, "IPC communication timeout, try to "
+			       "increase it via dmmp_context_timeout_set()");
+			goto out;
+		}
+		_error(ctx, "IPC failed when process command '%s' with "
+		       "error %d",
+		       _DMMP_IPC_SHOW_JSON_CMD, errno_save);
+		rc = DMMP_ERR_IPC_ERROR;
+		goto out;
+	}
+	mpath_disconnect(socket_fd);
+
+	if ((j_str == NULL) || (strlen(j_str) == 0)) {
+		_error(ctx, "IPC return empty reply for command %s",
+		       _DMMP_IPC_SHOW_JSON_CMD);
+		rc = DMMP_ERR_IPC_ERROR;
+		goto out;
+	}
+
+	_debug(ctx, "Got json output from multipathd: '%s'", j_str);
+	j_token = json_tokener_new();
+	if (j_token == NULL) {
+		rc = DMMP_ERR_BUG;
+		_error(ctx, "BUG: json_tokener_new() retuned NULL");
+		goto out;
+	}
+	j_obj = json_tokener_parse_ex(j_token, j_str, strlen(j_str) + 1);
+
+	if (j_obj == NULL) {
+		rc = DMMP_ERR_IPC_ERROR;
+		j_err = json_tokener_get_error(j_token);
+		_error(ctx, "Failed to parse JSON output from multipathd IPC: "
+		       "%s", json_tokener_error_desc(j_err));
+		goto out;
+	}
+
+	_json_obj_get_value(ctx, j_obj, cur_json_major_version,
+			    _DMMP_JSON_MAJOR_KEY, json_type_int,
+			    json_object_get_int, rc, out);
+
+	if (cur_json_major_version != _DMMP_JSON_MAJOR_VERSION) {
+		rc = DMMP_ERR_INCOMPATIBLE;
+		_error(ctx, "Incompatible multipathd JSON major version %d, "
+		       "should be %d", cur_json_major_version,
+		       _DMMP_JSON_MAJOR_VERSION);
+		goto out;
+	}
+	_debug(ctx, "multipathd JSON major version(%d) check pass",
+	       _DMMP_JSON_MAJOR_VERSION);
+
+	_json_obj_get_value(ctx, j_obj, ar_maps, _DMMP_JSON_MAPS_KEY,
+			    json_type_array, json_object_get_array, rc, out);
+
+	if (ar_maps == NULL) {
+		rc = DMMP_ERR_BUG;
+		_error(ctx, "BUG: Got NULL map array from "
+		       "_json_obj_get_value()");
+		goto out;
+	}
+
+
+	ar_maps_len = array_list_length(ar_maps);
+	if (ar_maps_len < 0) {
+		rc = DMMP_ERR_BUG;
+		_error(ctx, "BUG: Got negative length for ar_maps");
+		goto out;
+	}
+	else if (ar_maps_len == 0)
+		goto out;
+	else
+		*dmmp_mp_count = ar_maps_len & UINT32_MAX;
+
+	*dmmp_mps = (struct dmmp_mpath **)
+		malloc(sizeof(struct dmmp_mpath *) * (*dmmp_mp_count));
+	_dmmp_alloc_null_check(ctx, dmmp_mps, rc, out);
+	for (; i < *dmmp_mp_count; ++i)
+		(*dmmp_mps)[i] = NULL;
+
+	for (i = 0; i < *dmmp_mp_count; ++i) {
+		j_obj_map = array_list_get_idx(ar_maps, i);
+		if (j_obj_map == NULL) {
+			rc = DMMP_ERR_BUG;
+			_error(ctx, "BUG: array_list_get_idx() return NULL");
+			goto out;
+		}
+
+		dmmp_mp = _dmmp_mpath_new();
+		_dmmp_alloc_null_check(ctx, dmmp_mp, rc, out);
+		(*dmmp_mps)[i] = dmmp_mp;
+		_good(_dmmp_mpath_update(ctx, dmmp_mp, j_obj_map), rc, out);
+	}
+
+out:
+	free(j_str);
+	if (j_token != NULL)
+		json_tokener_free(j_token);
+	if (j_obj != NULL)
+		json_object_put(j_obj);
+
+	if (rc != DMMP_OK) {
+		dmmp_mpath_array_free(*dmmp_mps, *dmmp_mp_count);
+		*dmmp_mps = NULL;
+		*dmmp_mp_count = 0;
+	}
+
+	return rc;
+}
diff --git a/libdmmp/libdmmp.pc.in b/libdmmp/libdmmp.pc.in
new file mode 100644
index 0000000..ebb8cad
--- /dev/null
+++ b/libdmmp/libdmmp.pc.in
@@ -0,0 +1,9 @@
+includedir=__INCLUDEDIR__
+libdir=__LIBDIR__
+
+Name: libdmmp
+Version: __VERSION__
+Description: Device mapper multipath management library
+Requires:
+Libs: -L${libdir} -ldmmp
+Cflags: -I${includedir}
diff --git a/libdmmp/libdmmp/libdmmp.h b/libdmmp/libdmmp/libdmmp.h
new file mode 100644
index 0000000..8de60c0
--- /dev/null
+++ b/libdmmp/libdmmp/libdmmp.h
@@ -0,0 +1,607 @@
+/*
+ * Copyright (C) 2015 - 2016 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Gris Ge <fge@redhat.com>
+ *         Todd Gill <tgill@redhat.com>
+ */
+
+
+#ifndef _LIB_DMMP_H_
+#define _LIB_DMMP_H_
+
+#include <stdint.h>
+#include <stdarg.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define DMMP_DLL_EXPORT		__attribute__ ((visibility ("default")))
+#define DMMP_DLL_LOCAL		__attribute__ ((visibility ("hidden")))
+
+#define DMMP_OK				0
+#define DMMP_ERR_BUG			1
+#define DMMP_ERR_NO_MEMORY		2
+#define DMMP_ERR_IPC_TIMEOUT		3
+#define DMMP_ERR_IPC_ERROR		4
+#define DMMP_ERR_NO_DAEMON		5
+#define DMMP_ERR_INCOMPATIBLE		6
+
+/*
+ * Use the syslog severity level as log priority
+ */
+#define DMMP_LOG_PRIORITY_ERROR		3
+#define DMMP_LOG_PRIORITY_WARNING	4
+#define DMMP_LOG_PRIORITY_INFO		6
+#define DMMP_LOG_PRIORITY_DEBUG		7
+
+#define DMMP_LOG_PRIORITY_DEFAULT	DMMP_LOG_PRIORITY_WARNING
+
+/**
+ * dmmp_log_priority_str() - Convert log priority to string.
+ *
+ * Convert log priority to string (const char *).
+ * @priority:
+ *	int. Log priority.
+ * Return:
+ *	const char *. Valid string are\::
+ *
+ *	* "ERROR" for DMMP_LOG_PRIORITY_ERROR
+ *
+ *	* "WARN " for DMMP_LOG_PRIORITY_WARNING
+ *
+ *	* "INFO " for DMMP_LOG_PRIORITY_INFO
+ *
+ *	* "DEBUG" for DMMP_LOG_PRIORITY_DEBUG
+ *
+ *	* "Invalid argument" for invalid log priority.
+ */
+DMMP_DLL_EXPORT const char *dmmp_log_priority_str(int priority);
+
+DMMP_DLL_EXPORT struct dmmp_context;
+
+DMMP_DLL_EXPORT struct dmmp_mpath;
+
+DMMP_DLL_EXPORT struct dmmp_path_group;
+
+#define DMMP_PATH_GROUP_STATUS_UNKNOWN	0
+#define DMMP_PATH_GROUP_STATUS_ENABLED	1
+#define DMMP_PATH_GROUP_STATUS_DISABLED	2
+#define DMMP_PATH_GROUP_STATUS_ACTIVE	3
+
+DMMP_DLL_EXPORT struct dmmp_path;
+
+#define DMMP_PATH_STATUS_UNKNOWN	0
+//#define DMMP_PATH_STATUS_UNCHECKED	1
+// ^ print.h does not expose this.
+#define DMMP_PATH_STATUS_DOWN		2
+#define DMMP_PATH_STATUS_UP		3
+#define DMMP_PATH_STATUS_SHAKY		4
+#define DMMP_PATH_STATUS_GHOST		5
+#define DMMP_PATH_STATUS_PENDING	6
+#define DMMP_PATH_STATUS_TIMEOUT	7
+//#define DMMP_PATH_STATUS_REMOVED	8
+// ^ print.h does not expose this.
+#define DMMP_PATH_STATUS_DELAYED	9
+
+/**
+ * dmmp_strerror() - Convert error code to string.
+ *
+ * Convert error code (int) to string (const char *):
+ *
+ *	* DMMP_OK -- "OK"
+ *
+ *	* DMMP_ERR_BUG -- "BUG of libdmmp library"
+ *
+ *	* DMMP_ERR_NO_MEMORY -- "Out of memory"
+ *
+ *	* DMMP_ERR_IPC_TIMEOUT -- "Timeout when communicate with multipathd,
+ *	  try to set bigger timeout value via dmmp_context_timeout_set ()"
+ *
+ *	* DMMP_ERR_IPC_ERROR -- "Error when communicate with multipathd daemon"
+ *
+ *	* DMMP_ERR_NO_DAEMON -- "The multipathd daemon not started"
+ *
+ *	* DMMP_ERR_INCOMPATIBLE -- "The multipathd daemon version is not
+ *	  compatible with current library"
+ *
+ *	* Other invalid error number -- "Invalid argument"
+ *
+ * @rc:
+ *	int. Return code by libdmmp functions. When provided error code is not a
+ *	valid error code, return "Invalid argument".
+ * Return:
+ *	const char *. The meaning of provided error code.
+ *
+ */
+DMMP_DLL_EXPORT const char *dmmp_strerror(int rc);
+
+/**
+ * dmmp_context_new() - Create struct dmmp_context.
+ *
+ * The default logging level is
+ * DMMP_LOG_PRIORITY_WARNING/DMMP_LOG_PRIORITY_DEFAULT which means
+ * only warning and error message will be forward to log handler function.
+ * The default log handler function will print log message to STDERR,
+ * to change so, please use dmmp_context_log_func_set() to set your own log
+ * handler, check manpage libdmmp.h(3) for detail.
+ *
+ * Return:
+ *	Pointer of 'struct dmmp_context'. Should be freed by
+ *	dmmp_context_free().
+ */
+DMMP_DLL_EXPORT struct dmmp_context *dmmp_context_new(void);
+
+/**
+ * dmmp_context_free() - Release the memory of struct dmmp_context.
+ *
+ * Release the memory of struct dmmp_context, but the userdata memory defined
+ * via dmmp_context_userdata_set() will not be touched.
+ *
+ * @ctx:
+ *	Pointer of 'struct dmmp_context'.
+ * Return:
+ *	void
+ */
+DMMP_DLL_EXPORT void dmmp_context_free(struct dmmp_context *ctx);
+
+/**
+ * dmmp_context_timeout_set() - Set IPC timeout.
+ *
+ * By default, the IPC to multipathd daemon will timeout after 60 seconds.
+ *
+ * @ctx:
+ *	Pointer of 'struct dmmp_context'.
+ *	If this pointer is NULL, your program will be terminated by assert.
+ *
+ * @tmo:
+ *	Timeout in milliseconds(1 seconds equal 1000 milliseconds).
+ *
+ * Return:
+ *	void
+ */
+DMMP_DLL_EXPORT void dmmp_context_timeout_set(struct dmmp_context *ctx,
+					      unsigned int tmo);
+
+/**
+ * dmmp_context_timeout_get() - Get IPC timeout.
+ *
+ * Retrieve timeout value of IPC connection to multipathd daemon.
+ *
+ * @ctx:
+ *	Pointer of 'struct dmmp_context'.
+ *	If this pointer is NULL, your program will be terminated by assert.
+ *
+ * Return:
+ *	unsigned int. Timeout in milliseconds.
+ */
+DMMP_DLL_EXPORT unsigned int dmmp_context_timeout_get(struct dmmp_context *ctx);
+
+/**
+ * dmmp_context_log_priority_set() - Set log priority.
+ *
+ * When library generates log message, only equal or more important(less value)
+ * message will be forwarded to log handler function.
+ * Valid log priority values are\::
+ *
+ *	* DMMP_LOG_PRIORITY_ERROR -- 3
+ *
+ *	* DMMP_LOG_PRIORITY_WARNING -- 4
+ *
+ *	* DMMP_LOG_PRIORITY_INFO -- 5
+ *
+ *	* DMMP_LOG_PRIORITY_DEBUG -- 7
+ *
+ * @ctx:
+ *	Pointer of 'struct dmmp_context'.
+ *	If this pointer is NULL, your program will be terminated by assert.
+ *
+ * @priority:
+ *	int, log priority.
+ *
+ * Return:
+ *	void
+ */
+DMMP_DLL_EXPORT void dmmp_context_log_priority_set(struct dmmp_context *ctx,
+						   int priority);
+
+/**
+ * dmmp_context_log_priority_get() - Get log priority.
+ *
+ * Retrieve current log priority. Valid log priority values are\::
+ *
+ *	* DMMP_LOG_PRIORITY_ERROR -- 3
+ *
+ *	* DMMP_LOG_PRIORITY_WARNING -- 4
+ *
+ *	* DMMP_LOG_PRIORITY_INFO -- 5
+ *
+ *	* DMMP_LOG_PRIORITY_DEBUG -- 7
+ *
+ * @ctx:
+ *	Pointer of 'struct dmmp_context'.
+ *	If this pointer is NULL, your program will be terminated by assert.
+ * Return:
+ *	int, log priority.
+ */
+DMMP_DLL_EXPORT int dmmp_context_log_priority_get(struct dmmp_context *ctx);
+
+/**
+ * dmmp_context_log_func_set() - Set log handler function.
+ *
+ * Set custom log handler. The log handler will be invoked when log message
+ * is equal or more important(less value) than log priority setting.
+ * Please check manpage libdmmp.h(3) for detail usage.
+ *
+ * @ctx:
+ *	Pointer of 'struct dmmp_context'.
+ *	If this pointer is NULL, your program will be terminated by assert.
+ * @log_func:
+ *	Pointer of log handler function.
+ * Return:
+ *	void
+ */
+DMMP_DLL_EXPORT void dmmp_context_log_func_set
+	(struct dmmp_context *ctx,
+	 void (*log_func)
+	 (struct dmmp_context *ctx, int priority,
+	  const char *file, int line, const char *func_name,
+	  const char *format, va_list args));
+
+/**
+ * dmmp_context_userdata_set() - Set user data pointer.
+ *
+ * Store user data pointer into 'struct dmmp_context'.
+ *
+ * @ctx:
+ *	Pointer of 'struct dmmp_context'.
+ *	If this pointer is NULL, your program will be terminated by assert.
+ * @userdata:
+ *	Pointer of user defined data.
+ * Return:
+ *	void
+ */
+DMMP_DLL_EXPORT void dmmp_context_userdata_set(struct dmmp_context *ctx,
+					       void *userdata);
+
+/**
+ * dmmp_context_userdata_get() - Get user data pointer.
+ *
+ * Retrieve user data pointer from 'struct dmmp_context'.
+ *
+ * @ctx:
+ *	Pointer of 'struct dmmp_context'.
+ *	If this pointer is NULL, your program will be terminated by assert.
+ * Return:
+ *	void *. Pointer of user defined data.
+ */
+DMMP_DLL_EXPORT void *dmmp_context_userdata_get(struct dmmp_context *ctx);
+
+/**
+ * dmmp_mpath_array_get() - Query all existing multipath devices.
+ *
+ * Query all existing multipath devices and store them into a pointer array.
+ * The memory of 'dmmp_mps' should be freed via dmmp_mpath_array_free().
+ *
+ * @ctx:
+ *	Pointer of 'struct dmmp_context'.
+ *	If this pointer is NULL, your program will be terminated by assert.
+ * @dmmp_mps:
+ *	Output pointer array of 'struct dmmp_mpath'.
+ *	If this pointer is NULL, your program will be terminated by assert.
+ * @dmmp_mp_count:
+ *	Output pointer of uint32_t. Hold the size of 'dmmp_mps' pointer array.
+ *	If this pointer is NULL, your program will be terminated by assert.
+ *
+ * Return:
+ *	int. Valid error codes are\::
+ *
+ *	* DMMP_OK
+ *
+ *	* DMMP_ERR_BUG
+ *
+ *	* DMMP_ERR_NO_MEMORY
+ *
+ *	* DMMP_ERR_NO_DAEMON
+ *
+ *	* DMMP_ERR_INCONSISTENT_DATA
+ *
+ *	Error number could be converted to string by dmmp_strerror().
+ */
+DMMP_DLL_EXPORT int dmmp_mpath_array_get(struct dmmp_context *ctx,
+					 struct dmmp_mpath ***dmmp_mps,
+					 uint32_t *dmmp_mp_count);
+
+/**
+ * dmmp_mpath_array_free() - Free 'struct dmmp_mpath' pointer array.
+ *
+ * Free the 'dmmp_mps' pointer array generated by dmmp_mpath_array_get().
+ * If provided 'dmmp_mps' pointer is NULL or dmmp_mp_count == 0, do nothing.
+ *
+ * @dmmp_mps:
+ *	Pointer of 'struct dmmp_mpath' array.
+ * @dmmp_mp_count:
+ *	uint32_t, the size of 'dmmp_mps' pointer array.
+ * Return:
+ *	void
+ */
+DMMP_DLL_EXPORT void dmmp_mpath_array_free(struct dmmp_mpath **dmmp_mps,
+					   uint32_t dmmp_mp_count);
+
+/**
+ * dmmp_mpath_wwid_get() - Retrieve WWID of certain mpath.
+ *
+ * @dmmp_mp:
+ *	Pointer of 'struct dmmp_mpath'.
+ *	If this pointer is NULL, your program will be terminated by assert.
+ * Return:
+ *	const char *. No need to free this memory, the resources will get
+ *	freed when dmmp_mpath_array_free().
+ */
+DMMP_DLL_EXPORT const char *dmmp_mpath_wwid_get(struct dmmp_mpath *dmmp_mp);
+
+/**
+ * dmmp_mpath_name_get() - Retrieve name(alias) of certain mpath.
+ *
+ * Retrieve the name (also known as alias) of certain mpath.
+ * When the config 'user_friendly_names' been set 'no', the name will be
+ * identical to WWID retrieved by dmmp_mpath_wwid_get().
+ *
+ * @dmmp_mp:
+ *	Pointer of 'struct dmmp_mpath'.
+ *	If this pointer is NULL, your program will be terminated by assert.
+ * Return:
+ *	const char *. No need to free this memory, the resources will get
+ *	freed when dmmp_mpath_array_free().
+ */
+DMMP_DLL_EXPORT const char *dmmp_mpath_name_get(struct dmmp_mpath *dmmp_mp);
+
+/**
+ * dmmp_path_group_array_get() - Retrieve path groups pointer array.
+ *
+ * Retrieve the path groups of certain mpath.
+ *
+ * The memory of output pointer array is hold by 'struct dmmp_mpath', no
+ * need to free this memory, the resources will got freed when
+ * dmmp_mpath_array_free().
+ *
+ * @dmmp_mp:
+ *	Pointer of 'struct dmmp_mpath'.
+ *	If this pointer is NULL, your program will be terminated by assert.
+ * @dmmp_pgs:
+ *	Output pointer of 'struct dmmp_path_group' pointer array.
+ *	If this pointer is NULL, your program will be terminated by assert.
+ * @dmmp_pg_count:
+ *	Output pointer of uint32_t. Hold the size of 'dmmp_pgs' pointer array.
+ *	If this pointer is NULL, your program will be terminated by assert.
+ * Return:
+ *	void
+ */
+DMMP_DLL_EXPORT void dmmp_path_group_array_get
+	(struct dmmp_mpath *dmmp_mp, struct dmmp_path_group ***dmmp_pgs,
+	 uint32_t *dmmp_pg_count);
+
+/**
+ * dmmp_path_group_id_get() - Retrieve path group ID.
+ *
+ * Retrieve the path group ID which could be used to switch active path group
+ * via command\::
+ *
+ *	multipathd -k'switch multipath mpathb group $id'
+ *
+ * @dmmp_pg:
+ *	Pointer of 'struct dmmp_path_group'.
+ *	If this pointer is NULL, your program will be terminated by assert.
+ * Return:
+ *	uint32_t.
+ */
+DMMP_DLL_EXPORT uint32_t dmmp_path_group_id_get
+	(struct dmmp_path_group *dmmp_pg);
+
+/**
+ * dmmp_path_group_priority_get() - Retrieve path group priority.
+ *
+ * The enabled path group with highest priority will be next active path group
+ * if active path group down.
+ *
+ * @dmmp_pg:
+ *	Pointer of 'struct dmmp_path_group'.
+ *	If this pointer is NULL, your program will be terminated by assert.
+ * Return:
+ *	uint32_t.
+ */
+DMMP_DLL_EXPORT uint32_t dmmp_path_group_priority_get
+	(struct dmmp_path_group *dmmp_pg);
+
+/**
+ * dmmp_path_group_status_get() - Retrieve path group status.
+ *
+ * The valid path group statuses are\::
+ *
+ *	* DMMP_PATH_GROUP_STATUS_UNKNOWN
+ *
+ *	* DMMP_PATH_GROUP_STATUS_ENABLED  -- standby to be active
+ *
+ *	* DMMP_PATH_GROUP_STATUS_DISABLED -- disabled due to all path down
+ *
+ *	* DMMP_PATH_GROUP_STATUS_ACTIVE -- selected to handle I/O
+ *
+ * @dmmp_pg:
+ *	Pointer of 'struct dmmp_path_group'.
+ *	If this pointer is NULL, your program will be terminated by assert.
+ * Return:
+ *	uint32_t.
+ */
+DMMP_DLL_EXPORT uint32_t dmmp_path_group_status_get
+	(struct dmmp_path_group *dmmp_pg);
+
+/**
+ * dmmp_path_group_status_str() - Convert path group status to string.
+ *
+ * Convert path group status uint32_t to string (const char *).
+ *
+ * @pg_status:
+ *	uint32_t. Path group status.
+ *	When provided value is not a valid path group status, return "Invalid
+ *	argument".
+ * Return:
+ *	const char *. Valid string are\::
+ *
+ *	* "Invalid argument"
+ *
+ *	* "undef"
+ *
+ *	* "enabled"
+ *
+ *	* "disabled"
+ *
+ *	* "active"
+ */
+DMMP_DLL_EXPORT const char *dmmp_path_group_status_str(uint32_t pg_status);
+
+/**
+ * dmmp_path_group_selector_get() - Retrieve path group selector.
+ *
+ * Path group selector determine which path in active path group will be
+ * use to next I/O.
+ *
+ * @dmmp_pg:
+ *	Pointer of 'struct dmmp_path_group'.
+ *	If this pointer is NULL, your program will be terminated by assert.
+ * Return:
+ *	const char *.
+ */
+DMMP_DLL_EXPORT const char *dmmp_path_group_selector_get
+	(struct dmmp_path_group *dmmp_pg);
+
+/**
+ * dmmp_path_array_get() - Retrieve path pointer array.
+ *
+ * The memory of output pointer array is hold by 'struct dmmp_mpath', no
+ * need to free this memory, the resources will got freed when
+ * dmmp_mpath_array_free().
+ *
+ * @dmmp_pg:
+ *	Pointer of 'struct dmmp_path_group'.
+ *	If this pointer is NULL, your program will be terminated by assert.
+ * @dmmp_ps:
+ *	Output pointer of 'struct dmmp_path' pointer array.
+ *	If this pointer is NULL, your program will be terminated by assert.
+ * @dmmp_p_count:
+ *	Output pointer of uint32_t. Hold the size of 'dmmp_ps' pointer array.
+ *	If this pointer is NULL, your program will be terminated by assert.
+ * Return:
+ *	void
+ */
+DMMP_DLL_EXPORT void dmmp_path_array_get(struct dmmp_path_group *dmmp_pg,
+					 struct dmmp_path ***dmmp_ps,
+					 uint32_t *dmmp_p_count);
+
+/**
+ * dmmp_path_blk_name_get() - Retrieve block name.
+ *
+ * Retrieve block name of certain path. The example of block names are 'sda',
+ * 'nvme0n1'.
+ *
+ * @dmmp_p:
+ *	Pointer of 'struct dmmp_path'.
+ *	If this pointer is NULL, your program will be terminated by assert.
+ * Return:
+ *	const char *. No need to free this memory, the resources will get
+ *	freed when dmmp_mpath_array_free().
+ */
+DMMP_DLL_EXPORT const char *dmmp_path_blk_name_get(struct dmmp_path *dmmp_p);
+
+/**
+ * dmmp_path_status_get() - Retrieve the path status.
+ *
+ * The valid path statuses are\::
+ *
+ *	* DMMP_PATH_STATUS_UNKNOWN
+ *
+ *	* DMMP_PATH_STATUS_DOWN
+ *
+ *	Path is down and you shouldn't try to send commands to it.
+ *
+ *	* DMMP_PATH_STATUS_UP
+ *
+ *	Path is up and I/O can be sent to it.
+ *
+ *	* DMMP_PATH_STATUS_SHAKY
+ *
+ *	Only emc_clariion checker when path not available for "normal"
+ *	operations.
+ *
+ *	* DMMP_PATH_STATUS_GHOST
+ *
+ *	Only hp_sw and rdac checkers.
+ *	Indicates a "passive/standby" path on active/passive
+ *	HP arrays. These paths will return valid answers to certain SCSI
+ *	commands (tur, read_capacity, inquiry, start_stop), but will fail I/O
+ *	commands.
+ *	The path needs an initialization command to be sent to it in order for
+ *	I/Os to succeed.
+ *
+ *	* DMMP_PATH_STATUS_PENDING
+ *	Available for all async checkers when a check IO is in flight.
+ *
+ *	* DMMP_PATH_STATUS_TIMEOUT
+ *
+ *	Only tur checker when command timed out.
+ *
+ *	* DMMP_PATH_STATUS_DELAYED
+ *	If a path fails after being up for less than delay_watch_checks checks,
+ *	when it comes back up again, it will not be marked as up until it has
+ *	been up for delay_wait_checks checks. During this time, it is marked as
+ *	"delayed".
+ *
+ * @dmmp_p:
+ *	Pointer of 'struct dmmp_path'.
+ *	If this pointer is NULL, your program will be terminated by assert.
+ * Return:
+ *	uint32_t.
+ */
+DMMP_DLL_EXPORT uint32_t dmmp_path_status_get(struct dmmp_path *dmmp_p);
+
+/**
+ * dmmp_path_status_str() - Convert path status to string.
+ *
+ * Convert path status uint32_t to string (const char *):
+ *
+ *	* DMMP_PATH_STATUS_UNKNOWN -- "undef"
+ *	* DMMP_PATH_STATUS_DOWN -- "faulty"
+ *	* DMMP_PATH_STATUS_UP -- "ready"
+ *	* DMMP_PATH_STATUS_SHAKY -- "shaky"
+ *	* DMMP_PATH_STATUS_GHOST -- "ghost"
+ *	* DMMP_PATH_STATUS_PENDING -- "pending"
+ *	* DMMP_PATH_STATUS_TIMEOUT -- "timeout"
+ *	* DMMP_PATH_STATUS_REMOVED -- "removed"
+ *	* DMMP_PATH_STATUS_DELAYED -- "delayed"
+ *
+ * @path_status:
+ *	uint32_t. Path status.
+ *	When provided value is not a valid path status, return
+ *	"Invalid argument".
+ * Return:
+ *	const char *. The meaning of status value.
+ */
+DMMP_DLL_EXPORT const char *dmmp_path_status_str(uint32_t path_status);
+
+#ifdef __cplusplus
+} /* End of extern "C" */
+#endif
+
+#endif /* End of _LIB_DMMP_H_ */
diff --git a/libdmmp/libdmmp_misc.c b/libdmmp/libdmmp_misc.c
new file mode 100644
index 0000000..27f1161
--- /dev/null
+++ b/libdmmp/libdmmp_misc.c
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 - 2016 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Gris Ge <fge@redhat.com>
+ *         Todd Gill <tgill@redhat.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <limits.h>
+#include <assert.h>
+#include <json.h>
+
+#include "libdmmp/libdmmp.h"
+#include "libdmmp_private.h"
+
+#define _DMMP_LOG_STRERR_ALIGN_WIDTH	80
+/* ^ Only used in _dmmp_log_stderr() for pretty log output.
+ *   When provided log message is less than 80 bytes, fill it with space, then
+ *   print code file name, function name, line after the 80th bytes.
+ */
+
+static const struct _num_str_conv _DMMP_RC_MSG_CONV[] = {
+	{DMMP_OK, "OK"},
+	{DMMP_ERR_NO_MEMORY, "Out of memory"},
+	{DMMP_ERR_BUG, "BUG of libdmmp library"},
+	{DMMP_ERR_IPC_TIMEOUT, "Timeout when communicate with multipathd, "
+			       "try to increase it via "
+				"dmmp_context_timeout_set()"},
+	{DMMP_ERR_IPC_ERROR, "Error when communicate with multipathd daemon"},
+	{DMMP_ERR_NO_DAEMON, "The multipathd daemon not started"},
+	{DMMP_ERR_INCOMPATIBLE, "Incompatible multipathd daemon version"},
+};
+
+_dmmp_str_func_gen(dmmp_strerror, int, rc, _DMMP_RC_MSG_CONV);
+
+static const struct _num_str_conv _DMMP_PRI_CONV[] = {
+	{DMMP_LOG_PRIORITY_DEBUG, "DEBUG"},
+	{DMMP_LOG_PRIORITY_INFO, "INFO"},
+	{DMMP_LOG_PRIORITY_WARNING, "WARNING"},
+	{DMMP_LOG_PRIORITY_ERROR, "ERROR"},
+};
+_dmmp_str_func_gen(dmmp_log_priority_str, int, priority, _DMMP_PRI_CONV);
+
+void _dmmp_log_stderr(struct dmmp_context *ctx, int priority,
+		      const char *file, int line, const char *func_name,
+		      const char *format, va_list args)
+{
+	int printed_bytes = 0;
+	void *userdata = NULL;
+
+	printed_bytes += fprintf(stderr, "libdmmp %s: ",
+				 dmmp_log_priority_str(priority));
+	printed_bytes += vfprintf(stderr, format, args);
+
+	userdata = dmmp_context_userdata_get(ctx);
+	if (userdata != NULL)
+		fprintf(stderr, "(userdata address: %p)",
+			userdata);
+	/* ^ Just demonstrate how userdata could be used and
+	 *   bypass clang static analyzer about unused ctx argument warning
+	 */
+
+	if (printed_bytes < _DMMP_LOG_STRERR_ALIGN_WIDTH) {
+		fprintf(stderr, "%*s # %s:%s():%d\n",
+			_DMMP_LOG_STRERR_ALIGN_WIDTH - printed_bytes, "", file,
+			func_name, line);
+	} else {
+		fprintf(stderr, " # %s:%s():%d\n", file, func_name, line);
+	}
+}
diff --git a/libdmmp/libdmmp_mp.c b/libdmmp/libdmmp_mp.c
new file mode 100644
index 0000000..437a84c
--- /dev/null
+++ b/libdmmp/libdmmp_mp.c
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2015 - 2016 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Gris Ge <fge@redhat.com>
+ *         Todd Gill <tgill@redhat.com>
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <json.h>
+
+#include "libdmmp/libdmmp.h"
+#include "libdmmp_private.h"
+
+struct dmmp_mpath {
+	char *wwid;
+	char *alias;
+	uint32_t dmmp_pg_count;
+	struct dmmp_path_group **dmmp_pgs;
+};
+
+_dmmp_getter_func_gen(dmmp_mpath_name_get, struct dmmp_mpath, dmmp_mp,
+		      alias, const char *);
+_dmmp_getter_func_gen(dmmp_mpath_wwid_get, struct dmmp_mpath, dmmp_mp,
+		      wwid, const char *);
+
+struct dmmp_mpath *_dmmp_mpath_new(void)
+{
+	struct dmmp_mpath *dmmp_mp = NULL;
+
+	dmmp_mp = (struct dmmp_mpath *) malloc(sizeof(struct dmmp_mpath));
+
+	if (dmmp_mp != NULL) {
+		dmmp_mp->wwid = NULL;
+		dmmp_mp->alias = NULL;
+		dmmp_mp->dmmp_pg_count = 0;
+		dmmp_mp->dmmp_pgs = NULL;
+	}
+	return dmmp_mp;
+}
+
+int _dmmp_mpath_update(struct dmmp_context *ctx, struct dmmp_mpath *dmmp_mp,
+		       json_object *j_obj_map)
+{
+	int rc = DMMP_OK;
+	const char *wwid = NULL;
+	const char *alias = NULL;
+	struct array_list *ar_pgs = NULL;
+	int ar_pgs_len = -1;
+	uint32_t i = 0;
+	struct dmmp_path_group *dmmp_pg = NULL;
+
+	assert(ctx != NULL);
+	assert(dmmp_mp != NULL);
+	assert(j_obj_map != NULL);
+
+	_json_obj_get_value(ctx, j_obj_map, wwid, "uuid", json_type_string,
+			    json_object_get_string, rc, out);
+	_json_obj_get_value(ctx, j_obj_map, alias, "name", json_type_string,
+			    json_object_get_string, rc, out);
+
+	_dmmp_null_or_empty_str_check(ctx, wwid, rc, out);
+	_dmmp_null_or_empty_str_check(ctx, alias, rc, out);
+
+	dmmp_mp->wwid = strdup(wwid);
+	_dmmp_alloc_null_check(ctx, dmmp_mp->wwid, rc, out);
+	dmmp_mp->alias = strdup(alias);
+	_dmmp_alloc_null_check(ctx, dmmp_mp->alias, rc, out);
+
+	_json_obj_get_value(ctx, j_obj_map, ar_pgs, "path_groups",
+			    json_type_array, json_object_get_array, rc, out);
+	ar_pgs_len = array_list_length(ar_pgs);
+	if (ar_pgs_len < 0) {
+		rc = DMMP_ERR_BUG;
+		_error(ctx, "BUG: Got negative length for ar_pgs");
+		goto out;
+	}
+	else if (ar_pgs_len == 0)
+		goto out;
+	else
+		dmmp_mp->dmmp_pg_count = ar_pgs_len & UINT32_MAX;
+
+	dmmp_mp->dmmp_pgs = (struct dmmp_path_group **)
+		malloc(sizeof(struct dmmp_path_group *) *
+		       dmmp_mp->dmmp_pg_count);
+	_dmmp_alloc_null_check(ctx, dmmp_mp->dmmp_pgs, rc, out);
+	for (; i < dmmp_mp->dmmp_pg_count; ++i)
+		dmmp_mp->dmmp_pgs[i] = NULL;
+
+	for (i = 0; i < dmmp_mp->dmmp_pg_count; ++i) {
+		dmmp_pg = _dmmp_path_group_new();
+		_dmmp_alloc_null_check(ctx, dmmp_pg, rc, out);
+		dmmp_mp->dmmp_pgs[i] = dmmp_pg;
+		_good(_dmmp_path_group_update(ctx, dmmp_pg,
+					      array_list_get_idx(ar_pgs, i)),
+		      rc, out);
+	}
+
+	_debug(ctx, "Got mpath wwid: '%s', alias: '%s'", dmmp_mp->wwid,
+	       dmmp_mp->alias);
+
+out:
+	if (rc != DMMP_OK)
+		_dmmp_mpath_free(dmmp_mp);
+	return rc;
+}
+
+void _dmmp_mpath_free(struct dmmp_mpath *dmmp_mp)
+{
+	if (dmmp_mp == NULL)
+		return ;
+
+	free((char *) dmmp_mp->alias);
+	free((char *) dmmp_mp->wwid);
+
+	if (dmmp_mp->dmmp_pgs != NULL)
+		_dmmp_path_group_array_free(dmmp_mp->dmmp_pgs,
+					    dmmp_mp->dmmp_pg_count);
+
+	free(dmmp_mp);
+}
+
+void dmmp_path_group_array_get(struct dmmp_mpath *dmmp_mp,
+			       struct dmmp_path_group ***dmmp_pgs,
+			       uint32_t *dmmp_pg_count)
+{
+	assert(dmmp_mp != NULL);
+	assert(dmmp_pgs != NULL);
+	assert(dmmp_pg_count != NULL);
+
+	*dmmp_pgs = dmmp_mp->dmmp_pgs;
+	*dmmp_pg_count = dmmp_mp->dmmp_pg_count;
+}
diff --git a/libdmmp/libdmmp_path.c b/libdmmp/libdmmp_path.c
new file mode 100644
index 0000000..47a2162
--- /dev/null
+++ b/libdmmp/libdmmp_path.c
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2015 - 2016 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Gris Ge <fge@redhat.com>
+ *         Todd Gill <tgill@redhat.com>
+ */
+
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include <assert.h>
+#include <json.h>
+
+#include "libdmmp/libdmmp.h"
+#include "libdmmp_private.h"
+
+#define _DMMP_SHOW_PS_INDEX_BLK_NAME	0
+#define _DMMP_SHOW_PS_INDEX_SATAUS	1
+#define _DMMP_SHOW_PS_INDEX_WWID	2
+#define _DMMP_SHOW_PS_INDEX_PGID	3
+
+struct dmmp_path {
+	char *blk_name;
+	uint32_t status;
+};
+
+static const struct _num_str_conv _DMMP_PATH_STATUS_CONV[] = {
+	{DMMP_PATH_STATUS_UNKNOWN, "undef"},
+	{DMMP_PATH_STATUS_UP, "ready"},
+	{DMMP_PATH_STATUS_DOWN, "faulty"},
+	{DMMP_PATH_STATUS_SHAKY, "shaky"},
+	{DMMP_PATH_STATUS_GHOST, "ghost"},
+	{DMMP_PATH_STATUS_PENDING, "i/o pending"},
+	{DMMP_PATH_STATUS_TIMEOUT, "i/o timeout"},
+	{DMMP_PATH_STATUS_DELAYED, "delayed"},
+};
+
+_dmmp_str_func_gen(dmmp_path_status_str, uint32_t, path_status,
+		   _DMMP_PATH_STATUS_CONV);
+_dmmp_str_conv_func_gen(_dmmp_path_status_str_conv, ctx, path_status_str,
+			uint32_t, DMMP_PATH_STATUS_UNKNOWN,
+			_DMMP_PATH_STATUS_CONV);
+
+_dmmp_getter_func_gen(dmmp_path_blk_name_get, struct dmmp_path, dmmp_p,
+		      blk_name, const char *);
+_dmmp_getter_func_gen(dmmp_path_status_get, struct dmmp_path, dmmp_p,
+		      status, uint32_t);
+
+struct dmmp_path *_dmmp_path_new(void)
+{
+	struct dmmp_path *dmmp_p = NULL;
+
+	dmmp_p = (struct dmmp_path *) malloc(sizeof(struct dmmp_path));
+
+	if (dmmp_p != NULL) {
+		dmmp_p->blk_name = NULL;
+		dmmp_p->status = DMMP_PATH_STATUS_UNKNOWN;
+	}
+	return dmmp_p;
+}
+
+int _dmmp_path_update(struct dmmp_context *ctx, struct dmmp_path *dmmp_p,
+		      json_object *j_obj_p)
+{
+	int rc = DMMP_OK;
+	const char *blk_name = NULL;
+	const char *status_str = NULL;
+
+	assert(ctx != NULL);
+	assert(dmmp_p != NULL);
+	assert(j_obj_p != NULL);
+
+	_json_obj_get_value(ctx, j_obj_p, blk_name, "dev",
+			    json_type_string, json_object_get_string, rc, out);
+	_json_obj_get_value(ctx, j_obj_p, status_str, "chk_st",
+			    json_type_string, json_object_get_string, rc, out);
+
+	_dmmp_null_or_empty_str_check(ctx, blk_name, rc, out);
+	_dmmp_null_or_empty_str_check(ctx, status_str, rc, out);
+
+	dmmp_p->blk_name = strdup(blk_name);
+	_dmmp_alloc_null_check(ctx, dmmp_p->blk_name, rc, out);
+
+	dmmp_p->status = _dmmp_path_status_str_conv(ctx, status_str);
+
+	_debug(ctx, "Got path blk_name: '%s'", dmmp_p->blk_name);
+	_debug(ctx, "Got path status: %s(%" PRIu32 ")",
+	       dmmp_path_status_str(dmmp_p->status), dmmp_p->status);
+
+out:
+	if (rc != DMMP_OK)
+		_dmmp_path_free(dmmp_p);
+	return rc;
+}
+
+void _dmmp_path_free(struct dmmp_path *dmmp_p)
+{
+	if (dmmp_p == NULL)
+		return;
+	free(dmmp_p->blk_name);
+	free(dmmp_p);
+}
diff --git a/libdmmp/libdmmp_pg.c b/libdmmp/libdmmp_pg.c
new file mode 100644
index 0000000..5149161
--- /dev/null
+++ b/libdmmp/libdmmp_pg.c
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2015 - 2016 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Gris Ge <fge@redhat.com>
+ *         Todd Gill <tgill@redhat.com>
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <string.h>
+#include <assert.h>
+#include <json.h>
+
+#include "libdmmp/libdmmp.h"
+#include "libdmmp_private.h"
+
+#define _DMMP_SHOW_PGS_CMD "show groups raw format %w|%g|%p|%t|%s"
+#define _DMMP_SHOW_PG_INDEX_WWID	0
+#define _DMMP_SHOW_PG_INDEX_PG_ID	1
+#define _DMMP_SHOW_PG_INDEX_PRI		2
+#define _DMMP_SHOW_PG_INDEX_STATUS	3
+#define _DMMP_SHOW_PG_INDEX_SELECTOR	4
+
+struct dmmp_path_group {
+	uint32_t id;
+	/* ^ pgindex of struct path, will be used for path group switch */
+	uint32_t status;
+	uint32_t priority;
+	char *selector;
+	uint32_t dmmp_p_count;
+	struct dmmp_path **dmmp_ps;
+};
+
+static const struct _num_str_conv _DMMP_PATH_GROUP_STATUS_CONV[] = {
+	{DMMP_PATH_GROUP_STATUS_UNKNOWN, "undef"},
+	{DMMP_PATH_GROUP_STATUS_ACTIVE, "active"},
+	{DMMP_PATH_GROUP_STATUS_DISABLED, "disabled"},
+	{DMMP_PATH_GROUP_STATUS_ENABLED, "enabled"},
+};
+
+_dmmp_str_func_gen(dmmp_path_group_status_str, uint32_t, pg_status,
+		   _DMMP_PATH_GROUP_STATUS_CONV);
+_dmmp_str_conv_func_gen(_dmmp_path_group_status_str_conv, ctx, pg_status_str,
+			uint32_t, DMMP_PATH_GROUP_STATUS_UNKNOWN,
+			_DMMP_PATH_GROUP_STATUS_CONV);
+
+_dmmp_getter_func_gen(dmmp_path_group_id_get, struct dmmp_path_group, dmmp_pg,
+		      id, uint32_t);
+_dmmp_getter_func_gen(dmmp_path_group_status_get, struct dmmp_path_group,
+		      dmmp_pg, status, uint32_t);
+_dmmp_getter_func_gen(dmmp_path_group_priority_get, struct dmmp_path_group,
+		      dmmp_pg, priority, uint32_t);
+_dmmp_getter_func_gen(dmmp_path_group_selector_get, struct dmmp_path_group,
+		      dmmp_pg, selector, const char *);
+_dmmp_array_free_func_gen(_dmmp_path_group_array_free, struct dmmp_path_group,
+			  _dmmp_path_group_free);
+
+
+struct dmmp_path_group *_dmmp_path_group_new(void)
+{
+	struct dmmp_path_group *dmmp_pg = NULL;
+
+	dmmp_pg = (struct dmmp_path_group *)
+		malloc(sizeof(struct dmmp_path_group));
+
+	if (dmmp_pg != NULL) {
+		dmmp_pg->id = _DMMP_PATH_GROUP_ID_UNKNOWN;
+		dmmp_pg->status = DMMP_PATH_GROUP_STATUS_UNKNOWN;
+		dmmp_pg->priority = 0;
+		dmmp_pg->selector = NULL;
+		dmmp_pg->dmmp_p_count = 0;
+		dmmp_pg->dmmp_ps = NULL;
+	}
+	return dmmp_pg;
+}
+int _dmmp_path_group_update(struct dmmp_context *ctx,
+			    struct dmmp_path_group *dmmp_pg,
+			    json_object *j_obj_pg)
+{
+	int rc = DMMP_OK;
+	uint32_t id = 0;
+	int priority_int = -1 ;
+	const char *status_str = NULL;
+	const char *selector = NULL;
+	struct array_list *ar_ps = NULL;
+	int ar_ps_len = -1;
+	uint32_t i = 0;
+	struct dmmp_path *dmmp_p = NULL;
+
+	assert(ctx != NULL);
+	assert(dmmp_pg != NULL);
+	assert(j_obj_pg != NULL);
+
+	_json_obj_get_value(ctx, j_obj_pg, status_str, "dm_st",
+			    json_type_string, json_object_get_string, rc, out);
+
+	_json_obj_get_value(ctx, j_obj_pg, selector, "selector",
+			    json_type_string, json_object_get_string, rc, out);
+
+	_json_obj_get_value(ctx, j_obj_pg, priority_int, "pri",
+			    json_type_int, json_object_get_int, rc, out);
+
+	_json_obj_get_value(ctx, j_obj_pg, id, "group",
+			    json_type_int, json_object_get_int, rc, out);
+
+	dmmp_pg->priority = (priority_int <= 0) ? 0 : priority_int & UINT32_MAX;
+
+	_dmmp_null_or_empty_str_check(ctx, status_str, rc, out);
+	_dmmp_null_or_empty_str_check(ctx, selector, rc, out);
+
+	dmmp_pg->selector = strdup(selector);
+	_dmmp_alloc_null_check(ctx, dmmp_pg->selector, rc, out);
+
+	dmmp_pg->id = id;
+
+	if (dmmp_pg->id == _DMMP_PATH_GROUP_ID_UNKNOWN) {
+		rc = DMMP_ERR_BUG;
+		_error(ctx, "BUG: Got unknown(%d) path group ID",
+		       _DMMP_PATH_GROUP_ID_UNKNOWN);
+		goto out;
+	}
+
+	dmmp_pg->status = _dmmp_path_group_status_str_conv(ctx, status_str);
+
+	_json_obj_get_value(ctx, j_obj_pg, ar_ps, "paths",
+			    json_type_array, json_object_get_array, rc, out);
+
+	ar_ps_len = array_list_length(ar_ps);
+	if (ar_ps_len < 0) {
+		rc = DMMP_ERR_BUG;
+		_error(ctx, "BUG: Got negative length for ar_ps");
+		goto out;
+	}
+	else if (ar_ps_len == 0)
+		goto out;
+	else
+		dmmp_pg->dmmp_p_count = ar_ps_len & UINT32_MAX;
+
+	dmmp_pg->dmmp_ps = (struct dmmp_path **)
+		malloc(sizeof(struct dmmp_path *) * dmmp_pg->dmmp_p_count);
+	_dmmp_alloc_null_check(ctx, dmmp_pg->dmmp_ps, rc, out);
+	for (; i < dmmp_pg->dmmp_p_count; ++i)
+		dmmp_pg->dmmp_ps[i] = NULL;
+
+	for (i = 0; i < dmmp_pg->dmmp_p_count; ++i) {
+		dmmp_p = _dmmp_path_new();
+		_dmmp_alloc_null_check(ctx, dmmp_p, rc, out);
+		dmmp_pg->dmmp_ps[i] = dmmp_p;
+		_good(_dmmp_path_update(ctx, dmmp_p,
+					array_list_get_idx(ar_ps, i)),
+		      rc, out);
+	}
+
+	_debug(ctx, "Got path group id: %" PRIu32 "", dmmp_pg->id);
+	_debug(ctx, "Got path group priority: %" PRIu32 "", dmmp_pg->priority);
+	_debug(ctx, "Got path group status: %s(%" PRIu32 ")",
+	       dmmp_path_group_status_str(dmmp_pg->status), dmmp_pg->status);
+	_debug(ctx, "Got path group selector: '%s'", dmmp_pg->selector);
+
+out:
+	if (rc != DMMP_OK)
+		_dmmp_path_group_free(dmmp_pg);
+	return rc;
+}
+
+void _dmmp_path_group_free(struct dmmp_path_group *dmmp_pg)
+{
+	uint32_t i = 0;
+
+	if (dmmp_pg == NULL)
+		return;
+
+	free((char *) dmmp_pg->selector);
+
+	if (dmmp_pg->dmmp_ps != NULL) {
+		for (i = 0; i < dmmp_pg->dmmp_p_count; ++i) {
+			_dmmp_path_free(dmmp_pg->dmmp_ps[i]);
+		}
+		free(dmmp_pg->dmmp_ps);
+	}
+	free(dmmp_pg);
+}
+
+void dmmp_path_array_get(struct dmmp_path_group *mp_pg,
+			 struct dmmp_path ***mp_paths,
+			 uint32_t *dmmp_p_count)
+{
+	assert(mp_pg != NULL);
+	assert(mp_paths != NULL);
+	assert(dmmp_p_count != NULL);
+
+	*mp_paths = mp_pg->dmmp_ps;
+	*dmmp_p_count = mp_pg->dmmp_p_count;
+}
diff --git a/libdmmp/libdmmp_private.h b/libdmmp/libdmmp_private.h
new file mode 100644
index 0000000..e23c995
--- /dev/null
+++ b/libdmmp/libdmmp_private.h
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2015 - 2016 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Gris Ge <fge@redhat.com>
+ *         Todd Gill <tgill@redhat.com>
+ */
+
+#ifndef _LIB_DMMP_PRIVATE_H_
+#define _LIB_DMMP_PRIVATE_H_
+
+/*
+ * Notes:
+ *	Internal/Private functions does not check input argument but using
+ *	assert() to abort if NULL pointer found in argument.
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include <assert.h>
+#include <json.h>
+
+#include "libdmmp/libdmmp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define _good(rc, rc_val, out) \
+	do { \
+		rc_val = rc; \
+		if (rc_val != DMMP_OK) \
+			goto out; \
+	} while(0)
+
+#define _DMMP_PATH_GROUP_ID_UNKNOWN	0
+
+DMMP_DLL_LOCAL struct _num_str_conv {
+	const uint32_t value;
+	const char *str;
+};
+
+#define _dmmp_str_func_gen(func_name, var_type, var, conv_array) \
+const char *func_name(var_type var) { \
+	size_t i = 0; \
+	uint32_t tmp_var = var & UINT32_MAX; \
+	/* In the whole libdmmp, we don't have negative value */ \
+	for (; i < sizeof(conv_array)/sizeof(conv_array[0]); ++i) { \
+		if ((conv_array[i].value) == tmp_var) \
+			return conv_array[i].str; \
+	} \
+	return "Invalid argument"; \
+}
+
+#define _dmmp_str_conv_func_gen(func_name, ctx, var_name, out_type, \
+				unknown_value, conv_array) \
+static out_type func_name(struct dmmp_context *ctx, const char *var_name) { \
+	size_t i = 0; \
+	for (; i < sizeof(conv_array)/sizeof(conv_array[0]); ++i) { \
+		if (strcmp(conv_array[i].str, var_name) == 0) \
+			return conv_array[i].value; \
+	} \
+	_warn(ctx, "Got unknown " #var_name ": '%s'", var_name); \
+	return unknown_value; \
+}
+
+#define _json_obj_get_value(ctx, j_obj, out_value, key, value_type, \
+			    value_func, rc, out) \
+do { \
+	json_type j_type = json_type_null; \
+	json_object *j_obj_tmp = NULL; \
+	if (json_object_object_get_ex(j_obj, key, &j_obj_tmp) != TRUE) { \
+		_error(ctx, "Invalid JSON output from multipathd IPC: " \
+		       "key '%s' not found", key); \
+		rc = DMMP_ERR_IPC_ERROR; \
+		goto out; \
+	} \
+	if (j_obj_tmp == NULL) { \
+		_error(ctx, "BUG: Got NULL j_obj_tmp from " \
+		       "json_object_object_get_ex() while it return TRUE"); \
+		rc = DMMP_ERR_BUG; \
+		goto out; \
+	} \
+	j_type = json_object_get_type(j_obj_tmp); \
+	if (j_type != value_type) { \
+		_error(ctx, "Invalid value type for key'%s' of JSON output " \
+		       "from multipathd IPC. Should be %s(%d), " \
+		       "but got %s(%d)", key, json_type_to_name(value_type), \
+		       value_type, json_type_to_name(j_type), j_type); \
+		rc = DMMP_ERR_IPC_ERROR; \
+		goto out; \
+	} \
+	out_value = value_func(j_obj_tmp); \
+} while(0);
+
+DMMP_DLL_LOCAL int _dmmp_ipc_exec(struct dmmp_context *ctx, const char *cmd,
+				  char **output);
+
+DMMP_DLL_LOCAL struct dmmp_mpath *_dmmp_mpath_new(void);
+DMMP_DLL_LOCAL struct dmmp_path_group *_dmmp_path_group_new(void);
+DMMP_DLL_LOCAL struct dmmp_path *_dmmp_path_new(void);
+
+DMMP_DLL_LOCAL int _dmmp_mpath_update(struct dmmp_context *ctx,
+				      struct dmmp_mpath *dmmp_mp,
+				      json_object *j_obj_map);
+DMMP_DLL_LOCAL int _dmmp_path_group_update(struct dmmp_context *ctx,
+					   struct dmmp_path_group *dmmp_pg,
+					   json_object *j_obj_pg);
+DMMP_DLL_LOCAL int _dmmp_path_update(struct dmmp_context *ctx,
+				     struct dmmp_path *dmmp_p,
+				     json_object *j_obj_p);
+
+DMMP_DLL_LOCAL void _dmmp_mpath_free(struct dmmp_mpath *dmmp_mp);
+DMMP_DLL_LOCAL void _dmmp_path_group_free(struct dmmp_path_group *dmmp_pg);
+DMMP_DLL_LOCAL void _dmmp_path_group_array_free
+	(struct dmmp_path_group **dmmp_pgs, uint32_t dmmp_pg_count);
+DMMP_DLL_LOCAL void _dmmp_path_free(struct dmmp_path *dmmp_p);
+DMMP_DLL_LOCAL void _dmmp_log(struct dmmp_context *ctx, int priority,
+			      const char *file, int line,
+			      const char *func_name,
+			      const char *format, ...);
+DMMP_DLL_LOCAL void _dmmp_log_err_str(struct dmmp_context *ctx, int rc);
+
+DMMP_DLL_LOCAL void _dmmp_log_stderr(struct dmmp_context *ctx, int priority,
+				     const char *file, int line,
+				     const char *func_name, const char *format,
+				     va_list args);
+
+
+#define _dmmp_log_cond(ctx, prio, arg...) \
+	do { \
+		if (dmmp_context_log_priority_get(ctx) >= prio) \
+			_dmmp_log(ctx, prio, __FILE__, __LINE__, __FUNCTION__, \
+				  ## arg); \
+	} while (0)
+
+#define _debug(ctx, arg...) \
+	_dmmp_log_cond(ctx, DMMP_LOG_PRIORITY_DEBUG, ## arg)
+#define _info(ctx, arg...) \
+	_dmmp_log_cond(ctx, DMMP_LOG_PRIORITY_INFO, ## arg)
+#define _warn(ctx, arg...) \
+	_dmmp_log_cond(ctx, DMMP_LOG_PRIORITY_WARNING, ## arg)
+#define _error(ctx, arg...) \
+	_dmmp_log_cond(ctx, DMMP_LOG_PRIORITY_ERROR, ## arg)
+
+/*
+ * Check pointer returned by malloc() or strdup(), if NULL, set
+ * rc as DMMP_ERR_NO_MEMORY, report error and goto goto_out.
+ */
+#define _dmmp_alloc_null_check(ctx, ptr, rc, goto_out) \
+	do { \
+		if (ptr == NULL) { \
+			rc = DMMP_ERR_NO_MEMORY; \
+			_error(ctx, dmmp_strerror(rc)); \
+			goto goto_out; \
+		} \
+	} while(0)
+
+#define _dmmp_null_or_empty_str_check(ctx, var, rc, goto_out) \
+	do { \
+		if (var == NULL) { \
+			rc = DMMP_ERR_BUG; \
+			_error(ctx, "BUG: Got NULL " #var); \
+			goto goto_out; \
+		} \
+		if (strlen(var) == 0) { \
+			rc = DMMP_ERR_BUG; \
+			_error(ctx, "BUG: Got empty " #var); \
+			goto goto_out; \
+		} \
+	} while(0)
+
+#define _dmmp_getter_func_gen(func_name, struct_name, struct_data, \
+			      prop_name, prop_type) \
+	prop_type func_name(struct_name *struct_data) \
+	{ \
+		assert(struct_data != NULL); \
+		return struct_data->prop_name; \
+	}
+
+#define _dmmp_array_free_func_gen(func_name, struct_name, struct_free_func) \
+	void func_name(struct_name **ptr_array, uint32_t ptr_count) \
+	{ \
+		uint32_t i = 0; \
+		if (ptr_array == NULL) \
+			return; \
+		for (; i < ptr_count; ++i) \
+			struct_free_func(ptr_array[i]); \
+		free(ptr_array); \
+	}
+
+#ifdef __cplusplus
+} /* End of extern "C" */
+#endif
+
+#endif /* End of _LIB_DMMP_PRIVATE_H_ */
diff --git a/libdmmp/test/Makefile b/libdmmp/test/Makefile
new file mode 100644
index 0000000..68f1af3
--- /dev/null
+++ b/libdmmp/test/Makefile
@@ -0,0 +1,30 @@
+# Makefile
+#
+# Copyright (C) 2015-2016 Gris Ge <fge@redhat.com>
+#
+include ../../Makefile.inc
+
+_libdmmpdir=../$(libdmmpdir)
+_mpathcmddir=../$(mpathcmddir)
+
+TEST_EXEC = libdmmp_test
+SPD_TEST_EXEC = libdmmp_speed_test
+CFLAGS += -I$(_libdmmpdir)
+LDFLAGS += -L$(_libdmmpdir) -ldmmp
+
+all: $(TEST_EXEC) $(SPD_TEST_EXEC)
+
+check: $(TEST_EXEC) $(SPD_TEST_EXEC)
+	sudo env LD_LIBRARY_PATH=$(_libdmmpdir):$(_mpathcmddir) \
+		valgrind --quiet --leak-check=full \
+		--show-reachable=no --show-possibly-lost=no \
+		--trace-children=yes --error-exitcode=1 \
+		./$(TEST_EXEC)
+	$(MAKE) speed_test
+
+speed_test: $(SPD_TEST_EXEC)
+	sudo env LD_LIBRARY_PATH=$(_libdmmpdir):$(_mpathcmddir) \
+		time -p ./$(SPD_TEST_EXEC)
+
+clean:
+	rm -f $(TEST_EXEC)
diff --git a/libdmmp/test/libdmmp_speed_test.c b/libdmmp/test/libdmmp_speed_test.c
new file mode 100644
index 0000000..372cd39
--- /dev/null
+++ b/libdmmp/test/libdmmp_speed_test.c
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2015-2016 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Gris Ge <fge@redhat.com>
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include <pthread.h>
+#include <unistd.h>
+
+#include <libdmmp/libdmmp.h>
+
+int main(int argc, char *argv[])
+{
+	struct dmmp_context *ctx = NULL;
+	struct dmmp_mpath **dmmp_mps = NULL;
+	uint32_t dmmp_mp_count = 0;
+	int rc = EXIT_SUCCESS;
+
+	ctx = dmmp_context_new();
+	dmmp_context_log_priority_set(ctx, DMMP_LOG_PRIORITY_WARNING);
+
+	if (dmmp_mpath_array_get(ctx, &dmmp_mps, &dmmp_mp_count) != 0) {
+		printf("FAILED\n");
+		rc = EXIT_FAILURE;
+	} else {
+		printf("Got %" PRIu32 " mpath\n", dmmp_mp_count);
+		dmmp_mpath_array_free(dmmp_mps, dmmp_mp_count);
+	}
+	dmmp_context_free(ctx);
+	exit(rc);
+}
diff --git a/libdmmp/test/libdmmp_test.c b/libdmmp/test/libdmmp_test.c
new file mode 100644
index 0000000..3b10034
--- /dev/null
+++ b/libdmmp/test/libdmmp_test.c
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2015-2016 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Gris Ge <fge@redhat.com>
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include <pthread.h>
+#include <unistd.h>
+
+#include <libdmmp/libdmmp.h>
+
+#define FAIL(rc, out, ...) \
+	do { \
+		rc = EXIT_FAILURE; \
+		fprintf(stderr, "FAIL: "__VA_ARGS__ ); \
+		goto out; \
+	} while(0)
+#define PASS(...) fprintf(stdout, "PASS: "__VA_ARGS__ );
+#define FILE_NAME_SIZE 256
+#define TMO 10000	/* Forcing timeout to 10 seconds */
+
+int test_paths(struct dmmp_path_group *mp_pg)
+{
+	struct dmmp_path **mp_ps = NULL;
+	uint32_t mp_p_count = 0;
+	uint32_t i = 0;
+	const char *blk_name = NULL;
+	int rc = EXIT_SUCCESS;
+
+	dmmp_path_array_get(mp_pg, &mp_ps, &mp_p_count);
+	if (mp_p_count == 0)
+		FAIL(rc, out, "dmmp_path_array_get(): Got no path\n");
+	for (i = 0; i < mp_p_count; ++i) {
+		blk_name = dmmp_path_blk_name_get(mp_ps[i]);
+		if (blk_name == NULL)
+			FAIL(rc, out, "dmmp_path_blk_name_get(): Got NULL\n");
+		PASS("dmmp_path_blk_name_get(): %s\n", blk_name);
+		PASS("dmmp_path_status_get(): %" PRIu32 " -- %s\n",
+		     dmmp_path_status_get(mp_ps[i]),
+		     dmmp_path_status_str(dmmp_path_status_get(mp_ps[i])));
+	}
+out:
+	return rc;
+}
+
+int test_path_groups(struct dmmp_mpath *dmmp_mp)
+{
+	struct dmmp_path_group **dmmp_pgs = NULL;
+	uint32_t dmmp_pg_count = 0;
+	uint32_t i = 0;
+	int rc = EXIT_SUCCESS;
+
+	dmmp_path_group_array_get(dmmp_mp, &dmmp_pgs, &dmmp_pg_count);
+	if ((dmmp_pg_count == 0) && (dmmp_pgs != NULL))
+		FAIL(rc, out, "dmmp_path_group_array_get(): mp_pgs is not NULL "
+		     "but mp_pg_count is 0\n");
+	if ((dmmp_pg_count != 0) && (dmmp_pgs == NULL))
+		FAIL(rc, out, "dmmp_path_group_array_get(): mp_pgs is NULL "
+		     "but mp_pg_count is not 0\n");
+	if (dmmp_pg_count == 0)
+		FAIL(rc, out, "dmmp_path_group_array_get(): "
+		     "Got 0 path group\n");
+
+	PASS("dmmp_path_group_array_get(): Got %" PRIu32 " path groups\n",
+	     dmmp_pg_count);
+
+	for (i = 0; i < dmmp_pg_count; ++i) {
+		PASS("dmmp_path_group_id_get(): %" PRIu32 "\n",
+		     dmmp_path_group_id_get(dmmp_pgs[i]));
+		PASS("dmmp_path_group_priority_get(): %" PRIu32 "\n",
+		     dmmp_path_group_priority_get(dmmp_pgs[i]));
+		PASS("dmmp_path_group_status_get(): %" PRIu32 " -- %s\n",
+		     dmmp_path_group_status_get(dmmp_pgs[i]),
+		     dmmp_path_group_status_str
+			(dmmp_path_group_status_get(dmmp_pgs[i])));
+		PASS("dmmp_path_group_selector_get(): %s\n",
+		     dmmp_path_group_selector_get(dmmp_pgs[i]));
+		rc = test_paths(dmmp_pgs[i]);
+		if (rc != 0)
+			goto out;
+	}
+out:
+	return rc;
+}
+
+int main(int argc, char *argv[])
+{
+	struct dmmp_context *ctx = NULL;
+	struct dmmp_mpath **dmmp_mps = NULL;
+	uint32_t dmmp_mp_count = 0;
+	const char *name = NULL;
+	const char *wwid = NULL;
+	uint32_t i = 0;
+	int rc = EXIT_SUCCESS;
+
+	ctx = dmmp_context_new();
+	dmmp_context_log_priority_set(ctx, DMMP_LOG_PRIORITY_DEBUG);
+	dmmp_context_userdata_set(ctx, ctx);
+	dmmp_context_userdata_set(ctx, NULL);
+	dmmp_context_timeout_set(ctx, TMO);
+	if (dmmp_context_timeout_get(ctx) != TMO)
+		FAIL(rc, out, "dmmp_context_timeout_set(): Failed to set "
+		     "timeout to %u", TMO);
+
+	if (dmmp_mpath_array_get(ctx, &dmmp_mps, &dmmp_mp_count) != 0)
+		FAIL(rc, out, "dmmp_mpath_array_get(): rc != 0\n");
+	if (dmmp_mp_count == 0)
+		FAIL(rc, out, "dmmp_mpath_array_get(): "
+		     "Got no multipath devices\n");
+	PASS("dmmp_mpath_array_get(): Got %" PRIu32 " mpath\n", dmmp_mp_count);
+	for (i = 0; i < dmmp_mp_count; ++i) {
+		name = dmmp_mpath_name_get(dmmp_mps[i]);
+		wwid = dmmp_mpath_wwid_get(dmmp_mps[i]);
+		if ((name == NULL) ||(wwid == NULL))
+			FAIL(rc, out,
+			     "dmmp_mpath_array_get(): Got NULL name or wwid");
+		PASS("dmmp_mpath_array_get(): Got mpath: %s %s\n", name, wwid);
+		rc = test_path_groups(dmmp_mps[i]);
+		if (rc != 0)
+			goto out;
+	}
+	dmmp_mpath_array_free(dmmp_mps, dmmp_mp_count);
+out:
+	dmmp_context_free(ctx);
+	exit(rc);
+}
-- 
2.9.0

  parent reply	other threads:[~2016-07-01 13:06 UTC|newest]

Thread overview: 51+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-01-28  3:52 [PATCH] Introducing multipath C API <libdmmp/libdmmp.h> Gris Ge
2016-01-28  9:15 ` Hannes Reinecke
2016-01-28  9:40   ` Gris Ge
2016-02-01 13:13   ` Todd Gill
2016-02-01 13:36     ` Hannes Reinecke
2016-02-12  8:10       ` [PATCH V2] Introducing multipath C API Gris Ge
2016-02-12  8:10         ` [PATCH V2] Introducing multipath C API <libdmmp/libdmmp.h> Gris Ge
2016-03-04 16:06           ` Benjamin Marzinski
2016-03-05  9:46             ` Gris Ge
2016-07-01 12:46           ` [PATCH V3 0/3] Introducing multipath C API Gris Ge
2016-07-01 12:46             ` [PATCH V3 1/3] multipath-tools: Increase MAX_REPLY_LEN Gris Ge
2016-07-01 14:46               ` Hannes Reinecke
2016-07-02  0:25                 ` Gris Ge
2016-07-04  6:05                   ` Hannes Reinecke
2016-07-04  9:11                     ` Gris Ge
2016-07-04  9:17                       ` [PATCH V4 0/3] Introducing multipath C API Gris Ge
2016-07-04  9:17                         ` [PATCH V4 1/3] multipath-tools: New way to limit the IPC command length Gris Ge
2016-07-04  9:17                         ` [PATCH V4 2/3] multipath-tools: Set errno mpath_recv_reply() when failure Gris Ge
2016-07-04  9:17                         ` [PATCH V4 3/3] multipath-tools: Introducing multipath C API Gris Ge
2016-07-04  9:27                         ` [PATCH V4 0/3] " Gris Ge
2016-07-04  9:29                         ` Please ignore this patch set. ([PATCH V4 0/3] Introducing multipath C API) Gris Ge
2016-07-04  9:40                         ` [PATCH V5 0/3] Introducing multipath C API Gris Ge
2016-07-04  9:40                           ` [PATCH V5 1/3] multipath-tools: New way to limit the IPC command length Gris Ge
2016-07-04  9:40                           ` [PATCH V5 2/3] multipath-tools: Set errno mpath_recv_reply() when failure Gris Ge
2016-07-04  9:40                           ` [PATCH V5 3/3] multipath-tools: Introducing multipath C API Gris Ge
2016-07-01 12:46             ` [PATCH V3 2/3] multipath-tools: Set errno mpath_recv_reply() when failure Gris Ge
2016-07-01 12:46             ` [PATCH V3 3/3] multipath-tools: Introducing multipath C API <libdmmp/libdmmp.h> Gris Ge
2016-07-01 12:55             ` [PATCH " Gris Ge
2016-07-01 13:06             ` Gris Ge [this message]
2016-07-12  6:50 ` [PATCH V6 0/3] Introducing multipath C API Gris Ge
2016-07-12  6:50   ` [PATCH V6 1/3] multipath-tools: New way to limit the IPC command length Gris Ge
2016-07-15 21:35     ` Benjamin Marzinski
2016-07-18 12:38       ` Gris Ge
2016-08-12 15:57       ` Bart Van Assche
2016-08-12 21:35         ` Benjamin Marzinski
2016-08-12 21:49           ` Bart Van Assche
2016-07-12  6:50   ` [PATCH V6 2/3] multipath-tools: Set errno mpath_recv_reply() when failure Gris Ge
2016-07-12  6:50   ` [PATCH V6 3/3] multipath-tools: Introducing multipath C API Gris Ge
2016-07-15 21:36   ` [PATCH V6 0/3] " Benjamin Marzinski
2016-08-12 12:12 ` [PATCH V7 0/4] multipath: " Gris Ge
2016-08-12 12:12   ` [PATCH V7 1/4] libmpathcmd: Block SIGPIPE when write() Gris Ge
2016-08-12 16:01     ` Bart Van Assche
2016-08-12 12:12   ` [PATCH V7 2/4] multipath-tools: New way to limit the IPC command length Gris Ge
2016-08-12 15:48     ` Bart Van Assche
2016-08-12 21:53     ` Benjamin Marzinski
2016-08-12 12:12   ` [PATCH V7 3/4] multipath-tools: Set errno mpath_recv_reply() when failure Gris Ge
2016-08-12 12:12   ` [PATCH V7 4/4] multipath-tools: Introducing multipath C API Gris Ge
2017-02-24 12:50     ` [PATCH V5] " Gris Ge
2017-02-27  5:56       ` Christophe Varoqui
2017-02-24 13:07     ` Gris Ge
2016-08-12 13:26   ` [PATCH V7 0/4] multipath: " Gris Ge

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20160701130613.209313-1-fge@redhat.com \
    --to=fge@redhat.com \
    --cc=dm-devel@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.