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